diff --git a/context.go b/context.go index 7f59b26..5d04f5f 100644 --- a/context.go +++ b/context.go @@ -16,6 +16,22 @@ type Context struct { handle uint32 } +// SetAuxData saves metadata for argument n of the function. +// +// https://www.sqlite.org/c3ref/get_auxdata.html +func (c Context) SetAuxData(n int, data any) { + ptr := util.AddHandle(c.ctx, data) + c.call(c.api.setAuxData, uint64(c.handle), uint64(n), uint64(ptr)) +} + +// GetAuxData returns metadata for argument n of the function. +// +// https://www.sqlite.org/c3ref/get_auxdata.html +func (c Context) GetAuxData(n int) any { + ptr := uint32(c.call(c.api.getAuxData, uint64(c.handle), uint64(n))) + return util.GetHandle(c.ctx, ptr) +} + // ResultBool sets the result of the function to a bool. // SQLite does not have a separate boolean storage class. // Instead, boolean values are stored as integers 0 (false) and 1 (true). diff --git a/embed/exports.txt b/embed/exports.txt index 45418fa..94bfa66 100644 --- a/embed/exports.txt +++ b/embed/exports.txt @@ -48,12 +48,14 @@ sqlite3_changes64 sqlite3_last_insert_rowid sqlite3_get_autocommit sqlite3_anycollseq_init -sqlite3_create_go_collation -sqlite3_create_go_function -sqlite3_create_go_aggregate_function -sqlite3_create_go_window_function +sqlite3_create_collation_go +sqlite3_create_function_go +sqlite3_create_aggregate_function_go +sqlite3_create_window_function_go sqlite3_aggregate_context sqlite3_user_data +sqlite3_set_auxdata_go +sqlite3_get_auxdata sqlite3_value_type sqlite3_value_int64 sqlite3_value_double diff --git a/embed/sqlite3.wasm b/embed/sqlite3.wasm index 4dd5c40..7260b42 100755 Binary files a/embed/sqlite3.wasm and b/embed/sqlite3.wasm differ diff --git a/func_test.go b/func_test.go index d6cb53f..e9f6489 100644 --- a/func_test.go +++ b/func_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "log" + "regexp" "golang.org/x/text/collate" "golang.org/x/text/language" @@ -117,3 +118,64 @@ func ExampleConn_CreateFunction() { // COTÉE // COTER } + +func ExampleContext_SetAuxData() { + db, err := sqlite3.Open(":memory:") + if err != nil { + log.Fatal(err) + } + + err = db.Exec(`CREATE TABLE IF NOT EXISTS words (word VARCHAR(10))`) + if err != nil { + log.Fatal(err) + } + + err = db.Exec(`INSERT INTO words (word) VALUES ('côte'), ('cote'), ('coter'), ('coté'), ('cotée'), ('côté')`) + if err != nil { + log.Fatal(err) + } + + err = db.CreateFunction("regexp", 2, sqlite3.DETERMINISTIC|sqlite3.INNOCUOUS, func(ctx sqlite3.Context, arg ...sqlite3.Value) { + re, ok := ctx.GetAuxData(0).(*regexp.Regexp) + if !ok { + r, err := regexp.Compile(arg[0].Text()) + if err != nil { + ctx.ResultError(err) + return + } + ctx.SetAuxData(0, r) + re = r + } + ctx.ResultBool(re.Match(arg[1].RawText())) + }) + if err != nil { + log.Fatal(err) + } + + stmt, _, err := db.Prepare(`SELECT word FROM words WHERE word REGEXP '^\p{L}+e$'`) + if err != nil { + log.Fatal(err) + } + defer stmt.Close() + + for stmt.Step() { + fmt.Println(stmt.ColumnText(0)) + } + if err := stmt.Err(); err != nil { + log.Fatal(err) + } + + err = stmt.Close() + if err != nil { + log.Fatal(err) + } + + err = db.Close() + if err != nil { + log.Fatal(err) + } + // Unordered output: + // cote + // côte + // cotée +} diff --git a/internal/util/handle.go b/internal/util/handle.go index 20444a0..6aa1dc7 100644 --- a/internal/util/handle.go +++ b/internal/util/handle.go @@ -29,7 +29,7 @@ func (s *handleState) Close() (err error) { func GetHandle(ctx context.Context, id uint32) any { if id == 0 { - panic(NilErr) + return nil } s := ctx.Value(handleKey{}).(*handleState) return s.handles[^id] diff --git a/module.go b/module.go index 725f6da..e92525b 100644 --- a/module.go +++ b/module.go @@ -158,12 +158,14 @@ func newSQLite(mod api.Module) (sqlt *sqlite, err error) { changes: getFun("sqlite3_changes64"), lastRowid: getFun("sqlite3_last_insert_rowid"), autocommit: getFun("sqlite3_get_autocommit"), - createCollation: getFun("sqlite3_create_go_collation"), - createFunction: getFun("sqlite3_create_go_function"), - createAggregate: getFun("sqlite3_create_go_aggregate_function"), - createWindow: getFun("sqlite3_create_go_window_function"), + createCollation: getFun("sqlite3_create_collation_go"), + createFunction: getFun("sqlite3_create_function_go"), + createAggregate: getFun("sqlite3_create_aggregate_function_go"), + createWindow: getFun("sqlite3_create_window_function_go"), aggregateCtx: getFun("sqlite3_aggregate_context"), userData: getFun("sqlite3_user_data"), + setAuxData: getFun("sqlite3_set_auxdata_go"), + getAuxData: getFun("sqlite3_get_auxdata"), valueType: getFun("sqlite3_value_type"), valueInteger: getFun("sqlite3_value_int64"), valueFloat: getFun("sqlite3_value_double"), @@ -381,6 +383,8 @@ type sqliteAPI struct { createWindow api.Function aggregateCtx api.Function userData api.Function + setAuxData api.Function + getAuxData api.Function valueType api.Function valueInteger api.Function valueFloat api.Function diff --git a/sqlite3/func.c b/sqlite3/func.c index 89a4712..80e4981 100644 --- a/sqlite3/func.c +++ b/sqlite3/func.c @@ -10,27 +10,31 @@ void go_value(sqlite3_context *); void go_inverse(sqlite3_context *, int, sqlite3_value **); void go_destroy(void *); -int sqlite3_create_go_collation(sqlite3 *db, const char *zName, void *pApp) { +int sqlite3_create_collation_go(sqlite3 *db, const char *zName, void *pApp) { return sqlite3_create_collation_v2(db, zName, SQLITE_UTF8, pApp, go_compare, go_destroy); } -int sqlite3_create_go_function(sqlite3 *db, const char *zName, int nArg, +int sqlite3_create_function_go(sqlite3 *db, const char *zName, int nArg, int flags, void *pApp) { return sqlite3_create_function_v2(db, zName, nArg, SQLITE_UTF8 | flags, pApp, go_func, NULL, NULL, go_destroy); } -int sqlite3_create_go_aggregate_function(sqlite3 *db, const char *zName, +int sqlite3_create_aggregate_function_go(sqlite3 *db, const char *zName, int nArg, int flags, void *pApp) { return sqlite3_create_window_function(db, zName, nArg, SQLITE_UTF8 | flags, pApp, go_step, go_final, NULL, NULL, go_destroy); } -int sqlite3_create_go_window_function(sqlite3 *db, const char *zName, int nArg, +int sqlite3_create_window_function_go(sqlite3 *db, const char *zName, int nArg, int flags, void *pApp) { return sqlite3_create_window_function(db, zName, nArg, SQLITE_UTF8 | flags, pApp, go_step, go_final, go_value, go_inverse, go_destroy); } + +void sqlite3_set_auxdata_go(sqlite3_context *ctx, int iArg, void *pAux) { + sqlite3_set_auxdata(ctx, iArg, pAux, go_destroy); +}