diff --git a/sqlite3.go b/sqlite3.go index d1ff406..552a2ab 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -2007,6 +2007,13 @@ func (s *SQLiteStmt) execSync(args []namedValue) (driver.Result, error) { return &SQLiteResult{id: int64(rowid), changes: int64(changes)}, nil } +// Readonly reports if this statement is considered readonly by SQLite. +// +// See: https://sqlite.org/c3ref/stmt_readonly.html +func (s *SQLiteStmt) Readonly() bool { + return C.sqlite3_stmt_readonly(s.s) == 1 +} + // Close the rows. func (rc *SQLiteRows) Close() error { rc.s.mu.Lock() diff --git a/sqlite3_go113_test.go b/sqlite3_go113_test.go index 6f74e6b..a010cb7 100644 --- a/sqlite3_go113_test.go +++ b/sqlite3_go113_test.go @@ -11,6 +11,7 @@ import ( "context" "database/sql" "database/sql/driver" + "errors" "os" "testing" ) @@ -76,3 +77,43 @@ func TestBeginTxCancel(t *testing.T) { }() } } + +func TestStmtReadonly(t *testing.T) { + db, err := sql.Open("sqlite3", ":memory:") + if err != nil { + t.Fatal(err) + } + + _, err = db.Exec("CREATE TABLE t (count INT)") + if err != nil { + t.Fatal(err) + } + + isRO := func(query string) bool { + c, err := db.Conn(context.Background()) + if err != nil { + return false + } + + var ro bool + c.Raw(func(dc interface{}) error { + stmt, err := dc.(*SQLiteConn).Prepare(query) + if err != nil { + return err + } + if stmt == nil { + return errors.New("stmt is nil") + } + ro = stmt.(*SQLiteStmt).Readonly() + return nil + }) + return ro // On errors ro will remain false. + } + + if !isRO(`select * from t`) { + t.Error("select not seen as read-only") + } + if isRO(`insert into t values (1), (2)`) { + t.Error("insert seen as read-only") + } +}