mirror of https://github.com/mattn/go-sqlite3.git
Add IsNullCallbackArg
This commit is contained in:
parent
f08f1b6b9c
commit
274eefa1e3
14
callback.go
14
callback.go
|
@ -360,11 +360,11 @@ func callbackRetGeneric(ctx *C.sqlite3_context, v reflect.Value) error {
|
|||
}
|
||||
|
||||
cb, err := callbackRet(v.Elem().Type())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cb(ctx, v.Elem())
|
||||
return cb(ctx, v.Elem())
|
||||
}
|
||||
|
||||
func callbackRet(typ reflect.Type) (callbackRetConverter, error) {
|
||||
|
@ -409,3 +409,9 @@ func callbackSyntheticForTests(v reflect.Value, err error) callbackArgConverter
|
|||
return v, err
|
||||
}
|
||||
}
|
||||
|
||||
// NULL is passed into custom functions as a nil byte slice.
|
||||
func IsNullCallbackArg(arg interface{}) bool {
|
||||
val, ok := arg.([]byte)
|
||||
return ok && val == nil
|
||||
}
|
||||
|
|
149
sqlite3.go
149
sqlite3.go
|
@ -965,103 +965,104 @@ func (c *SQLiteConn) begin(ctx context.Context) (driver.Tx, error) {
|
|||
// The argument is may be either in parentheses or it may be separated from
|
||||
// the pragma name by an equal sign. The two syntaxes yield identical results.
|
||||
// In many pragmas, the argument is a boolean. The boolean can be one of:
|
||||
// 1 yes true on
|
||||
// 0 no false off
|
||||
//
|
||||
// 1 yes true on
|
||||
// 0 no false off
|
||||
//
|
||||
// You can specify a DSN string using a URI as the filename.
|
||||
// test.db
|
||||
// file:test.db?cache=shared&mode=memory
|
||||
// :memory:
|
||||
// file::memory:
|
||||
//
|
||||
// mode
|
||||
// Access mode of the database.
|
||||
// https://www.sqlite.org/c3ref/open.html
|
||||
// Values:
|
||||
// - ro
|
||||
// - rw
|
||||
// - rwc
|
||||
// - memory
|
||||
// test.db
|
||||
// file:test.db?cache=shared&mode=memory
|
||||
// :memory:
|
||||
// file::memory:
|
||||
//
|
||||
// cache
|
||||
// SQLite Shared-Cache Mode
|
||||
// https://www.sqlite.org/sharedcache.html
|
||||
// Values:
|
||||
// - shared
|
||||
// - private
|
||||
// mode
|
||||
// Access mode of the database.
|
||||
// https://www.sqlite.org/c3ref/open.html
|
||||
// Values:
|
||||
// - ro
|
||||
// - rw
|
||||
// - rwc
|
||||
// - memory
|
||||
//
|
||||
// immutable=Boolean
|
||||
// The immutable parameter is a boolean query parameter that indicates
|
||||
// that the database file is stored on read-only media. When immutable is set,
|
||||
// SQLite assumes that the database file cannot be changed,
|
||||
// even by a process with higher privilege,
|
||||
// and so the database is opened read-only and all locking and change detection is disabled.
|
||||
// Caution: Setting the immutable property on a database file that
|
||||
// does in fact change can result in incorrect query results and/or SQLITE_CORRUPT errors.
|
||||
// cache
|
||||
// SQLite Shared-Cache Mode
|
||||
// https://www.sqlite.org/sharedcache.html
|
||||
// Values:
|
||||
// - shared
|
||||
// - private
|
||||
//
|
||||
// immutable=Boolean
|
||||
// The immutable parameter is a boolean query parameter that indicates
|
||||
// that the database file is stored on read-only media. When immutable is set,
|
||||
// SQLite assumes that the database file cannot be changed,
|
||||
// even by a process with higher privilege,
|
||||
// and so the database is opened read-only and all locking and change detection is disabled.
|
||||
// Caution: Setting the immutable property on a database file that
|
||||
// does in fact change can result in incorrect query results and/or SQLITE_CORRUPT errors.
|
||||
//
|
||||
// go-sqlite3 adds the following query parameters to those used by SQLite:
|
||||
// _loc=XXX
|
||||
// Specify location of time format. It's possible to specify "auto".
|
||||
//
|
||||
// _mutex=XXX
|
||||
// Specify mutex mode. XXX can be "no", "full".
|
||||
// _loc=XXX
|
||||
// Specify location of time format. It's possible to specify "auto".
|
||||
//
|
||||
// _txlock=XXX
|
||||
// Specify locking behavior for transactions. XXX can be "immediate",
|
||||
// "deferred", "exclusive".
|
||||
// _mutex=XXX
|
||||
// Specify mutex mode. XXX can be "no", "full".
|
||||
//
|
||||
// _auto_vacuum=X | _vacuum=X
|
||||
// 0 | none - Auto Vacuum disabled
|
||||
// 1 | full - Auto Vacuum FULL
|
||||
// 2 | incremental - Auto Vacuum Incremental
|
||||
// _txlock=XXX
|
||||
// Specify locking behavior for transactions. XXX can be "immediate",
|
||||
// "deferred", "exclusive".
|
||||
//
|
||||
// _busy_timeout=XXX"| _timeout=XXX
|
||||
// Specify value for sqlite3_busy_timeout.
|
||||
// _auto_vacuum=X | _vacuum=X
|
||||
// 0 | none - Auto Vacuum disabled
|
||||
// 1 | full - Auto Vacuum FULL
|
||||
// 2 | incremental - Auto Vacuum Incremental
|
||||
//
|
||||
// _case_sensitive_like=Boolean | _cslike=Boolean
|
||||
// https://www.sqlite.org/pragma.html#pragma_case_sensitive_like
|
||||
// Default or disabled the LIKE operation is case-insensitive.
|
||||
// When enabling this options behaviour of LIKE will become case-sensitive.
|
||||
// _busy_timeout=XXX"| _timeout=XXX
|
||||
// Specify value for sqlite3_busy_timeout.
|
||||
//
|
||||
// _defer_foreign_keys=Boolean | _defer_fk=Boolean
|
||||
// Defer Foreign Keys until outermost transaction is committed.
|
||||
// _case_sensitive_like=Boolean | _cslike=Boolean
|
||||
// https://www.sqlite.org/pragma.html#pragma_case_sensitive_like
|
||||
// Default or disabled the LIKE operation is case-insensitive.
|
||||
// When enabling this options behaviour of LIKE will become case-sensitive.
|
||||
//
|
||||
// _foreign_keys=Boolean | _fk=Boolean
|
||||
// Enable or disable enforcement of foreign keys.
|
||||
// _defer_foreign_keys=Boolean | _defer_fk=Boolean
|
||||
// Defer Foreign Keys until outermost transaction is committed.
|
||||
//
|
||||
// _ignore_check_constraints=Boolean
|
||||
// This pragma enables or disables the enforcement of CHECK constraints.
|
||||
// The default setting is off, meaning that CHECK constraints are enforced by default.
|
||||
// _foreign_keys=Boolean | _fk=Boolean
|
||||
// Enable or disable enforcement of foreign keys.
|
||||
//
|
||||
// _journal_mode=MODE | _journal=MODE
|
||||
// Set journal mode for the databases associated with the current connection.
|
||||
// https://www.sqlite.org/pragma.html#pragma_journal_mode
|
||||
// _ignore_check_constraints=Boolean
|
||||
// This pragma enables or disables the enforcement of CHECK constraints.
|
||||
// The default setting is off, meaning that CHECK constraints are enforced by default.
|
||||
//
|
||||
// _locking_mode=X | _locking=X
|
||||
// Sets the database connection locking-mode.
|
||||
// The locking-mode is either NORMAL or EXCLUSIVE.
|
||||
// https://www.sqlite.org/pragma.html#pragma_locking_mode
|
||||
// _journal_mode=MODE | _journal=MODE
|
||||
// Set journal mode for the databases associated with the current connection.
|
||||
// https://www.sqlite.org/pragma.html#pragma_journal_mode
|
||||
//
|
||||
// _query_only=Boolean
|
||||
// The query_only pragma prevents all changes to database files when enabled.
|
||||
// _locking_mode=X | _locking=X
|
||||
// Sets the database connection locking-mode.
|
||||
// The locking-mode is either NORMAL or EXCLUSIVE.
|
||||
// https://www.sqlite.org/pragma.html#pragma_locking_mode
|
||||
//
|
||||
// _recursive_triggers=Boolean | _rt=Boolean
|
||||
// Enable or disable recursive triggers.
|
||||
// _query_only=Boolean
|
||||
// The query_only pragma prevents all changes to database files when enabled.
|
||||
//
|
||||
// _secure_delete=Boolean|FAST
|
||||
// When secure_delete is on, SQLite overwrites deleted content with zeros.
|
||||
// https://www.sqlite.org/pragma.html#pragma_secure_delete
|
||||
// _recursive_triggers=Boolean | _rt=Boolean
|
||||
// Enable or disable recursive triggers.
|
||||
//
|
||||
// _synchronous=X | _sync=X
|
||||
// Change the setting of the "synchronous" flag.
|
||||
// https://www.sqlite.org/pragma.html#pragma_synchronous
|
||||
//
|
||||
// _writable_schema=Boolean
|
||||
// When this pragma is on, the SQLITE_MASTER tables in which database
|
||||
// can be changed using ordinary UPDATE, INSERT, and DELETE statements.
|
||||
// Warning: misuse of this pragma can easily result in a corrupt database file.
|
||||
// _secure_delete=Boolean|FAST
|
||||
// When secure_delete is on, SQLite overwrites deleted content with zeros.
|
||||
// https://www.sqlite.org/pragma.html#pragma_secure_delete
|
||||
//
|
||||
// _synchronous=X | _sync=X
|
||||
// Change the setting of the "synchronous" flag.
|
||||
// https://www.sqlite.org/pragma.html#pragma_synchronous
|
||||
//
|
||||
// _writable_schema=Boolean
|
||||
// When this pragma is on, the SQLITE_MASTER tables in which database
|
||||
// can be changed using ordinary UPDATE, INSERT, and DELETE statements.
|
||||
// Warning: misuse of this pragma can easily result in a corrupt database file.
|
||||
func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
||||
if C.sqlite3_threadsafe() == 0 {
|
||||
return nil, errors.New("sqlite library was not compiled for thread-safe operation")
|
||||
|
|
114
sqlite3_test.go
114
sqlite3_test.go
|
@ -1128,7 +1128,7 @@ func TestQueryer(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Error("Failed to db.Query:", err)
|
||||
}
|
||||
if id != n + 1 {
|
||||
if id != n+1 {
|
||||
t.Error("Failed to db.Query: not matched results")
|
||||
}
|
||||
n = n + 1
|
||||
|
@ -1439,6 +1439,70 @@ func TestFunctionRegistration(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNullCallbackArg(t *testing.T) {
|
||||
sql.Register("sqlite3_NullCallbackArg", &SQLiteDriver{
|
||||
ConnectHook: func(conn *SQLiteConn) error {
|
||||
return conn.RegisterFunc("isNullArg", IsNullCallbackArg, true)
|
||||
},
|
||||
})
|
||||
db, err := sql.Open("sqlite3_NullCallbackArg", ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal("Failed to open database:", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Exec("CREATE TABLE test (id integer not null primary key, col_int int, col_float float, col_blob blob, col_bool bool)")
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create table:", err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("insert into test values (1, NULL, NULL, NULL, NULL), (2, ?, ?, ?, ?)", 1, 1.5, []byte("blob"), false)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to insert records:", err)
|
||||
}
|
||||
_, err = db.Exec("insert into test values (3, NULL, ?, NULL, ?)", 1.5, true)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to insert records:", err)
|
||||
}
|
||||
_, err = db.Exec("insert into test values (4, NULL, NULL, ?, NULL)", []byte{})
|
||||
if err != nil {
|
||||
t.Fatal("Failed to insert records:", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
id int64
|
||||
nullInt bool
|
||||
nullFloat bool
|
||||
nullBlob bool
|
||||
nullBool bool
|
||||
}{
|
||||
{1, true, true, true, true},
|
||||
{2, false, false, false, false},
|
||||
{3, true, false, true, false},
|
||||
{4, true, true, false, true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
var retInt, retFloat, retBlob, retBool bool
|
||||
err = db.QueryRow("select isNullArg(col_int), isNullArg(col_float), isNullArg(col_blob), isNullArg(col_bool) from test where id = $1", test.id).Scan(&retInt, &retFloat, &retBlob, &retBool)
|
||||
if err != nil {
|
||||
t.Fatal("Query failed:", err)
|
||||
}
|
||||
if retInt != test.nullInt {
|
||||
t.Fatalf("isNullArg returned wrong value for col_int, got %v, want %v", retInt, test.nullInt)
|
||||
}
|
||||
if retFloat != test.nullFloat {
|
||||
t.Fatalf("isNullArg returned wrong value for col_float, got %v, want %v", retFloat, test.nullFloat)
|
||||
}
|
||||
if retBlob != test.nullBlob {
|
||||
t.Fatalf("isNullArg returned wrong value for col_blob, got %v, want %v", retBlob, test.nullBlob)
|
||||
}
|
||||
if retBool != test.nullBool {
|
||||
t.Fatalf("isNullArg returned wrong value for col_bool, got %v, want %v", retBool, test.nullBlob)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type sumAggregator int64
|
||||
|
||||
func (s *sumAggregator) Step(x int64) {
|
||||
|
@ -1497,28 +1561,28 @@ func TestAggregatorRegistration(t *testing.T) {
|
|||
}
|
||||
|
||||
type mode struct {
|
||||
counts map[interface{}]int
|
||||
top interface{}
|
||||
topCount int
|
||||
counts map[interface{}]int
|
||||
top interface{}
|
||||
topCount int
|
||||
}
|
||||
|
||||
func newMode() *mode {
|
||||
return &mode{
|
||||
counts: map[interface{}]int{},
|
||||
}
|
||||
return &mode{
|
||||
counts: map[interface{}]int{},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mode) Step(x interface{}) {
|
||||
m.counts[x]++
|
||||
c := m.counts[x]
|
||||
if c > m.topCount {
|
||||
m.top = x
|
||||
m.topCount = c
|
||||
}
|
||||
m.counts[x]++
|
||||
c := m.counts[x]
|
||||
if c > m.topCount {
|
||||
m.top = x
|
||||
m.topCount = c
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mode) Done() interface{} {
|
||||
return m.top
|
||||
return m.top
|
||||
}
|
||||
|
||||
func TestAggregatorRegistration_GenericReturn(t *testing.T) {
|
||||
|
@ -1534,19 +1598,19 @@ func TestAggregatorRegistration_GenericReturn(t *testing.T) {
|
|||
defer db.Close()
|
||||
|
||||
_, err = db.Exec("create table foo (department integer, profits integer)")
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create table:", err)
|
||||
}
|
||||
_, err = db.Exec("insert into foo values (1, 10), (1, 20), (1, 45), (2, 42), (2, 115), (2, 20)")
|
||||
if err != nil {
|
||||
t.Fatal("Failed to insert records:", err)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create table:", err)
|
||||
}
|
||||
_, err = db.Exec("insert into foo values (1, 10), (1, 20), (1, 45), (2, 42), (2, 115), (2, 20)")
|
||||
if err != nil {
|
||||
t.Fatal("Failed to insert records:", err)
|
||||
}
|
||||
|
||||
var mode int
|
||||
err = db.QueryRow("select mode(profits) from foo").Scan(&mode)
|
||||
if err != nil {
|
||||
t.Fatal("MODE query error:", err)
|
||||
}
|
||||
err = db.QueryRow("select mode(profits) from foo").Scan(&mode)
|
||||
if err != nil {
|
||||
t.Fatal("MODE query error:", err)
|
||||
}
|
||||
|
||||
if mode != 20 {
|
||||
t.Fatal("Got incorrect mode. Wanted 20, got: ", mode)
|
||||
|
|
Loading…
Reference in New Issue