diff options
Diffstat (limited to 'vendor/github.com/ncruces/go-sqlite3/driver')
| -rw-r--r-- | vendor/github.com/ncruces/go-sqlite3/driver/driver.go | 186 | ||||
| -rw-r--r-- | vendor/github.com/ncruces/go-sqlite3/driver/time.go | 11 |
2 files changed, 119 insertions, 78 deletions
diff --git a/vendor/github.com/ncruces/go-sqlite3/driver/driver.go b/vendor/github.com/ncruces/go-sqlite3/driver/driver.go index 21799aeb2..9250cf39d 100644 --- a/vendor/github.com/ncruces/go-sqlite3/driver/driver.go +++ b/vendor/github.com/ncruces/go-sqlite3/driver/driver.go @@ -20,22 +20,45 @@ // - 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") // -// Possible values are: "auto" (the default), "sqlite", "rfc3339"; +// 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), -// consider using the TIME [collating sequence] to produce a time-ordered sequence. +// 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 other formats, [sqlite3.TimeFormat.Scanner] may be helpful. -// To bind values in other formats, [sqlite3.TimeFormat.Encode] them before binding. +// 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]. @@ -48,7 +71,7 @@ // 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 what whoever wrote the value. +// depending on the column type and whoever wrote the value. // [sqlite3.TimeFormat.Decode] may help. // // # Setting PRAGMAs @@ -358,13 +381,10 @@ func (c *conn) Commit() error { } func (c *conn) Rollback() error { - err := c.Conn.Exec(`ROLLBACK` + c.txReset) - if errors.Is(err, sqlite3.INTERRUPT) { - old := c.Conn.SetInterrupt(context.Background()) - defer c.Conn.SetInterrupt(old) - err = c.Conn.Exec(`ROLLBACK` + c.txReset) - } - return err + // ROLLBACK even if interrupted. + old := c.Conn.SetInterrupt(context.Background()) + defer c.Conn.SetInterrupt(old) + return c.Conn.Exec(`ROLLBACK` + c.txReset) } func (c *conn) Prepare(query string) (driver.Stmt, error) { @@ -598,6 +618,28 @@ const ( _TIME ) +func scanFromDecl(decl 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 decl { + 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 +} + var ( // Ensure these interfaces are implemented: _ driver.RowsColumnTypeDatabaseTypeName = &rows{} @@ -622,6 +664,18 @@ func (r *rows) Columns() []string { return r.names } +func (r *rows) scanType(index int) scantype { + if r.scans == nil { + count := r.Stmt.ColumnCount() + scans := make([]scantype, count) + for i := range scans { + scans[i] = scanFromDecl(strings.ToUpper(r.Stmt.ColumnDeclType(i))) + } + r.scans = scans + } + return r.scans[index] +} + func (r *rows) loadColumnMetadata() { if r.nulls == nil { count := r.Stmt.ColumnCount() @@ -635,24 +689,7 @@ func (r *rows) loadColumnMetadata() { r.Stmt.ColumnTableName(i), col) types[i] = strings.ToUpper(types[i]) - // 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 types[i] { - case "INT", "INTEGER": - scans[i] = _INT - case "REAL": - scans[i] = _REAL - case "TEXT": - scans[i] = _TEXT - case "BLOB": - scans[i] = _BLOB - case "BOOLEAN": - scans[i] = _BOOL - case "DATE", "TIME", "DATETIME", "TIMESTAMP": - scans[i] = _TIME - } + scans[i] = scanFromDecl(types[i]) } } r.nulls = nulls @@ -661,27 +698,15 @@ func (r *rows) loadColumnMetadata() { } } -func (r *rows) declType(index int) string { - if r.types == nil { - count := r.Stmt.ColumnCount() - types := make([]string, count) - for i := range types { - types[i] = strings.ToUpper(r.Stmt.ColumnDeclType(i)) - } - r.types = types - } - return r.types[index] -} - func (r *rows) ColumnTypeDatabaseTypeName(index int) string { r.loadColumnMetadata() - decltype := r.types[index] - if len := len(decltype); len > 0 && decltype[len-1] == ')' { - if i := strings.LastIndexByte(decltype, '('); i >= 0 { - decltype = decltype[:i] + decl := r.types[index] + if len := len(decl); len > 0 && decl[len-1] == ')' { + if i := strings.LastIndexByte(decl, '('); i >= 0 { + decl = decl[:i] } } - return strings.TrimSpace(decltype) + return strings.TrimSpace(decl) } func (r *rows) ColumnTypeNullable(index int) (nullable, ok bool) { @@ -748,36 +773,49 @@ func (r *rows) Next(dest []driver.Value) error { } data := unsafe.Slice((*any)(unsafe.SliceData(dest)), len(dest)) - err := r.Stmt.Columns(data...) - for i := range dest { - if t, ok := r.decodeTime(i, dest[i]); ok { - dest[i] = t - } + if err := r.Stmt.ColumnsRaw(data...); err != nil { + return err } - return err -} - -func (r *rows) decodeTime(i int, v any) (_ time.Time, ok bool) { - switch v := v.(type) { - case int64, float64: - // could be a time value - case string: - if r.tmWrite != "" && r.tmWrite != time.RFC3339 && r.tmWrite != time.RFC3339Nano { + for i := range dest { + scan := r.scanType(i) + switch v := dest[i].(type) { + case int64: + if scan == _BOOL { + switch v { + case 1: + dest[i] = true + case 0: + dest[i] = false + } + continue + } + case []byte: + if len(v) == cap(v) { // a BLOB + continue + } + if scan != _TEXT { + switch r.tmWrite { + case "", time.RFC3339, time.RFC3339Nano: + t, ok := maybeTime(v) + if ok { + dest[i] = t + continue + } + } + } + dest[i] = string(v) + case float64: break + default: + continue } - t, ok := maybeTime(v) - if ok { - return t, true + if scan == _TIME { + t, err := r.tmRead.Decode(dest[i]) + if err == nil { + dest[i] = t + continue + } } - default: - return } - switch r.declType(i) { - case "DATE", "TIME", "DATETIME", "TIMESTAMP": - // could be a time value - default: - return - } - t, err := r.tmRead.Decode(v) - return t, err == nil + return nil } diff --git a/vendor/github.com/ncruces/go-sqlite3/driver/time.go b/vendor/github.com/ncruces/go-sqlite3/driver/time.go index b3ebdd263..4d48bd8dc 100644 --- a/vendor/github.com/ncruces/go-sqlite3/driver/time.go +++ b/vendor/github.com/ncruces/go-sqlite3/driver/time.go @@ -1,12 +1,15 @@ package driver -import "time" +import ( + "bytes" + "time" +) // Convert a string in [time.RFC3339Nano] format into a [time.Time] // if it roundtrips back to the same string. // This way times can be persisted to, and recovered from, the database, // but if a string is needed, [database/sql] will recover the same string. -func maybeTime(text string) (_ time.Time, _ bool) { +func maybeTime(text []byte) (_ time.Time, _ bool) { // Weed out (some) values that can't possibly be // [time.RFC3339Nano] timestamps. if len(text) < len("2006-01-02T15:04:05Z") { @@ -21,8 +24,8 @@ func maybeTime(text string) (_ time.Time, _ bool) { // Slow path. var buf [len(time.RFC3339Nano)]byte - date, err := time.Parse(time.RFC3339Nano, text) - if err == nil && text == string(date.AppendFormat(buf[:0], time.RFC3339Nano)) { + date, err := time.Parse(time.RFC3339Nano, string(text)) + if err == nil && bytes.Equal(text, date.AppendFormat(buf[:0], time.RFC3339Nano)) { return date, true } return |
