// Package driver provides a database/sql driver for SQLite. // // Importing package driver registers a [database/sql] driver named "sqlite3". // You may also need to import package embed. // // import _ "github.com/ncruces/go-sqlite3/driver" // import _ "github.com/ncruces/go-sqlite3/embed" // // The data source name for "sqlite3" databases can be a filename or a "file:" [URI]. // // # Default transaction mode // // The [TRANSACTION] mode can be specified using "_txlock": // // sql.Open("sqlite3", "file:demo.db?_txlock=immediate") // // Possible values are: "deferred" (the default), "immediate", "exclusive". // Regardless of "_txlock": // - a [linearizable] transaction is always "exclusive"; // - a [serializable] transaction is always "immediate"; // - a [read-only] transaction is always "deferred". // // # Datatypes In SQLite // // SQLite is dynamically typed. // Columns can mostly hold any value regardless of their declared type. // SQLite supports most [driver.Value] types out of the box, // but bool and [time.Time] require special care. // // Booleans can be stored on any column type and scanned back to a *bool. // However, if scanned to a *any, booleans may either become an // int64, string or bool, depending on the declared type of the column. // If you use BOOLEAN for your column type, // 1 and 0 will always scan as true and false. // // # Working with time // // Time values can similarly be stored on any column type. // The time encoding/decoding format can be specified using "_timefmt": // // sql.Open("sqlite3", "file:demo.db?_timefmt=sqlite") // // Special values are: "auto" (the default), "sqlite", "rfc3339"; // - "auto" encodes as RFC 3339 and decodes any [format] supported by SQLite; // - "sqlite" encodes as SQLite and decodes any [format] supported by SQLite; // - "rfc3339" encodes and decodes RFC 3339 only. // // You can also set "_timefmt" to an arbitrary [sqlite3.TimeFormat] or [time.Layout]. // // If you encode as RFC 3339 (the default), // consider using the TIME [collating sequence] to produce time-ordered sequences. // // If you encode as RFC 3339 (the default), // time values will scan back to a *time.Time unless your column type is TEXT. // Otherwise, if scanned to a *any, time values may either become an // int64, float64 or string, depending on the time format and declared type of the column. // If you use DATE, TIME, DATETIME, or TIMESTAMP for your column type, // "_timefmt" will be used to decode values. // // To scan values in custom formats, [sqlite3.TimeFormat.Scanner] may be helpful. // To bind values in custom formats, [sqlite3.TimeFormat.Encode] them before binding. // // When using a custom time struct, you'll have to implement // [database/sql/driver.Valuer] and [database/sql.Scanner]. // // The Value method should ideally encode to a time [format] supported by SQLite. // This ensures SQL date and time functions work as they should, // and that your schema works with other SQLite tools. // [sqlite3.TimeFormat.Encode] may help. // // The Scan method needs to take into account that the value it receives can be of differing types. // It can already be a [time.Time], if the driver decoded the value according to "_timefmt" rules. // Or it can be a: string, int64, float64, []byte, or nil, // depending on the column type and whoever wrote the value. // [sqlite3.TimeFormat.Decode] may help. // // # Setting PRAGMAs // // [PRAGMA] statements can be specified using "_pragma": // // sql.Open("sqlite3", "file:demo.db?_pragma=busy_timeout(10000)") // // If no PRAGMAs are specified, a busy timeout of 1 minute is set. // // Order matters: // encryption keys, busy timeout and locking mode should be the first PRAGMAs set, // in that order. // // [URI]: https://sqlite.org/uri.html // [PRAGMA]: https://sqlite.org/pragma.html // [TRANSACTION]: https://sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions // [linearizable]: https://pkg.go.dev/database/sql#TxOptions // [serializable]: https://pkg.go.dev/database/sql#TxOptions // [read-only]: https://pkg.go.dev/database/sql#TxOptions // [format]: https://sqlite.org/lang_datefunc.html#time_values // [collating sequence]: https://sqlite.org/datatype3.html#collating_sequences
package driver import ( ) // This variable can be replaced with -ldflags: // // go build -ldflags="-X github.com/ncruces/go-sqlite3/driver.driverName=sqlite" var driverName = "sqlite3" func init() { if driverName != "" { sql.Register(driverName, &SQLite{}) } } // Open opens the SQLite database specified by dataSourceName as a [database/sql.DB]. // // Open accepts zero, one, or two callbacks (nil callbacks are ignored). // The first callback is called when the driver opens a new connection. // The second callback is called before the driver closes a connection. // The [sqlite3.Conn] can be used to execute queries, register functions, etc. func ( string, ...func(*sqlite3.Conn) error) (*sql.DB, error) { if len() > 2 { return nil, sqlite3.MISUSE } var , func(*sqlite3.Conn) error if len() > 1 { = [1] } if len() > 0 { = [0] } , := newConnector(, , ) if != nil { return nil, } return sql.OpenDB(), nil } // SQLite implements [database/sql/driver.Driver]. type SQLite struct{} var ( // Ensure these interfaces are implemented: _ driver.DriverContext = &SQLite{} ) // Open implements [database/sql/driver.Driver]. func ( *SQLite) ( string) (driver.Conn, error) { , := newConnector(, nil, nil) if != nil { return nil, } return .Connect(context.Background()) } // OpenConnector implements [database/sql/driver.DriverContext]. func ( *SQLite) ( string) (driver.Connector, error) { return newConnector(, nil, nil) } func newConnector( string, , func(*sqlite3.Conn) error) (*connector, error) { := connector{name: , init: , term: } var , string if strings.HasPrefix(, "file:") { if , , := strings.Cut(, "?"); { , := url.ParseQuery() if != nil { return nil, } = .Get("_txlock") = .Get("_timefmt") .pragmas = .Has("_pragma") } } switch { case "", "deferred", "concurrent", "immediate", "exclusive": .txLock = default: return nil, fmt.Errorf("sqlite3: invalid _txlock: %s", ) } switch { case "": .tmRead = sqlite3.TimeFormatAuto .tmWrite = sqlite3.TimeFormatDefault case "sqlite": .tmRead = sqlite3.TimeFormatAuto .tmWrite = sqlite3.TimeFormat3 case "rfc3339": .tmRead = sqlite3.TimeFormatDefault .tmWrite = sqlite3.TimeFormatDefault default: .tmRead = sqlite3.TimeFormat() .tmWrite = sqlite3.TimeFormat() } return &, nil } type connector struct { init func(*sqlite3.Conn) error term func(*sqlite3.Conn) error name string txLock string tmRead sqlite3.TimeFormat tmWrite sqlite3.TimeFormat pragmas bool } func ( *connector) () driver.Driver { return &SQLite{} } func ( *connector) ( context.Context) ( driver.Conn, error) { := &conn{ txLock: .txLock, tmRead: .tmRead, tmWrite: .tmWrite, } .Conn, = sqlite3.OpenContext(, .name) if != nil { return nil, } defer func() { if == nil { .Close() } }() if := .Conn.SetInterrupt(); != { defer .Conn.SetInterrupt() } if !.pragmas { = .Conn.BusyTimeout(time.Minute) if != nil { return nil, } } if .init != nil { = .init(.Conn) if != nil { return nil, } } if .pragmas || .init != nil { , , := .Conn.Prepare(`PRAGMA query_only`) if != nil { return nil, } defer .Close() if .Step() { .readOnly = .ColumnBool(0) } = .Close() if != nil { return nil, } } if .term != nil { = .Conn.Trace(sqlite3.TRACE_CLOSE, func(sqlite3.TraceEvent, any, any) error { return .term(.Conn) }) if != nil { return nil, } } return , nil } // Conn is implemented by the SQLite [database/sql] driver connections. // // It can be used to access SQLite features like [online backup]: // // db, err := driver.Open("temp.db") // if err != nil { // log.Fatal(err) // } // defer db.Close() // // conn, err := db.Conn(context.TODO()) // if err != nil { // log.Fatal(err) // } // defer conn.Close() // // err = conn.Raw(func(driverConn any) error { // conn := driverConn.(driver.Conn) // return conn.Raw().Backup("main", "backup.db") // }) // if err != nil { // log.Fatal(err) // } // // [online backup]: https://sqlite.org/backup.html type Conn interface { Raw() *sqlite3.Conn driver.Conn driver.ConnBeginTx driver.ConnPrepareContext } type conn struct { *sqlite3.Conn txLock string txReset string tmRead sqlite3.TimeFormat tmWrite sqlite3.TimeFormat readOnly bool } var ( // Ensure these interfaces are implemented: _ Conn = &conn{} _ driver.ExecerContext = &conn{} ) func ( *conn) () *sqlite3.Conn { return .Conn } // Deprecated: use BeginTx instead. func ( *conn) () (driver.Tx, error) { // notest return .BeginTx(context.Background(), driver.TxOptions{}) } func ( *conn) ( context.Context, driver.TxOptions) (driver.Tx, error) { var string switch .Isolation { default: return nil, util.IsolationErr case driver.IsolationLevel(sql.LevelLinearizable): = "exclusive" case driver.IsolationLevel(sql.LevelSerializable): = "immediate" case driver.IsolationLevel(sql.LevelDefault): if !.ReadOnly { = .txLock } } .txReset = `` := `BEGIN ` + if .ReadOnly && !.readOnly { += ` ; PRAGMA query_only=on` .txReset = `; PRAGMA query_only=off` } if := .Conn.SetInterrupt(); != { defer .Conn.SetInterrupt() } := .Conn.Exec() if != nil { return nil, } return , nil } func ( *conn) () error { := .Conn.Exec(`COMMIT` + .txReset) if != nil && !.Conn.GetAutocommit() { .Rollback() } return } func ( *conn) () error { // ROLLBACK even if interrupted. := context.Background() if := .Conn.SetInterrupt(); != { defer .Conn.SetInterrupt() } return .Conn.Exec(`ROLLBACK` + .txReset) } func ( *conn) ( string) (driver.Stmt, error) { // notest return .PrepareContext(context.Background(), ) } func ( *conn) ( context.Context, string) (driver.Stmt, error) { if := .Conn.SetInterrupt(); != { defer .Conn.SetInterrupt() } , , := .Conn.Prepare() if != nil { return nil, } if notWhitespace() { .Close() return nil, util.TailErr } return &stmt{Stmt: , tmRead: .tmRead, tmWrite: .tmWrite, inputs: -2}, nil } func ( *conn) ( context.Context, string, []driver.NamedValue) (driver.Result, error) { if len() != 0 { // Slow path. return nil, driver.ErrSkip } if , := .(*saveptCtx); { // Called from driver.Savepoint. .Savepoint = .Conn.Savepoint() return resultRowsAffected(0), nil } if := .Conn.SetInterrupt(); != { defer .Conn.SetInterrupt() } := .Conn.Exec() if != nil { return nil, } return newResult(.Conn), nil } func ( *conn) ( *driver.NamedValue) error { // Fast path: short circuit argument verification. // Arguments will be rejected by conn.ExecContext. return nil } type stmt struct { *sqlite3.Stmt tmWrite sqlite3.TimeFormat tmRead sqlite3.TimeFormat inputs int } var ( // Ensure these interfaces are implemented: _ driver.StmtExecContext = &stmt{} _ driver.StmtQueryContext = &stmt{} _ driver.NamedValueChecker = &stmt{} ) func ( *stmt) () int { if .inputs >= -1 { return .inputs } := .Stmt.BindCount() for := 1; <= ; ++ { if .Stmt.BindName() != "" { .inputs = -1 return -1 } } .inputs = return } // Deprecated: use ExecContext instead. func ( *stmt) ( []driver.Value) (driver.Result, error) { // notest return .ExecContext(context.Background(), namedValues()) } // Deprecated: use QueryContext instead. func ( *stmt) ( []driver.Value) (driver.Rows, error) { // notest return .QueryContext(context.Background(), namedValues()) } func ( *stmt) ( context.Context, []driver.NamedValue) (driver.Result, error) { := .setupBindings() if != nil { return nil, } := .Stmt.Conn() if := .SetInterrupt(); != { defer .SetInterrupt() } = errors.Join( .Stmt.Exec(), .Stmt.ClearBindings()) if != nil { return nil, } return newResult(), nil } func ( *stmt) ( context.Context, []driver.NamedValue) (driver.Rows, error) { := .setupBindings() if != nil { return nil, } return &rows{ctx: , stmt: }, nil } func ( *stmt) ( []driver.NamedValue) ( error) { var [3]int for , := range { := [:0] if .Name == "" { = append(, .Ordinal) } else { for , := range [...]string{":", "@", "$"} { if := .Stmt.BindIndex( + .Name); != 0 { = append(, ) } } } for , := range { switch a := .Value.(type) { case bool: = .Stmt.BindBool(, ) case int: = .Stmt.BindInt(, ) case int64: = .Stmt.BindInt64(, ) case float64: = .Stmt.BindFloat(, ) case string: = .Stmt.BindText(, ) case []byte: = .Stmt.BindBlob(, ) case sqlite3.ZeroBlob: = .Stmt.BindZeroBlob(, int64()) case time.Time: = .Stmt.BindTime(, , .tmWrite) case util.JSON: = .Stmt.BindJSON(, .Value) case util.Pointer: = .Stmt.BindPointer(, .Value) case nil: = .Stmt.BindNull() default: panic(util.AssertErr()) } if != nil { return } } } return nil } func ( *stmt) ( *driver.NamedValue) error { switch .Value.(type) { case bool, int, int64, float64, string, []byte, time.Time, sqlite3.ZeroBlob, util.JSON, util.Pointer, nil: return nil default: return driver.ErrSkip } } func newResult( *sqlite3.Conn) driver.Result { := .Changes() if != 0 { := .LastInsertRowID() if != 0 { return result{, } } } return resultRowsAffected() } type result struct{ lastInsertId, rowsAffected int64 } func ( result) () (int64, error) { return .lastInsertId, nil } func ( result) () (int64, error) { return .rowsAffected, nil } type resultRowsAffected int64 func ( resultRowsAffected) () (int64, error) { return 0, nil } func ( resultRowsAffected) () (int64, error) { return int64(), nil } type scantype byte const ( _ANY scantype = iota _INT _REAL _TEXT _BLOB _NULL _BOOL _TIME _NOT_NULL ) var ( _ [0]struct{} = [scantype(sqlite3.INTEGER) - _INT]struct{}{} _ [0]struct{} = [scantype(sqlite3.FLOAT) - _REAL]struct{}{} _ [0]struct{} = [scantype(sqlite3.TEXT) - _TEXT]struct{}{} _ [0]struct{} = [scantype(sqlite3.BLOB) - _BLOB]struct{}{} _ [0]struct{} = [scantype(sqlite3.NULL) - _NULL]struct{}{} _ [0]struct{} = [_NOT_NULL & (_NOT_NULL - 1)]struct{}{} ) func scanFromDecl( string) scantype { // These types are only used before we have rows, // and otherwise as type hints. // The first few ensure STRICT tables are strictly typed. // The other two are type hints for booleans and time. switch { case "INT", "INTEGER": return _INT case "REAL": return _REAL case "TEXT": return _TEXT case "BLOB": return _BLOB case "BOOLEAN": return _BOOL case "DATE", "TIME", "DATETIME", "TIMESTAMP": return _TIME } return _ANY } type rows struct { ctx context.Context *stmt names []string types []string scans []scantype } var ( // Ensure these interfaces are implemented: _ driver.RowsColumnTypeDatabaseTypeName = &rows{} _ driver.RowsColumnTypeNullable = &rows{} ) func ( *rows) () error { return errors.Join( .Stmt.Reset(), .Stmt.ClearBindings()) } func ( *rows) () []string { if .names == nil { := .Stmt.ColumnCount() := make([]string, ) for := range { [] = .Stmt.ColumnName() } .names = } return .names } func ( *rows) ( int) scantype { if .scans == nil { := len(.names) := make([]scantype, ) for := range { [] = scanFromDecl(strings.ToUpper(.Stmt.ColumnDeclType())) } .scans = } return .scans[] &^ _NOT_NULL } func ( *rows) () { if .types == nil { := .Stmt.Conn() := len(.names) := make([]string, ) := make([]scantype, ) for := range { var bool if := .Stmt.ColumnOriginName(); != "" { [], _, , _, _, _ = .TableColumnMetadata( .Stmt.ColumnDatabaseName(), .Stmt.ColumnTableName(), ) [] = strings.ToUpper([]) [] = scanFromDecl([]) if { [] |= _NOT_NULL } } } .types = .scans = } } func ( *rows) ( int) string { .loadColumnMetadata() := .types[] if := len(); > 0 && [-1] == ')' { if := strings.LastIndexByte(, '('); >= 0 { = [:] } } return strings.TrimSpace() } func ( *rows) ( int) (, bool) { .loadColumnMetadata() = .scans[]&^_NOT_NULL == 0 return , ! } func ( *rows) ( int) ( reflect.Type) { .loadColumnMetadata() := .scans[] &^ _NOT_NULL if .Stmt.Busy() { // SQLite is dynamically typed and we now have a row. // Always use the type of the value itself, // unless the scan type is more specific // and can scan the actual value. := scantype(.Stmt.ColumnType()) := true switch { case == _TIME && != _BLOB && != _NULL: := .Stmt.ColumnTime(, .tmRead) = .IsZero() case == _BOOL && == _INT: := .Stmt.ColumnInt64() = != 0 && != 1 case == _BLOB && == _NULL: = false } if { = } } switch { case _INT: return reflect.TypeFor[int64]() case _REAL: return reflect.TypeFor[float64]() case _TEXT: return reflect.TypeFor[string]() case _BLOB: return reflect.TypeFor[[]byte]() case _BOOL: return reflect.TypeFor[bool]() case _TIME: return reflect.TypeFor[time.Time]() default: return reflect.TypeFor[any]() } } func ( *rows) ( []driver.Value) error { := .Stmt.Conn() if := .SetInterrupt(.ctx); != .ctx { defer .SetInterrupt() } if !.Stmt.Step() { if := .Stmt.Err(); != nil { return } return io.EOF } := unsafe.Slice((*any)(unsafe.SliceData()), len()) if := .Stmt.ColumnsRaw(...); != nil { return } for := range { := .scanType() if , := [].([]byte); { if len() == cap() { // a BLOB continue } if != _TEXT { switch .tmWrite { case "", time.RFC3339, time.RFC3339Nano: , := maybeTime() if { [] = continue } } } [] = string() } switch { case _TIME: , := .tmRead.Decode([]) if == nil { [] = } case _BOOL: switch [] { case int64(0): [] = false case int64(1): [] = true } } } return nil }