summaryrefslogtreecommitdiff
path: root/vendor/github.com/ncruces/go-sqlite3/driver
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/ncruces/go-sqlite3/driver')
-rw-r--r--vendor/github.com/ncruces/go-sqlite3/driver/driver.go186
-rw-r--r--vendor/github.com/ncruces/go-sqlite3/driver/time.go11
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