Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IsNullCallbackArg #1170

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 changes: 75 additions & 74 deletions sqlite3.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
//
// 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".
// 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.
//
// _mutex=XXX
// Specify mutex mode. XXX can be "no", "full".
// go-sqlite3 adds the following query parameters to those used by SQLite:
//
// _txlock=XXX
// Specify locking behavior for transactions. XXX can be "immediate",
// "deferred", "exclusive".
// _loc=XXX
// Specify location of time format. It's possible to specify "auto".
//
// _auto_vacuum=X | _vacuum=X
// 0 | none - Auto Vacuum disabled
// 1 | full - Auto Vacuum FULL
// 2 | incremental - Auto Vacuum Incremental
// _mutex=XXX
// Specify mutex mode. XXX can be "no", "full".
//
// _busy_timeout=XXX"| _timeout=XXX
// Specify value for sqlite3_busy_timeout.
// _txlock=XXX
// Specify locking behavior for transactions. XXX can be "immediate",
// "deferred", "exclusive".
//
// _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.
// _auto_vacuum=X | _vacuum=X
// 0 | none - Auto Vacuum disabled
// 1 | full - Auto Vacuum FULL
// 2 | incremental - Auto Vacuum Incremental
//
// _defer_foreign_keys=Boolean | _defer_fk=Boolean
// Defer Foreign Keys until outermost transaction is committed.
// _busy_timeout=XXX"| _timeout=XXX
// Specify value for sqlite3_busy_timeout.
//
// _foreign_keys=Boolean | _fk=Boolean
// Enable or disable enforcement of foreign keys.
// _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.
//
// _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.
// _defer_foreign_keys=Boolean | _defer_fk=Boolean
// Defer Foreign Keys until outermost transaction is committed.
//
// _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
// _foreign_keys=Boolean | _fk=Boolean
// Enable or disable enforcement of foreign keys.
//
// _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
// _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.
//
// _query_only=Boolean
// The query_only pragma prevents all changes to database files when enabled.
// _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
//
// _recursive_triggers=Boolean | _rt=Boolean
// Enable or disable recursive triggers.
// _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
//
// _secure_delete=Boolean|FAST
// When secure_delete is on, SQLite overwrites deleted content with zeros.
// https://www.sqlite.org/pragma.html#pragma_secure_delete
// _query_only=Boolean
// The query_only pragma prevents all changes to database files when enabled.
//
// _synchronous=X | _sync=X
// Change the setting of the "synchronous" flag.
// https://www.sqlite.org/pragma.html#pragma_synchronous
// _recursive_triggers=Boolean | _rt=Boolean
// Enable or disable recursive triggers.
//
// _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")
Expand Down
114 changes: 89 additions & 25 deletions sqlite3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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)
Expand Down