2023-02-14 18:21:18 +00:00
|
|
|
// Package driver provides a database/sql driver for SQLite.
|
2023-02-28 14:50:15 +00:00
|
|
|
//
|
|
|
|
|
// 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].
|
|
|
|
|
//
|
|
|
|
|
// The [TRANSACTION] mode can be specified using "_txlock":
|
|
|
|
|
//
|
|
|
|
|
// sql.Open("sqlite3", "file:demo.db?_txlock=immediate")
|
|
|
|
|
//
|
|
|
|
|
// [PRAGMA] statements can be specified using "_pragma":
|
|
|
|
|
//
|
|
|
|
|
// sql.Open("sqlite3", "file:demo.db?_pragma=busy_timeout(10000)&_pragma=locking_mode(normal)")
|
|
|
|
|
//
|
|
|
|
|
// If no PRAGMAs are specifed, a busy timeout of 1 minute
|
|
|
|
|
// and normal locking mode are used.
|
|
|
|
|
//
|
|
|
|
|
// [URI]: https://www.sqlite.org/uri.html
|
|
|
|
|
// [PRAGMA]: https://www.sqlite.org/pragma.html
|
|
|
|
|
// [TRANSACTION]: https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions
|
2023-02-14 18:21:18 +00:00
|
|
|
package driver
|
|
|
|
|
|
|
|
|
|
import (
|
2023-02-18 02:16:11 +00:00
|
|
|
"context"
|
2023-02-14 18:21:18 +00:00
|
|
|
"database/sql"
|
|
|
|
|
"database/sql/driver"
|
2023-02-20 13:30:01 +00:00
|
|
|
"fmt"
|
2023-02-17 02:21:07 +00:00
|
|
|
"io"
|
2023-02-18 12:20:42 +00:00
|
|
|
"net/url"
|
|
|
|
|
"strings"
|
2023-02-17 02:21:07 +00:00
|
|
|
"time"
|
2023-02-14 18:21:18 +00:00
|
|
|
|
|
|
|
|
"github.com/ncruces/go-sqlite3"
|
2023-03-29 15:01:25 +01:00
|
|
|
"github.com/ncruces/go-sqlite3/internal/util"
|
2023-02-14 18:21:18 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
|
sql.Register("sqlite3", sqlite{})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type sqlite struct{}
|
|
|
|
|
|
2023-02-28 16:03:31 +00:00
|
|
|
func (sqlite) Open(name string) (_ driver.Conn, err error) {
|
2023-03-16 12:27:44 +00:00
|
|
|
c, err := sqlite3.Open(name)
|
2023-02-17 16:19:55 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2023-02-19 16:16:13 +00:00
|
|
|
|
2023-02-20 13:38:03 +00:00
|
|
|
var txBegin string
|
2023-02-28 16:03:31 +00:00
|
|
|
var pragmas []string
|
|
|
|
|
if strings.HasPrefix(name, "file:") {
|
|
|
|
|
if _, after, ok := strings.Cut(name, "?"); ok {
|
|
|
|
|
query, _ := url.ParseQuery(after)
|
|
|
|
|
|
|
|
|
|
switch s := query.Get("_txlock"); s {
|
|
|
|
|
case "":
|
|
|
|
|
txBegin = "BEGIN"
|
|
|
|
|
case "deferred", "immediate", "exclusive":
|
|
|
|
|
txBegin = "BEGIN " + s
|
|
|
|
|
default:
|
|
|
|
|
c.Close()
|
|
|
|
|
return nil, fmt.Errorf("sqlite3: invalid _txlock: %s", s)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pragmas = query["_pragma"]
|
2023-02-19 16:16:13 +00:00
|
|
|
}
|
2023-02-18 12:20:42 +00:00
|
|
|
}
|
2023-02-28 16:03:31 +00:00
|
|
|
if len(pragmas) == 0 {
|
|
|
|
|
err := c.Exec(`
|
|
|
|
|
PRAGMA busy_timeout=60000;
|
|
|
|
|
PRAGMA locking_mode=normal;
|
|
|
|
|
`)
|
|
|
|
|
if err != nil {
|
|
|
|
|
c.Close()
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2023-02-17 16:19:55 +00:00
|
|
|
}
|
2023-02-19 16:16:13 +00:00
|
|
|
|
|
|
|
|
return conn{
|
|
|
|
|
conn: c,
|
|
|
|
|
txBegin: txBegin,
|
|
|
|
|
}, nil
|
2023-02-14 18:21:18 +00:00
|
|
|
}
|
|
|
|
|
|
2023-02-18 12:20:42 +00:00
|
|
|
type conn struct {
|
2023-03-08 18:05:18 +00:00
|
|
|
conn *sqlite3.Conn
|
|
|
|
|
txBegin string
|
|
|
|
|
txCommit string
|
|
|
|
|
txRollback string
|
2023-02-18 12:20:42 +00:00
|
|
|
}
|
2023-02-17 02:21:07 +00:00
|
|
|
|
|
|
|
|
var (
|
2023-02-17 16:19:55 +00:00
|
|
|
// Ensure these interfaces are implemented:
|
2023-02-23 02:22:57 +00:00
|
|
|
_ driver.ExecerContext = conn{}
|
|
|
|
|
_ driver.ConnBeginTx = conn{}
|
2023-03-10 15:50:11 +00:00
|
|
|
_ driver.Validator = conn{}
|
2023-02-27 13:45:32 +00:00
|
|
|
_ sqlite3.DriverConn = conn{}
|
2023-02-17 02:21:07 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func (c conn) Close() error {
|
|
|
|
|
return c.conn.Close()
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-10 15:50:11 +00:00
|
|
|
func (c conn) IsValid() (valid bool) {
|
|
|
|
|
r, err := c.conn.Pragma("locking_mode")
|
|
|
|
|
return err == nil && len(r) == 1 && r[0] == "normal"
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-17 02:21:07 +00:00
|
|
|
func (c conn) Begin() (driver.Tx, error) {
|
2023-02-19 16:16:13 +00:00
|
|
|
return c.BeginTx(context.Background(), driver.TxOptions{})
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-08 17:39:41 +00:00
|
|
|
func (c conn) BeginTx(_ context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
2023-02-19 16:16:13 +00:00
|
|
|
txBegin := c.txBegin
|
2023-02-25 15:34:24 +00:00
|
|
|
c.txCommit = `COMMIT`
|
2023-03-08 18:05:18 +00:00
|
|
|
c.txRollback = `ROLLBACK`
|
|
|
|
|
|
2023-02-19 16:16:13 +00:00
|
|
|
if opts.ReadOnly {
|
2023-03-08 17:39:41 +00:00
|
|
|
query_only, err := c.conn.Pragma("query_only")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2023-02-19 16:16:13 +00:00
|
|
|
txBegin = `
|
2023-02-20 13:30:01 +00:00
|
|
|
BEGIN deferred;
|
2023-02-25 15:34:24 +00:00
|
|
|
PRAGMA query_only=on`
|
2023-03-08 18:05:18 +00:00
|
|
|
c.txCommit = `
|
|
|
|
|
ROLLBACK;
|
|
|
|
|
PRAGMA query_only=` + query_only[0]
|
|
|
|
|
c.txRollback = c.txCommit
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch opts.Isolation {
|
|
|
|
|
default:
|
2023-03-29 15:01:25 +01:00
|
|
|
return nil, util.IsolationErr
|
2023-03-08 18:05:18 +00:00
|
|
|
case
|
|
|
|
|
driver.IsolationLevel(sql.LevelDefault),
|
|
|
|
|
driver.IsolationLevel(sql.LevelSerializable):
|
|
|
|
|
break
|
|
|
|
|
case driver.IsolationLevel(sql.LevelReadUncommitted):
|
|
|
|
|
read_uncommitted, err := c.conn.Pragma("read_uncommitted")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
txBegin += `; PRAGMA read_uncommitted=on`
|
|
|
|
|
c.txCommit += `; PRAGMA read_uncommitted=` + read_uncommitted[0]
|
|
|
|
|
c.txRollback += `; PRAGMA read_uncommitted=` + read_uncommitted[0]
|
2023-02-19 16:16:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err := c.conn.Exec(txBegin)
|
2023-02-17 02:21:07 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return c, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c conn) Commit() error {
|
2023-02-25 15:34:24 +00:00
|
|
|
err := c.conn.Exec(c.txCommit)
|
2023-03-08 18:05:18 +00:00
|
|
|
if err != nil && !c.conn.GetAutocommit() {
|
2023-02-17 02:21:07 +00:00
|
|
|
c.Rollback()
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
}
|
2023-02-14 18:21:18 +00:00
|
|
|
|
2023-02-17 02:21:07 +00:00
|
|
|
func (c conn) Rollback() error {
|
2023-03-08 18:05:18 +00:00
|
|
|
return c.conn.Exec(c.txRollback)
|
2023-02-17 02:21:07 +00:00
|
|
|
}
|
2023-02-14 18:21:18 +00:00
|
|
|
|
|
|
|
|
func (c conn) Prepare(query string) (driver.Stmt, error) {
|
2023-02-18 02:57:47 +00:00
|
|
|
s, tail, err := c.conn.Prepare(query)
|
2023-02-17 02:21:07 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2023-02-18 02:57:47 +00:00
|
|
|
if tail != "" {
|
|
|
|
|
// Check if the tail contains any SQL.
|
2023-02-19 12:44:26 +00:00
|
|
|
st, _, err := c.conn.Prepare(tail)
|
2023-02-18 02:57:47 +00:00
|
|
|
if err != nil {
|
2023-02-19 12:44:26 +00:00
|
|
|
s.Close()
|
2023-02-18 02:57:47 +00:00
|
|
|
return nil, err
|
|
|
|
|
}
|
2023-02-19 12:44:26 +00:00
|
|
|
if st != nil {
|
2023-02-18 02:57:47 +00:00
|
|
|
s.Close()
|
2023-02-19 12:44:26 +00:00
|
|
|
st.Close()
|
2023-03-29 15:01:25 +01:00
|
|
|
return nil, util.TailErr
|
2023-02-18 02:57:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
2023-02-17 02:21:07 +00:00
|
|
|
return stmt{s, c.conn}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-27 13:45:32 +00:00
|
|
|
func (c conn) PrepareContext(_ context.Context, query string) (driver.Stmt, error) {
|
|
|
|
|
return c.Prepare(query)
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-18 02:57:47 +00:00
|
|
|
func (c conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
|
|
|
|
if len(args) != 0 {
|
|
|
|
|
// Slow path.
|
|
|
|
|
return nil, driver.ErrSkip
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-24 14:56:49 +00:00
|
|
|
old := c.conn.SetInterrupt(ctx)
|
|
|
|
|
defer c.conn.SetInterrupt(old)
|
2023-02-18 02:57:47 +00:00
|
|
|
|
|
|
|
|
err := c.conn.Exec(query)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result{
|
2023-02-27 13:45:32 +00:00
|
|
|
c.conn.LastInsertRowID(),
|
|
|
|
|
c.conn.Changes(),
|
2023-02-18 02:57:47 +00:00
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-08 16:29:29 +00:00
|
|
|
func (c conn) Savepoint() sqlite3.Savepoint {
|
2023-02-27 13:45:32 +00:00
|
|
|
return c.conn.Savepoint()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c conn) OpenBlob(db, table, column string, row int64, write bool) (*sqlite3.Blob, error) {
|
|
|
|
|
return c.conn.OpenBlob(db, table, column, row, write)
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-17 02:21:07 +00:00
|
|
|
type stmt struct {
|
|
|
|
|
stmt *sqlite3.Stmt
|
|
|
|
|
conn *sqlite3.Conn
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-17 16:19:55 +00:00
|
|
|
var (
|
|
|
|
|
// Ensure these interfaces are implemented:
|
2023-02-22 14:19:56 +00:00
|
|
|
_ driver.StmtExecContext = stmt{}
|
|
|
|
|
_ driver.StmtQueryContext = stmt{}
|
|
|
|
|
_ driver.NamedValueChecker = stmt{}
|
2023-02-17 16:19:55 +00:00
|
|
|
)
|
|
|
|
|
|
2023-02-17 02:21:07 +00:00
|
|
|
func (s stmt) Close() error {
|
|
|
|
|
return s.stmt.Close()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s stmt) NumInput() int {
|
2023-02-20 13:30:01 +00:00
|
|
|
n := s.stmt.BindCount()
|
|
|
|
|
for i := 1; i <= n; i++ {
|
|
|
|
|
if s.stmt.BindName(i) != "" {
|
|
|
|
|
return -1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return n
|
2023-02-17 02:21:07 +00:00
|
|
|
}
|
|
|
|
|
|
2023-02-18 02:16:11 +00:00
|
|
|
// Deprecated: use ExecContext instead.
|
2023-02-17 02:21:07 +00:00
|
|
|
func (s stmt) Exec(args []driver.Value) (driver.Result, error) {
|
2023-02-18 02:16:11 +00:00
|
|
|
return s.ExecContext(context.Background(), namedValues(args))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Deprecated: use QueryContext instead.
|
|
|
|
|
func (s stmt) Query(args []driver.Value) (driver.Rows, error) {
|
|
|
|
|
return s.QueryContext(context.Background(), namedValues(args))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
2023-02-20 13:38:03 +00:00
|
|
|
// Use QueryContext to setup bindings.
|
|
|
|
|
// No need to close rows: that simply resets the statement, exec does the same.
|
2023-02-18 02:16:11 +00:00
|
|
|
_, err := s.QueryContext(ctx, args)
|
2023-02-14 18:21:18 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2023-02-17 02:21:07 +00:00
|
|
|
|
|
|
|
|
err = s.stmt.Exec()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result{
|
|
|
|
|
int64(s.conn.LastInsertRowID()),
|
|
|
|
|
int64(s.conn.Changes()),
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-18 02:16:11 +00:00
|
|
|
func (s stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
|
|
|
|
err := s.stmt.ClearBindings()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ids [3]int
|
|
|
|
|
for _, arg := range args {
|
|
|
|
|
ids := ids[:0]
|
|
|
|
|
if arg.Name == "" {
|
|
|
|
|
ids = append(ids, arg.Ordinal)
|
|
|
|
|
} else {
|
|
|
|
|
for _, prefix := range []string{":", "@", "$"} {
|
|
|
|
|
if id := s.stmt.BindIndex(prefix + arg.Name); id != 0 {
|
|
|
|
|
ids = append(ids, id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, id := range ids {
|
|
|
|
|
switch a := arg.Value.(type) {
|
|
|
|
|
case bool:
|
|
|
|
|
err = s.stmt.BindBool(id, a)
|
2023-02-22 14:19:56 +00:00
|
|
|
case int:
|
|
|
|
|
err = s.stmt.BindInt(id, a)
|
2023-02-18 02:16:11 +00:00
|
|
|
case int64:
|
|
|
|
|
err = s.stmt.BindInt64(id, a)
|
|
|
|
|
case float64:
|
|
|
|
|
err = s.stmt.BindFloat(id, a)
|
|
|
|
|
case string:
|
|
|
|
|
err = s.stmt.BindText(id, a)
|
|
|
|
|
case []byte:
|
|
|
|
|
err = s.stmt.BindBlob(id, a)
|
2023-02-22 14:19:56 +00:00
|
|
|
case sqlite3.ZeroBlob:
|
|
|
|
|
err = s.stmt.BindZeroBlob(id, int64(a))
|
2023-02-18 02:16:11 +00:00
|
|
|
case time.Time:
|
|
|
|
|
err = s.stmt.BindText(id, a.Format(time.RFC3339Nano))
|
|
|
|
|
case nil:
|
|
|
|
|
err = s.stmt.BindNull(id)
|
|
|
|
|
default:
|
2023-03-29 15:01:25 +01:00
|
|
|
panic(util.AssertErr())
|
2023-02-18 02:16:11 +00:00
|
|
|
}
|
2023-02-17 02:21:07 +00:00
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-02-18 02:16:11 +00:00
|
|
|
|
|
|
|
|
return rows{ctx, s.stmt, s.conn}, nil
|
2023-02-17 02:21:07 +00:00
|
|
|
}
|
|
|
|
|
|
2023-02-22 14:19:56 +00:00
|
|
|
func (s stmt) CheckNamedValue(arg *driver.NamedValue) error {
|
|
|
|
|
switch arg.Value.(type) {
|
|
|
|
|
case bool, int, int64, float64, string, []byte,
|
|
|
|
|
sqlite3.ZeroBlob, time.Time, nil:
|
|
|
|
|
return nil
|
|
|
|
|
default:
|
|
|
|
|
return driver.ErrSkip
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-17 02:21:07 +00:00
|
|
|
type result struct{ lastInsertId, rowsAffected int64 }
|
|
|
|
|
|
|
|
|
|
func (r result) LastInsertId() (int64, error) {
|
|
|
|
|
return r.lastInsertId, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r result) RowsAffected() (int64, error) {
|
|
|
|
|
return r.rowsAffected, nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-18 02:16:11 +00:00
|
|
|
type rows struct {
|
|
|
|
|
ctx context.Context
|
|
|
|
|
stmt *sqlite3.Stmt
|
|
|
|
|
conn *sqlite3.Conn
|
|
|
|
|
}
|
2023-02-17 02:21:07 +00:00
|
|
|
|
|
|
|
|
func (r rows) Close() error {
|
2023-02-18 02:16:11 +00:00
|
|
|
return r.stmt.Reset()
|
2023-02-17 02:21:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r rows) Columns() []string {
|
2023-02-18 02:16:11 +00:00
|
|
|
count := r.stmt.ColumnCount()
|
2023-02-17 02:21:07 +00:00
|
|
|
columns := make([]string, count)
|
|
|
|
|
for i := range columns {
|
2023-02-18 02:16:11 +00:00
|
|
|
columns[i] = r.stmt.ColumnName(i)
|
2023-02-17 02:21:07 +00:00
|
|
|
}
|
|
|
|
|
return columns
|
2023-02-14 18:21:18 +00:00
|
|
|
}
|
|
|
|
|
|
2023-02-17 02:21:07 +00:00
|
|
|
func (r rows) Next(dest []driver.Value) error {
|
2023-02-24 14:56:49 +00:00
|
|
|
old := r.conn.SetInterrupt(r.ctx)
|
|
|
|
|
defer r.conn.SetInterrupt(old)
|
2023-02-18 02:16:11 +00:00
|
|
|
|
|
|
|
|
if !r.stmt.Step() {
|
|
|
|
|
if err := r.stmt.Err(); err != nil {
|
2023-02-17 12:30:07 +00:00
|
|
|
return err
|
2023-02-17 02:21:07 +00:00
|
|
|
}
|
2023-02-17 12:30:07 +00:00
|
|
|
return io.EOF
|
2023-02-17 02:21:07 +00:00
|
|
|
}
|
2023-02-14 18:21:18 +00:00
|
|
|
|
2023-02-17 02:21:07 +00:00
|
|
|
for i := range dest {
|
2023-02-18 02:16:11 +00:00
|
|
|
switch r.stmt.ColumnType(i) {
|
2023-02-17 02:21:07 +00:00
|
|
|
case sqlite3.INTEGER:
|
2023-02-18 02:16:11 +00:00
|
|
|
dest[i] = r.stmt.ColumnInt64(i)
|
2023-02-17 02:21:07 +00:00
|
|
|
case sqlite3.FLOAT:
|
2023-02-18 02:16:11 +00:00
|
|
|
dest[i] = r.stmt.ColumnFloat(i)
|
2023-02-17 02:21:07 +00:00
|
|
|
case sqlite3.TEXT:
|
2023-02-24 10:50:16 +00:00
|
|
|
dest[i] = maybeTime(r.stmt.ColumnText(i))
|
2023-02-17 02:21:07 +00:00
|
|
|
case sqlite3.BLOB:
|
2023-03-20 02:16:42 +00:00
|
|
|
dest[i] = r.stmt.ColumnRawBlob(i)
|
2023-02-17 10:40:43 +00:00
|
|
|
case sqlite3.NULL:
|
2023-02-17 12:30:07 +00:00
|
|
|
if buf, ok := dest[i].([]byte); ok {
|
|
|
|
|
dest[i] = buf[0:0]
|
|
|
|
|
} else {
|
|
|
|
|
dest[i] = nil
|
|
|
|
|
}
|
2023-02-17 10:40:43 +00:00
|
|
|
default:
|
2023-03-29 15:01:25 +01:00
|
|
|
panic(util.AssertErr())
|
2023-02-17 02:21:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
2023-02-14 18:21:18 +00:00
|
|
|
|
2023-02-18 02:16:11 +00:00
|
|
|
return r.stmt.Err()
|
2023-02-17 02:21:07 +00:00
|
|
|
}
|