From 2866d285fb411ad5f7ec4b9eeed588a2b22115af Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Tue, 19 Jun 2018 17:53:53 +0200 Subject: [PATCH] Rewrite --- _driver/connection_go18.go | 70 ---- {_driver => driver}/callback.go | 5 +- {_driver => driver}/callback_test.go | 7 + driver/connection.go | 309 +++++++++++++++++- driver/connection_go18.go | 53 +++ {_driver => driver}/connection_go18_test.go | 0 driver/connector.go | 5 + driver/const.go | 53 +++ .../sqlite3_context.go => driver/context.go | 5 +- driver/driver.go | 69 +++- driver/driver_go110.go | 16 + {_driver => driver}/error.go | 41 ++- {_driver => driver}/error_test.go | 0 driver/func.go | 274 ++++++++++++++++ driver/hooks.go | 69 ++++ driver/limit.go | 58 ++++ driver/result.go | 217 ++++++++++++ driver/sqlite3.go | 150 --------- driver/statement.go | 243 ++++++++++++++ driver/statement_go18.go | 37 +++ driver/transaction.go | 60 ++++ driver/util.go | 45 +++ 22 files changed, 1559 insertions(+), 227 deletions(-) delete mode 100644 _driver/connection_go18.go rename {_driver => driver}/callback.go (99%) rename {_driver => driver}/callback_test.go (94%) rename {_driver => driver}/connection_go18_test.go (100%) create mode 100644 driver/const.go rename _driver/sqlite3_context.go => driver/context.go (97%) rename {_driver => driver}/error.go (88%) rename {_driver => driver}/error_test.go (100%) create mode 100644 driver/func.go create mode 100644 driver/hooks.go create mode 100644 driver/limit.go create mode 100644 driver/result.go delete mode 100644 driver/sqlite3.go create mode 100644 driver/statement.go create mode 100644 driver/statement_go18.go create mode 100644 driver/transaction.go create mode 100644 driver/util.go diff --git a/_driver/connection_go18.go b/_driver/connection_go18.go deleted file mode 100644 index ded06ae..0000000 --- a/_driver/connection_go18.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (C) 2018 The Go-SQLite3 Authors. -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -// +build cgo -// +build go1.8 - -package sqlite3 - -import ( - "database/sql/driver" - "errors" - - "context" -) - -// Ping implement Pinger. -func (c *SQLiteConn) Ping(ctx context.Context) error { - if c.db == nil { - return errors.New("Connection was closed") - } - return nil -} - -// QueryContext implement QueryerContext. -func (c *SQLiteConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { - list := make([]namedValue, len(args)) - for i, nv := range args { - list[i] = namedValue(nv) - } - return c.query(ctx, query, list) -} - -// ExecContext implement ExecerContext. -func (c *SQLiteConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { - list := make([]namedValue, len(args)) - for i, nv := range args { - list[i] = namedValue(nv) - } - return c.exec(ctx, query, list) -} - -// PrepareContext implement ConnPrepareContext. -func (c *SQLiteConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { - return c.prepare(ctx, query) -} - -// BeginTx implement ConnBeginTx. -func (c *SQLiteConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { - return c.begin(ctx) -} - -// QueryContext implement QueryerContext. -func (s *SQLiteStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { - list := make([]namedValue, len(args)) - for i, nv := range args { - list[i] = namedValue(nv) - } - return s.query(ctx, list) -} - -// ExecContext implement ExecerContext. -func (s *SQLiteStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { - list := make([]namedValue, len(args)) - for i, nv := range args { - list[i] = namedValue(nv) - } - return s.exec(ctx, list) -} diff --git a/_driver/callback.go b/driver/callback.go similarity index 99% rename from _driver/callback.go rename to driver/callback.go index 5a735c0..3070f91 100644 --- a/_driver/callback.go +++ b/driver/callback.go @@ -1,8 +1,10 @@ -// Copyright (C) 2014 Yasuhiro Matsumoto . +// Copyright (C) 2018 The Go-SQLite3 Authors. // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. +// +build cgo + package sqlite3 // You can't export a Go function to C and have definitions in the C @@ -16,6 +18,7 @@ package sqlite3 #else #include #endif + #include void _sqlite3_result_text(sqlite3_context* ctx, const char* s); diff --git a/_driver/callback_test.go b/driver/callback_test.go similarity index 94% rename from _driver/callback_test.go rename to driver/callback_test.go index 5c61f44..0407f0a 100644 --- a/_driver/callback_test.go +++ b/driver/callback_test.go @@ -1,3 +1,10 @@ +// Copyright (C) 2018 The Go-SQLite3 Authors. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build cgo + package sqlite3 import ( diff --git a/driver/connection.go b/driver/connection.go index 54c60c1..0427956 100644 --- a/driver/connection.go +++ b/driver/connection.go @@ -3,7 +3,314 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. +// +build cgo + package sqlite3 -type SQLiteConn struct { +/* +#ifndef USE_LIBSQLITE3 +#include +#else +#include +#endif + +#include + +static int +_sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs) { +#ifdef SQLITE_OPEN_URI + return sqlite3_open_v2(filename, ppDb, flags | SQLITE_OPEN_URI, zVfs); +#else + return sqlite3_open_v2(filename, ppDb, flags, zVfs); +#endif +} +*/ +import "C" +import ( + "context" + "database/sql/driver" + "errors" + "fmt" + "reflect" + "runtime" + "strings" + "sync" + "time" + "unsafe" +) + +var ( + _ driver.Conn = (*SQLiteConn)(nil) + _ driver.Execer = (*SQLiteConn)(nil) +) + +// SQLiteConn implement sql.Conn. +type SQLiteConn struct { + mu sync.Mutex + db *C.sqlite3 + loc *time.Location + txlock string + funcs []*functionInfo + aggregators []*aggInfo +} + +type functionInfo struct { + f reflect.Value + argConverters []callbackArgConverter + variadicConverter callbackArgConverter + retConverter callbackRetConverter +} + +func (fi *functionInfo) Call(ctx *C.sqlite3_context, argv []*C.sqlite3_value) { + args, err := callbackConvertArgs(argv, fi.argConverters, fi.variadicConverter) + if err != nil { + callbackError(ctx, err) + return + } + + ret := fi.f.Call(args) + + if len(ret) == 2 && ret[1].Interface() != nil { + callbackError(ctx, ret[1].Interface().(error)) + return + } + + err = fi.retConverter(ctx, ret[0]) + if err != nil { + callbackError(ctx, err) + return + } +} + +type aggInfo struct { + constructor reflect.Value + + // Active aggregator objects for aggregations in flight. The + // aggregators are indexed by a counter stored in the aggregation + // user data space provided by sqlite. + active map[int64]reflect.Value + next int64 + + stepArgConverters []callbackArgConverter + stepVariadicConverter callbackArgConverter + + doneRetConverter callbackRetConverter +} + +func (ai *aggInfo) agg(ctx *C.sqlite3_context) (int64, reflect.Value, error) { + aggIdx := (*int64)(C.sqlite3_aggregate_context(ctx, C.int(8))) + if *aggIdx == 0 { + *aggIdx = ai.next + ret := ai.constructor.Call(nil) + if len(ret) == 2 && ret[1].Interface() != nil { + return 0, reflect.Value{}, ret[1].Interface().(error) + } + if ret[0].IsNil() { + return 0, reflect.Value{}, errors.New("aggregator constructor returned nil state") + } + ai.next++ + ai.active[*aggIdx] = ret[0] + } + return *aggIdx, ai.active[*aggIdx], nil +} + +func (ai *aggInfo) Step(ctx *C.sqlite3_context, argv []*C.sqlite3_value) { + _, agg, err := ai.agg(ctx) + if err != nil { + callbackError(ctx, err) + return + } + + args, err := callbackConvertArgs(argv, ai.stepArgConverters, ai.stepVariadicConverter) + if err != nil { + callbackError(ctx, err) + return + } + + ret := agg.MethodByName("Step").Call(args) + if len(ret) == 1 && ret[0].Interface() != nil { + callbackError(ctx, ret[0].Interface().(error)) + return + } +} + +func (ai *aggInfo) Done(ctx *C.sqlite3_context) { + idx, agg, err := ai.agg(ctx) + if err != nil { + callbackError(ctx, err) + return + } + defer func() { delete(ai.active, idx) }() + + ret := agg.MethodByName("Done").Call(nil) + if len(ret) == 2 && ret[1].Interface() != nil { + callbackError(ctx, ret[1].Interface().(error)) + return + } + + err = ai.doneRetConverter(ctx, ret[0]) + if err != nil { + callbackError(ctx, err) + return + } +} + +type namedValue struct { + Name string + Ordinal int + Value driver.Value +} + +// Query implements Queryer. +func (c *SQLiteConn) Query(query string, args []driver.Value) (driver.Rows, error) { + list := make([]namedValue, len(args)) + for i, v := range args { + list[i] = namedValue{ + Ordinal: i + 1, + Value: v, + } + } + return c.query(context.Background(), query, list) +} + +func (c *SQLiteConn) query(ctx context.Context, query string, args []namedValue) (driver.Rows, error) { + start := 0 + for { + s, err := c.prepare(ctx, query) + if err != nil { + return nil, err + } + s.(*SQLiteStmt).cls = true + na := s.NumInput() + if len(args) < na { + return nil, fmt.Errorf("not enough args to execute query: want %d got %d", na, len(args)) + } + for i := 0; i < na; i++ { + args[i].Ordinal -= start + } + rows, err := s.(*SQLiteStmt).query(ctx, args[:na]) + if err != nil && err != driver.ErrSkip { + s.Close() + return rows, err + } + args = args[na:] + start += na + tail := s.(*SQLiteStmt).t + if tail == "" { + return rows, nil + } + rows.Close() + s.Close() + query = tail + } +} + +// Exec implements Execer. +func (c *SQLiteConn) Exec(query string, args []driver.Value) (driver.Result, error) { + fmt.Println("Exec()") + list := make([]namedValue, len(args)) + for i, v := range args { + list[i] = namedValue{ + Ordinal: i + 1, + Value: v, + } + } + return c.exec(context.Background(), query, list) +} + +func (c *SQLiteConn) exec(ctx context.Context, query string, args []namedValue) (driver.Result, error) { + start := 0 + for { + s, err := c.prepare(ctx, query) + if err != nil { + return nil, err + } + var res driver.Result + if s.(*SQLiteStmt).s != nil { + na := s.NumInput() + if len(args) < na { + s.Close() + return nil, fmt.Errorf("not enough args to execute query: want %d got %d", na, len(args)) + } + for i := 0; i < na; i++ { + args[i].Ordinal -= start + } + res, err = s.(*SQLiteStmt).exec(ctx, args[:na]) + if err != nil && err != driver.ErrSkip { + s.Close() + fmt.Printf("exec() %s\n", err) + return nil, err + } + args = args[na:] + start += na + } + tail := s.(*SQLiteStmt).t + s.Close() + if tail == "" { + return res, nil + } + query = tail + } +} + +// AutoCommit return which currently auto commit or not. +func (c *SQLiteConn) AutoCommit() bool { + return int(C.sqlite3_get_autocommit(c.db)) != 0 +} + +// GetFilename returns the absolute path to the file containing +// the requested schema. When passed an empty string, it will +// instead use the database's default schema: "main". +// See: sqlite3_db_filename, https://www.sqlite.org/c3ref/db_filename.html +func (c *SQLiteConn) GetFilename(schemaName string) string { + if schemaName == "" { + schemaName = "main" + } + return C.GoString(C.sqlite3_db_filename(c.db, C.CString(schemaName))) +} + +// Close the connection. +func (c *SQLiteConn) Close() error { + rv := C.sqlite3_close_v2(c.db) + if rv != C.SQLITE_OK { + return c.lastError() + } + deleteHandles(c) + c.mu.Lock() + c.db = nil + c.mu.Unlock() + runtime.SetFinalizer(c, nil) + return nil +} + +func (c *SQLiteConn) dbConnOpen() bool { + if c == nil { + return false + } + c.mu.Lock() + defer c.mu.Unlock() + return c.db != nil +} + +// Prepare the query string. Return a new statement. +func (c *SQLiteConn) Prepare(query string) (driver.Stmt, error) { + return c.prepare(context.Background(), query) +} + +func (c *SQLiteConn) prepare(ctx context.Context, query string) (driver.Stmt, error) { + pquery := C.CString(query) + defer C.free(unsafe.Pointer(pquery)) + var s *C.sqlite3_stmt + var tail *C.char + rv := C.sqlite3_prepare_v2(c.db, pquery, -1, &s, &tail) + if rv != C.SQLITE_OK { + return nil, c.lastError() + } + var t string + if tail != nil && *tail != '\000' { + t = strings.TrimSpace(C.GoString(tail)) + } + ss := &SQLiteStmt{c: c, s: s, t: t} + runtime.SetFinalizer(ss, (*SQLiteStmt).Close) + return ss, nil } diff --git a/driver/connection_go18.go b/driver/connection_go18.go index c0f7f32..a21e487 100644 --- a/driver/connection_go18.go +++ b/driver/connection_go18.go @@ -3,6 +3,59 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. +// +build cgo // +build go1.8 package sqlite3 + +import ( + "database/sql/driver" + "errors" + + "context" +) + +// Enforce implementation +var ( + _ driver.ConnBeginTx = (*SQLiteConn)(nil) + _ driver.ConnPrepareContext = (*SQLiteConn)(nil) + _ driver.ExecerContext = (*SQLiteConn)(nil) + _ driver.QueryerContext = (*SQLiteConn)(nil) + _ driver.Pinger = (*SQLiteConn)(nil) +) + +// Ping implement Pinger. +func (c *SQLiteConn) Ping(ctx context.Context) error { + if c.db == nil { + return errors.New("Connection was closed") + } + return nil +} + +// QueryContext implement QueryerContext. +func (c *SQLiteConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { + list := make([]namedValue, len(args)) + for i, nv := range args { + list[i] = namedValue(nv) + } + return c.query(ctx, query, list) +} + +// ExecContext implement ExecerContext. +func (c *SQLiteConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { + list := make([]namedValue, len(args)) + for i, nv := range args { + list[i] = namedValue(nv) + } + return c.exec(ctx, query, list) +} + +// PrepareContext implement ConnPrepareContext. +func (c *SQLiteConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { + return c.prepare(ctx, query) +} + +// BeginTx implement ConnBeginTx. +func (c *SQLiteConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { + return c.begin(ctx) +} diff --git a/_driver/connection_go18_test.go b/driver/connection_go18_test.go similarity index 100% rename from _driver/connection_go18_test.go rename to driver/connection_go18_test.go diff --git a/driver/connector.go b/driver/connector.go index ab22ad6..11386a2 100644 --- a/driver/connector.go +++ b/driver/connector.go @@ -3,6 +3,7 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. +// +build cgo // +build go1.10 package sqlite3 @@ -12,6 +13,10 @@ import ( "database/sql/driver" ) +var ( + _ driver.Connector = (*Connector)(nil) +) + // Connector is a driver in a fixed configuration. type Connector struct { } diff --git a/driver/const.go b/driver/const.go new file mode 100644 index 0000000..ee32916 --- /dev/null +++ b/driver/const.go @@ -0,0 +1,53 @@ +// Copyright (C) 2018 The Go-SQLite3 Authors. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build cgo + +package sqlite3 + +/* +#ifndef USE_LIBSQLITE3 +#include +#else +#include +#endif +*/ +import "C" + +const ( + SQLiteDelete = C.SQLITE_DELETE + SQLiteInsert = C.SQLITE_INSERT + SQLiteUpdate = C.SQLITE_UPDATE +) + +// Backward compliant +const ( + SQLITE_DELETE = SQLiteDelete + SQLITE_INSERT = SQLiteInsert + SQLITE_UPDATE = SQLiteUpdate +) + +const ( + columnDate string = "date" + columnDatetime string = "datetime" + columnTimestamp string = "timestamp" +) + +// Run-Time Limit Categories. +// See: http://www.sqlite.org/c3ref/c_limit_attached.html +const ( + SQLITE_LIMIT_LENGTH = C.SQLITE_LIMIT_LENGTH + SQLITE_LIMIT_SQL_LENGTH = C.SQLITE_LIMIT_SQL_LENGTH + SQLITE_LIMIT_COLUMN = C.SQLITE_LIMIT_COLUMN + SQLITE_LIMIT_EXPR_DEPTH = C.SQLITE_LIMIT_EXPR_DEPTH + SQLITE_LIMIT_COMPOUND_SELECT = C.SQLITE_LIMIT_COMPOUND_SELECT + SQLITE_LIMIT_VDBE_OP = C.SQLITE_LIMIT_VDBE_OP + SQLITE_LIMIT_FUNCTION_ARG = C.SQLITE_LIMIT_FUNCTION_ARG + SQLITE_LIMIT_ATTACHED = C.SQLITE_LIMIT_ATTACHED + SQLITE_LIMIT_LIKE_PATTERN_LENGTH = C.SQLITE_LIMIT_LIKE_PATTERN_LENGTH + SQLITE_LIMIT_VARIABLE_NUMBER = C.SQLITE_LIMIT_VARIABLE_NUMBER + SQLITE_LIMIT_TRIGGER_DEPTH = C.SQLITE_LIMIT_TRIGGER_DEPTH + SQLITE_LIMIT_WORKER_THREADS = C.SQLITE_LIMIT_WORKER_THREADS +) diff --git a/_driver/sqlite3_context.go b/driver/context.go similarity index 97% rename from _driver/sqlite3_context.go rename to driver/context.go index b81dd59..74ec6c3 100644 --- a/_driver/sqlite3_context.go +++ b/driver/context.go @@ -1,8 +1,10 @@ -// Copyright (C) 2014 Yasuhiro Matsumoto . +// Copyright (C) 2018 The Go-SQLite3 Authors. // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. +// +build cgo + package sqlite3 /* @@ -11,6 +13,7 @@ package sqlite3 #else #include #endif + #include // These wrappers are necessary because SQLITE_TRANSIENT diff --git a/driver/driver.go b/driver/driver.go index 03147b8..70f068f 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -3,8 +3,71 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. +// +build cgo + package sqlite3 -// SQLiteDriver is exported to make the driver directly accessible. -// In general the driver is used via the database/sql package. -type SQLiteDriver struct{} +/* +#cgo CFLAGS: -std=gnu99 +#cgo CFLAGS: -DSQLITE_ENABLE_RTREE +#cgo CFLAGS: -DSQLITE_THREADSAFE=1 +#cgo CFLAGS: -DHAVE_USLEEP=1 +#cgo CFLAGS: -DSQLITE_ENABLE_FTS3 +#cgo CFLAGS: -DSQLITE_ENABLE_FTS3_PARENTHESIS +#cgo CFLAGS: -DSQLITE_ENABLE_FTS4_UNICODE61 +#cgo CFLAGS: -DSQLITE_TRACE_SIZE_LIMIT=15 +#cgo CFLAGS: -DSQLITE_OMIT_DEPRECATED +#cgo CFLAGS: -DSQLITE_DISABLE_INTRINSIC +#cgo CFLAGS: -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 +#cgo CFLAGS: -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT +#cgo CFLAGS: -Wno-deprecated-declarations +#cgo linux,!android CFLAGS: -DHAVE_PREAD64=1 -DHAVE_PWRITE64=1 + +#ifndef USE_LIBSQLITE3 +#include +#else +#include +#endif +#include +#include + +#ifdef __CYGWIN__ +# include +#endif + +#ifndef SQLITE_OPEN_READWRITE +# define SQLITE_OPEN_READWRITE 0 +#endif + +#ifndef SQLITE_OPEN_FULLMUTEX +# define SQLITE_OPEN_FULLMUTEX 0 +#endif + +#ifndef SQLITE_DETERMINISTIC +# define SQLITE_DETERMINISTIC 0 +#endif +*/ +import "C" +import ( + "database/sql" + "database/sql/driver" +) + +var ( + _ driver.Driver = (*SQLiteDriver)(nil) +) + +func init() { + sql.Register("sqlite3", &SQLiteDriver{}) +} + +// SQLiteDriver implement sql.Driver. +type SQLiteDriver struct { + Extensions []string + ConnectHook func(*SQLiteConn) error +} + +// Open database and return a new connection. +func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { + return nil, nil +} diff --git a/driver/driver_go110.go b/driver/driver_go110.go index 289a35c..834895f 100644 --- a/driver/driver_go110.go +++ b/driver/driver_go110.go @@ -3,6 +3,22 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. +// +build cgo // +build go1.10 package sqlite3 + +import "database/sql/driver" + +var ( + _ driver.DriverContext = (*SQLiteDriver)(nil) +) + +// OpenConnector will call OpenConnector to obtain a Connector and then invoke +// that Connector's Conn method to obtain each needed connection, +// instead of invoking the Driver's Open method for each connection. +// The two-step sequence allows drivers to parse the name just once and also provides +// access to per-Conn contexts. +func (d *SQLiteDriver) OpenConnector(name string) (driver.Connector, error) { + return nil, nil +} diff --git a/_driver/error.go b/driver/error.go similarity index 88% rename from _driver/error.go rename to driver/error.go index 49ab890..22885fd 100644 --- a/_driver/error.go +++ b/driver/error.go @@ -1,11 +1,29 @@ -// Copyright (C) 2014 Yasuhiro Matsumoto . +// Copyright (C) 2018 The Go-SQLite3 Authors. // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. +// +build cgo + package sqlite3 +/* +#ifndef USE_LIBSQLITE3 +#include +#else +#include +#endif +#include +#include + +#ifdef __CYGWIN__ +# include +#endif +*/ import "C" +import ( + "fmt" +) // ErrNo inherit errno. type ErrNo int @@ -133,3 +151,24 @@ var ( ErrNoticeRecoverRollback = ErrNotice.Extend(2) ErrWarningAutoIndex = ErrWarning.Extend(1) ) + +func lastError(db *C.sqlite3) error { + rv := C.sqlite3_errcode(db) + if rv == C.SQLITE_OK { + return nil + } + return Error{ + Code: ErrNo(rv), + ExtendedCode: ErrNoExtended(C.sqlite3_extended_errcode(db)), + err: C.GoString(C.sqlite3_errmsg(db)), + } +} + +func errorString(err Error) string { + fmt.Println("errorString") + return C.GoString(C.sqlite3_errstr(C.int(err.Code))) +} + +func (c *SQLiteConn) lastError() error { + return lastError(c.db) +} diff --git a/_driver/error_test.go b/driver/error_test.go similarity index 100% rename from _driver/error_test.go rename to driver/error_test.go diff --git a/driver/func.go b/driver/func.go new file mode 100644 index 0000000..63056b8 --- /dev/null +++ b/driver/func.go @@ -0,0 +1,274 @@ +// Copyright (C) 2018 The Go-SQLite3 Authors. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build cgo + +package sqlite3 + +/* +#ifndef USE_LIBSQLITE3 +#include +#else +#include +#endif + +#include +#include +#include + +void callbackTrampoline(sqlite3_context*, int, sqlite3_value**); +void stepTrampoline(sqlite3_context*, int, sqlite3_value**); +void doneTrampoline(sqlite3_context*); + +int compareTrampoline(void*, int, char*, int, char*); + +int _sqlite3_create_function( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int eTextRep, + uintptr_t pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +) { + return sqlite3_create_function(db, zFunctionName, nArg, eTextRep, (void*) pApp, xFunc, xStep, xFinal); +} +*/ +import "C" +import ( + "errors" + "reflect" + "unsafe" +) + +func sqlite3CreateFunction(db *C.sqlite3, zFunctionName *C.char, nArg C.int, eTextRep C.int, pApp uintptr, xFunc unsafe.Pointer, xStep unsafe.Pointer, xFinal unsafe.Pointer) C.int { + return C._sqlite3_create_function(db, zFunctionName, nArg, eTextRep, C.uintptr_t(pApp), (*[0]byte)(xFunc), (*[0]byte)(xStep), (*[0]byte)(xFinal)) +} + +// RegisterCollation makes a Go function available as a collation. +// +// cmp receives two UTF-8 strings, a and b. The result should be 0 if +// a==b, -1 if a < b, and +1 if a > b. +// +// cmp must always return the same result given the same +// inputs. Additionally, it must have the following properties for all +// strings A, B and C: if A==B then B==A; if A==B and B==C then A==C; +// if AA; if A +#else +#include +#endif +#include +#include + +int commitHookTrampoline(void*); +void rollbackHookTrampoline(void*); +void updateHookTrampoline(void*, int, char*, char*, sqlite3_int64); +*/ +import "C" +import "unsafe" + +// RegisterCommitHook sets the commit hook for a connection. +// +// If the callback returns non-zero the transaction will become a rollback. +// +// If there is an existing commit hook for this connection, it will be +// removed. If callback is nil the existing hook (if any) will be removed +// without creating a new one. +func (c *SQLiteConn) RegisterCommitHook(callback func() int) { + if callback == nil { + C.sqlite3_commit_hook(c.db, nil, nil) + } else { + C.sqlite3_commit_hook(c.db, (*[0]byte)(C.commitHookTrampoline), unsafe.Pointer(newHandle(c, callback))) + } +} + +// RegisterRollbackHook sets the rollback hook for a connection. +// +// If there is an existing rollback hook for this connection, it will be +// removed. If callback is nil the existing hook (if any) will be removed +// without creating a new one. +func (c *SQLiteConn) RegisterRollbackHook(callback func()) { + if callback == nil { + C.sqlite3_rollback_hook(c.db, nil, nil) + } else { + C.sqlite3_rollback_hook(c.db, (*[0]byte)(C.rollbackHookTrampoline), unsafe.Pointer(newHandle(c, callback))) + } +} + +// RegisterUpdateHook sets the update hook for a connection. +// +// The parameters to the callback are the operation (one of the constants +// SQLITE_INSERT, SQLITE_DELETE, or SQLITE_UPDATE), the database name, the +// table name, and the rowid. +// +// If there is an existing update hook for this connection, it will be +// removed. If callback is nil the existing hook (if any) will be removed +// without creating a new one. +func (c *SQLiteConn) RegisterUpdateHook(callback func(int, string, string, int64)) { + if callback == nil { + C.sqlite3_update_hook(c.db, nil, nil) + } else { + C.sqlite3_update_hook(c.db, (*[0]byte)(C.updateHookTrampoline), unsafe.Pointer(newHandle(c, callback))) + } +} diff --git a/driver/limit.go b/driver/limit.go new file mode 100644 index 0000000..db5bc9c --- /dev/null +++ b/driver/limit.go @@ -0,0 +1,58 @@ +// Copyright (C) 2018 The Go-SQLite3 Authors. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build cgo + +package sqlite3 + +/* +#ifndef USE_LIBSQLITE3 +#include +#else +#include +#endif +#include +#include + +#ifdef SQLITE_LIMIT_WORKER_THREADS +# define _SQLITE_HAS_LIMIT +# define SQLITE_LIMIT_LENGTH 0 +# define SQLITE_LIMIT_SQL_LENGTH 1 +# define SQLITE_LIMIT_COLUMN 2 +# define SQLITE_LIMIT_EXPR_DEPTH 3 +# define SQLITE_LIMIT_COMPOUND_SELECT 4 +# define SQLITE_LIMIT_VDBE_OP 5 +# define SQLITE_LIMIT_FUNCTION_ARG 6 +# define SQLITE_LIMIT_ATTACHED 7 +# define SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8 +# define SQLITE_LIMIT_VARIABLE_NUMBER 9 +# define SQLITE_LIMIT_TRIGGER_DEPTH 10 +# define SQLITE_LIMIT_WORKER_THREADS 11 +# else +# define SQLITE_LIMIT_WORKER_THREADS 11 +#endif + +static int _sqlite3_limit(sqlite3* db, int limitId, int newLimit) { +#ifndef _SQLITE_HAS_LIMIT + return -1; +#else + return sqlite3_limit(db, limitId, newLimit); +#endif +} +*/ +import "C" + +// GetLimit returns the current value of a run-time limit. +// See: sqlite3_limit, http://www.sqlite.org/c3ref/limit.html +func (c *SQLiteConn) GetLimit(id int) int { + return int(C._sqlite3_limit(c.db, C.int(id), -1)) +} + +// SetLimit changes the value of a run-time limits. +// Then this method returns the prior value of the limit. +// See: sqlite3_limit, http://www.sqlite.org/c3ref/limit.html +func (c *SQLiteConn) SetLimit(id int, newVal int) int { + return int(C._sqlite3_limit(c.db, C.int(id), C.int(newVal))) +} diff --git a/driver/result.go b/driver/result.go new file mode 100644 index 0000000..c675ad7 --- /dev/null +++ b/driver/result.go @@ -0,0 +1,217 @@ +// Copyright (C) 2018 The Go-SQLite3 Authors. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build cgo + +package sqlite3 + +/* +#ifndef USE_LIBSQLITE3 +#include +#else +#include +#endif + +#include + +void _sqlite3_result_text(sqlite3_context* ctx, const char* s) { + sqlite3_result_text(ctx, s, -1, &free); +} + +void _sqlite3_result_blob(sqlite3_context* ctx, const void* b, int l) { + sqlite3_result_blob(ctx, b, l, SQLITE_TRANSIENT); +} +*/ +import "C" +import ( + "database/sql/driver" + "io" + "strings" + "time" + "unsafe" +) + +var ( + _ driver.Result = (*SQLiteResult)(nil) + _ driver.Rows = (*SQLiteRows)(nil) +) + +// SQLiteResult implement sql.Result. +type SQLiteResult struct { + id int64 + changes int64 +} + +// SQLiteRows implement sql.Rows. +type SQLiteRows struct { + s *SQLiteStmt + nc int + cols []string + decltype []string + cls bool + closed bool + done chan struct{} +} + +// LastInsertId teturn last inserted ID. +func (r *SQLiteResult) LastInsertId() (int64, error) { + return r.id, nil +} + +// RowsAffected return how many rows affected. +func (r *SQLiteResult) RowsAffected() (int64, error) { + return r.changes, nil +} + +// Close the rows. +func (rc *SQLiteRows) Close() error { + rc.s.mu.Lock() + if rc.s.closed || rc.closed { + rc.s.mu.Unlock() + return nil + } + rc.closed = true + if rc.done != nil { + close(rc.done) + } + if rc.cls { + rc.s.mu.Unlock() + return rc.s.Close() + } + rv := C.sqlite3_reset(rc.s.s) + if rv != C.SQLITE_OK { + rc.s.mu.Unlock() + return rc.s.c.lastError() + } + rc.s.mu.Unlock() + return nil +} + +// Columns return column names. +func (rc *SQLiteRows) Columns() []string { + rc.s.mu.Lock() + defer rc.s.mu.Unlock() + if rc.s.s != nil && rc.nc != len(rc.cols) { + rc.cols = make([]string, rc.nc) + for i := 0; i < rc.nc; i++ { + rc.cols[i] = C.GoString(C.sqlite3_column_name(rc.s.s, C.int(i))) + } + } + return rc.cols +} + +func (rc *SQLiteRows) declTypes() []string { + if rc.s.s != nil && rc.decltype == nil { + rc.decltype = make([]string, rc.nc) + for i := 0; i < rc.nc; i++ { + rc.decltype[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(rc.s.s, C.int(i)))) + } + } + return rc.decltype +} + +// DeclTypes return column types. +func (rc *SQLiteRows) DeclTypes() []string { + rc.s.mu.Lock() + defer rc.s.mu.Unlock() + return rc.declTypes() +} + +// Next move cursor to next. +func (rc *SQLiteRows) Next(dest []driver.Value) error { + if rc.s.closed { + return io.EOF + } + rc.s.mu.Lock() + defer rc.s.mu.Unlock() + rv := C.sqlite3_step(rc.s.s) + if rv == C.SQLITE_DONE { + return io.EOF + } + if rv != C.SQLITE_ROW { + rv = C.sqlite3_reset(rc.s.s) + if rv != C.SQLITE_OK { + return rc.s.c.lastError() + } + return nil + } + + rc.declTypes() + + for i := range dest { + switch C.sqlite3_column_type(rc.s.s, C.int(i)) { + case C.SQLITE_INTEGER: + val := int64(C.sqlite3_column_int64(rc.s.s, C.int(i))) + switch rc.decltype[i] { + case columnTimestamp, columnDatetime, columnDate: + var t time.Time + // Assume a millisecond unix timestamp if it's 13 digits -- too + // large to be a reasonable timestamp in seconds. + if val > 1e12 || val < -1e12 { + val *= int64(time.Millisecond) // convert ms to nsec + t = time.Unix(0, val) + } else { + t = time.Unix(val, 0) + } + t = t.UTC() + if rc.s.c.loc != nil { + t = t.In(rc.s.c.loc) + } + dest[i] = t + case "boolean": + dest[i] = val > 0 + default: + dest[i] = val + } + case C.SQLITE_FLOAT: + dest[i] = float64(C.sqlite3_column_double(rc.s.s, C.int(i))) + case C.SQLITE_BLOB: + p := C.sqlite3_column_blob(rc.s.s, C.int(i)) + if p == nil { + dest[i] = nil + continue + } + n := int(C.sqlite3_column_bytes(rc.s.s, C.int(i))) + switch dest[i].(type) { + default: + slice := make([]byte, n) + copy(slice[:], (*[1 << 30]byte)(p)[0:n]) + dest[i] = slice + } + case C.SQLITE_NULL: + dest[i] = nil + case C.SQLITE_TEXT: + var err error + var timeVal time.Time + + n := int(C.sqlite3_column_bytes(rc.s.s, C.int(i))) + s := C.GoStringN((*C.char)(unsafe.Pointer(C.sqlite3_column_text(rc.s.s, C.int(i)))), C.int(n)) + + switch rc.decltype[i] { + case columnTimestamp, columnDatetime, columnDate: + var t time.Time + s = strings.TrimSuffix(s, "Z") + for _, format := range SQLiteTimestampFormats { + if timeVal, err = time.ParseInLocation(format, s, time.UTC); err == nil { + t = timeVal + break + } + } + if err != nil { + // The column is a time value, so return the zero time on parse failure. + t = time.Time{} + } + if rc.s.c.loc != nil { + t = t.In(rc.s.c.loc) + } + dest[i] = t + default: + dest[i] = []byte(s) + } + + } + } + return nil +} diff --git a/driver/sqlite3.go b/driver/sqlite3.go deleted file mode 100644 index 08d47e4..0000000 --- a/driver/sqlite3.go +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (C) 2018 The Go-SQLite3 Authors. -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -// +build cgo - -package sqlite3 - -/* -#cgo CFLAGS: -DSQLITE_ENABLE_RTREE -#cgo CFLAGS: -DSQLITE_THREADSAFE=1 -#cgo CFLAGS: -DHAVE_USLEEP=1 -#cgo CFLAGS: -DSQLITE_ENABLE_FTS3 -#cgo CFLAGS: -DSQLITE_ENABLE_FTS3_PARENTHESIS -#cgo CFLAGS: -DSQLITE_ENABLE_FTS4_UNICODE61 -#cgo CFLAGS: -DSQLITE_TRACE_SIZE_LIMIT=15 -#cgo CFLAGS: -DSQLITE_OMIT_DEPRECATED -#cgo CFLAGS: -DSQLITE_DISABLE_INTRINSIC -#cgo CFLAGS: -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -#cgo CFLAGS: -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT - -#cgo CFLAGS: -Wno-deprecated-declarations -#cgo CFLAGS: -std=gnu99 - -#cgo linux,!android CFLAGS: -DHAVE_PREAD64=1 -DHAVE_PWRITE64=1 - -#ifndef USE_LIBSQLITE3 -#include -#else -#include -#endif -#include -#include - -#ifdef __CYGWIN__ -# include -#endif - -#ifndef SQLITE_OPEN_READWRITE -# define SQLITE_OPEN_READWRITE 0 -#endif - -#ifndef SQLITE_OPEN_FULLMUTEX -# define SQLITE_OPEN_FULLMUTEX 0 -#endif - -#ifndef SQLITE_DETERMINISTIC -# define SQLITE_DETERMINISTIC 0 -#endif - -static int -_sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs) { -#ifdef SQLITE_OPEN_URI - return sqlite3_open_v2(filename, ppDb, flags | SQLITE_OPEN_URI, zVfs); -#else - return sqlite3_open_v2(filename, ppDb, flags, zVfs); -#endif -} - -static int -_sqlite3_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) { - return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT); -} - -static int -_sqlite3_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) { - return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT); -} - -#include -#include - -static int -_sqlite3_exec(sqlite3* db, const char* pcmd, long long* rowid, long long* changes) -{ - int rv = sqlite3_exec(db, pcmd, 0, 0, 0); - *rowid = (long long) sqlite3_last_insert_rowid(db); - *changes = (long long) sqlite3_changes(db); - return rv; -} - -static int -_sqlite3_step(sqlite3_stmt* stmt, long long* rowid, long long* changes) -{ - int rv = sqlite3_step(stmt); - sqlite3* db = sqlite3_db_handle(stmt); - *rowid = (long long) sqlite3_last_insert_rowid(db); - *changes = (long long) sqlite3_changes(db); - return rv; -} - -void _sqlite3_result_text(sqlite3_context* ctx, const char* s) { - sqlite3_result_text(ctx, s, -1, &free); -} - -void _sqlite3_result_blob(sqlite3_context* ctx, const void* b, int l) { - sqlite3_result_blob(ctx, b, l, SQLITE_TRANSIENT); -} - - -int _sqlite3_create_function( - sqlite3 *db, - const char *zFunctionName, - int nArg, - int eTextRep, - uintptr_t pApp, - void (*xFunc)(sqlite3_context*,int,sqlite3_value**), - void (*xStep)(sqlite3_context*,int,sqlite3_value**), - void (*xFinal)(sqlite3_context*) -) { - return sqlite3_create_function(db, zFunctionName, nArg, eTextRep, (void*) pApp, xFunc, xStep, xFinal); -} - -void callbackTrampoline(sqlite3_context*, int, sqlite3_value**); -void stepTrampoline(sqlite3_context*, int, sqlite3_value**); -void doneTrampoline(sqlite3_context*); - -int compareTrampoline(void*, int, char*, int, char*); -int commitHookTrampoline(void*); -void rollbackHookTrampoline(void*); -void updateHookTrampoline(void*, int, char*, char*, sqlite3_int64); - -#ifdef SQLITE_LIMIT_WORKER_THREADS -# define _SQLITE_HAS_LIMIT -# define SQLITE_LIMIT_LENGTH 0 -# define SQLITE_LIMIT_SQL_LENGTH 1 -# define SQLITE_LIMIT_COLUMN 2 -# define SQLITE_LIMIT_EXPR_DEPTH 3 -# define SQLITE_LIMIT_COMPOUND_SELECT 4 -# define SQLITE_LIMIT_VDBE_OP 5 -# define SQLITE_LIMIT_FUNCTION_ARG 6 -# define SQLITE_LIMIT_ATTACHED 7 -# define SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8 -# define SQLITE_LIMIT_VARIABLE_NUMBER 9 -# define SQLITE_LIMIT_TRIGGER_DEPTH 10 -# define SQLITE_LIMIT_WORKER_THREADS 11 -# else -# define SQLITE_LIMIT_WORKER_THREADS 11 -#endif - -static int _sqlite3_limit(sqlite3* db, int limitId, int newLimit) { -#ifndef _SQLITE_HAS_LIMIT - return -1; -#else - return sqlite3_limit(db, limitId, newLimit); -#endif -} -*/ -import "C" diff --git a/driver/statement.go b/driver/statement.go new file mode 100644 index 0000000..6518dd5 --- /dev/null +++ b/driver/statement.go @@ -0,0 +1,243 @@ +// Copyright (C) 2018 The Go-SQLite3 Authors. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build cgo + +package sqlite3 + +/* +#ifndef USE_LIBSQLITE3 +#include +#else +#include +#endif + +#include + +static int +_sqlite3_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) { + return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT); +} + +static int +_sqlite3_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) { + return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT); +} + +static int +_sqlite3_step(sqlite3_stmt* stmt, long long* rowid, long long* changes) +{ + int rv = sqlite3_step(stmt); + sqlite3* db = sqlite3_db_handle(stmt); + *rowid = (long long) sqlite3_last_insert_rowid(db); + *changes = (long long) sqlite3_changes(db); + return rv; +} +*/ +import "C" +import ( + "context" + "database/sql/driver" + "errors" + "runtime" + "sync" + "time" + "unsafe" +) + +var ( + _ driver.Stmt = (*SQLiteStmt)(nil) +) + +// SQLiteStmt implement sql.Stmt. +type SQLiteStmt struct { + mu sync.Mutex + c *SQLiteConn + s *C.sqlite3_stmt + t string + closed bool + cls bool +} + +// Close the statement. +func (s *SQLiteStmt) Close() error { + s.mu.Lock() + defer s.mu.Unlock() + if s.closed { + return nil + } + s.closed = true + if !s.c.dbConnOpen() { + return errors.New("sqlite statement with already closed database connection") + } + rv := C.sqlite3_finalize(s.s) + s.s = nil + if rv != C.SQLITE_OK { + return s.c.lastError() + } + runtime.SetFinalizer(s, nil) + return nil +} + +// NumInput return a number of parameters. +func (s *SQLiteStmt) NumInput() int { + return int(C.sqlite3_bind_parameter_count(s.s)) +} + +type bindArg struct { + n int + v driver.Value +} + +var placeHolder = []byte{0} + +func (s *SQLiteStmt) bind(args []namedValue) error { + rv := C.sqlite3_reset(s.s) + if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE { + return s.c.lastError() + } + + for i, v := range args { + if v.Name != "" { + cname := C.CString(":" + v.Name) + args[i].Ordinal = int(C.sqlite3_bind_parameter_index(s.s, cname)) + C.free(unsafe.Pointer(cname)) + } + } + + for _, arg := range args { + n := C.int(arg.Ordinal) + switch v := arg.Value.(type) { + case nil: + rv = C.sqlite3_bind_null(s.s, n) + case string: + if len(v) == 0 { + rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&placeHolder[0])), C.int(0)) + } else { + b := []byte(v) + rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b))) + } + case int64: + rv = C.sqlite3_bind_int64(s.s, n, C.sqlite3_int64(v)) + case bool: + if v { + rv = C.sqlite3_bind_int(s.s, n, 1) + } else { + rv = C.sqlite3_bind_int(s.s, n, 0) + } + case float64: + rv = C.sqlite3_bind_double(s.s, n, C.double(v)) + case []byte: + if v == nil { + rv = C.sqlite3_bind_null(s.s, n) + } else { + ln := len(v) + if ln == 0 { + v = placeHolder + } + rv = C._sqlite3_bind_blob(s.s, n, unsafe.Pointer(&v[0]), C.int(ln)) + } + case time.Time: + b := []byte(v.Format(SQLiteTimestampFormats[0])) + rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b))) + } + if rv != C.SQLITE_OK { + return s.c.lastError() + } + } + return nil +} + +// Query the statement with arguments. Return records. +func (s *SQLiteStmt) Query(args []driver.Value) (driver.Rows, error) { + list := make([]namedValue, len(args)) + for i, v := range args { + list[i] = namedValue{ + Ordinal: i + 1, + Value: v, + } + } + return s.query(context.Background(), list) +} + +func (s *SQLiteStmt) query(ctx context.Context, args []namedValue) (driver.Rows, error) { + if err := s.bind(args); err != nil { + return nil, err + } + + rows := &SQLiteRows{ + s: s, + nc: int(C.sqlite3_column_count(s.s)), + cols: nil, + decltype: nil, + cls: s.cls, + closed: false, + done: make(chan struct{}), + } + + if ctxdone := ctx.Done(); ctxdone != nil { + go func(db *C.sqlite3) { + select { + case <-ctxdone: + select { + case <-rows.done: + default: + C.sqlite3_interrupt(db) + rows.Close() + } + case <-rows.done: + } + }(s.c.db) + } + + return rows, nil +} + +// Exec execute the statement with arguments. Return result object. +func (s *SQLiteStmt) Exec(args []driver.Value) (driver.Result, error) { + list := make([]namedValue, len(args)) + for i, v := range args { + list[i] = namedValue{ + Ordinal: i + 1, + Value: v, + } + } + return s.exec(context.Background(), list) +} + +func (s *SQLiteStmt) exec(ctx context.Context, args []namedValue) (driver.Result, error) { + if err := s.bind(args); err != nil { + C.sqlite3_reset(s.s) + C.sqlite3_clear_bindings(s.s) + return nil, err + } + + if ctxdone := ctx.Done(); ctxdone != nil { + done := make(chan struct{}) + defer close(done) + go func(db *C.sqlite3) { + select { + case <-done: + case <-ctxdone: + select { + case <-done: + default: + C.sqlite3_interrupt(db) + } + } + }(s.c.db) + } + + var rowid, changes C.longlong + rv := C._sqlite3_step(s.s, &rowid, &changes) + if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE { + err := s.c.lastError() + C.sqlite3_reset(s.s) + C.sqlite3_clear_bindings(s.s) + return nil, err + } + + return &SQLiteResult{id: int64(rowid), changes: int64(changes)}, nil +} diff --git a/driver/statement_go18.go b/driver/statement_go18.go new file mode 100644 index 0000000..35b0119 --- /dev/null +++ b/driver/statement_go18.go @@ -0,0 +1,37 @@ +// Copyright (C) 2018 The Go-SQLite3 Authors. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build cgo +// +build go1.8 + +package sqlite3 + +import ( + "context" + "database/sql/driver" +) + +var ( + _ driver.StmtExecContext = (*SQLiteStmt)(nil) + _ driver.StmtQueryContext = (*SQLiteStmt)(nil) +) + +// QueryContext implement QueryerContext. +func (s *SQLiteStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { + list := make([]namedValue, len(args)) + for i, nv := range args { + list[i] = namedValue(nv) + } + return s.query(ctx, list) +} + +// ExecContext implement ExecerContext. +func (s *SQLiteStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { + list := make([]namedValue, len(args)) + for i, nv := range args { + list[i] = namedValue(nv) + } + return s.exec(ctx, list) +} diff --git a/driver/transaction.go b/driver/transaction.go new file mode 100644 index 0000000..ff7bf59 --- /dev/null +++ b/driver/transaction.go @@ -0,0 +1,60 @@ +// Copyright (C) 2018 The Go-SQLite3 Authors. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build cgo + +package sqlite3 + +/* +#ifndef USE_LIBSQLITE3 +#include +#else +#include +#endif +*/ +import "C" +import ( + "context" + "database/sql/driver" +) + +var ( + _ driver.Tx = (*SQLiteTx)(nil) +) + +// SQLiteTx implemen sql.Tx. +type SQLiteTx struct { + c *SQLiteConn +} + +// Begin transaction. +func (c *SQLiteConn) Begin() (driver.Tx, error) { + return c.begin(context.Background()) +} + +func (c *SQLiteConn) begin(ctx context.Context) (driver.Tx, error) { + if _, err := c.exec(ctx, c.txlock, nil); err != nil { + return nil, err + } + return &SQLiteTx{c}, nil +} + +// Commit transaction. +func (tx *SQLiteTx) Commit() error { + _, err := tx.c.exec(context.Background(), "COMMIT", nil) + if err != nil && err.(Error).Code == C.SQLITE_BUSY { + // sqlite3 will leave the transaction open in this scenario. + // However, database/sql considers the transaction complete once we + // return from Commit() - we must clean up to honour its semantics. + tx.c.exec(context.Background(), "ROLLBACK", nil) + } + return err +} + +// Rollback transaction. +func (tx *SQLiteTx) Rollback() error { + _, err := tx.c.exec(context.Background(), "ROLLBACK", nil) + return err +} diff --git a/driver/util.go b/driver/util.go new file mode 100644 index 0000000..3b1bc29 --- /dev/null +++ b/driver/util.go @@ -0,0 +1,45 @@ +// Copyright (C) 2018 The Go-SQLite3 Authors. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build cgo + +package sqlite3 + +/* +#ifndef USE_LIBSQLITE3 +#include +#else +#include +#endif +#include +#include +*/ +import "C" + +// SQLiteTimestampFormats is timestamp formats understood by both this module +// and SQLite. The first format in the slice will be used when saving time +// values into the database. When parsing a string from a timestamp or datetime +// column, the formats are tried in order. +var SQLiteTimestampFormats = []string{ + // By default, store timestamps with whatever timezone they come with. + // When parsed, they will be returned with the same timezone. + "2006-01-02 15:04:05.999999999-07:00", + "2006-01-02T15:04:05.999999999-07:00", + "2006-01-02 15:04:05.999999999", + "2006-01-02T15:04:05.999999999", + "2006-01-02 15:04:05", + "2006-01-02T15:04:05", + "2006-01-02 15:04", + "2006-01-02T15:04", + "2006-01-02", +} + +// Version returns SQLite library version information. +func Version() (libVersion string, libVersionNumber int, sourceID string) { + libVersion = C.GoString(C.sqlite3_libversion()) + libVersionNumber = int(C.sqlite3_libversion_number()) + sourceID = C.GoString(C.sqlite3_sourceid()) + return libVersion, libVersionNumber, sourceID +}