Add IsNullCallbackArg

This commit is contained in:
Andrew Zhang 2023-06-07 20:26:58 -05:00
parent f08f1b6b9c
commit 274eefa1e3
3 changed files with 174 additions and 103 deletions

View File

@ -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
}

View File

@ -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")

View File

@ -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)