From ba97a23a7f0670fd1ff44e16ba61d10f66c6899d Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Wed, 23 May 2018 22:20:06 +0200 Subject: Rename Renamed files containing current features. --- sqlite3_fts3_test.go | 130 --------- sqlite3_fts5.go | 13 - sqlite3_icu.go | 13 - sqlite3_json1.go | 12 - sqlite3_opt_fts3_test.go | 130 +++++++++ sqlite3_opt_fts5.go | 13 + sqlite3_opt_icu.go | 13 + sqlite3_opt_json1.go | 12 + sqlite3_opt_vtable.go | 646 +++++++++++++++++++++++++++++++++++++++++++++ sqlite3_opt_vtable_test.go | 485 ++++++++++++++++++++++++++++++++++ sqlite3_vtable.go | 646 --------------------------------------------- sqlite3_vtable_test.go | 485 ---------------------------------- 12 files changed, 1299 insertions(+), 1299 deletions(-) delete mode 100644 sqlite3_fts3_test.go delete mode 100644 sqlite3_fts5.go delete mode 100644 sqlite3_icu.go delete mode 100644 sqlite3_json1.go create mode 100644 sqlite3_opt_fts3_test.go create mode 100644 sqlite3_opt_fts5.go create mode 100644 sqlite3_opt_icu.go create mode 100644 sqlite3_opt_json1.go create mode 100644 sqlite3_opt_vtable.go create mode 100644 sqlite3_opt_vtable_test.go delete mode 100644 sqlite3_vtable.go delete mode 100644 sqlite3_vtable_test.go diff --git a/sqlite3_fts3_test.go b/sqlite3_fts3_test.go deleted file mode 100644 index e06fc5d..0000000 --- a/sqlite3_fts3_test.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (C) 2015 Yasuhiro Matsumoto . -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package sqlite3 - -import ( - "database/sql" - "os" - "testing" -) - -func TestFTS3(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - _, err = db.Exec("DROP TABLE foo") - _, err = db.Exec("CREATE VIRTUAL TABLE foo USING fts3(id INTEGER PRIMARY KEY, value TEXT)") - if err != nil { - t.Fatal("Failed to create table:", err) - } - - _, err = db.Exec("INSERT INTO foo(id, value) VALUES(?, ?)", 1, `今日の 晩御飯は 天麩羅よ`) - if err != nil { - t.Fatal("Failed to insert value:", err) - } - - _, err = db.Exec("INSERT INTO foo(id, value) VALUES(?, ?)", 2, `今日は いい 天気だ`) - if err != nil { - t.Fatal("Failed to insert value:", err) - } - - rows, err := db.Query("SELECT id, value FROM foo WHERE value MATCH '今日* 天*'") - if err != nil { - t.Fatal("Unable to query foo table:", err) - } - defer rows.Close() - - for rows.Next() { - var id int - var value string - - if err := rows.Scan(&id, &value); err != nil { - t.Error("Unable to scan results:", err) - continue - } - - if id == 1 && value != `今日の 晩御飯は 天麩羅よ` { - t.Error("Value for id 1 should be `今日の 晩御飯は 天麩羅よ`, but:", value) - } else if id == 2 && value != `今日は いい 天気だ` { - t.Error("Value for id 2 should be `今日は いい 天気だ`, but:", value) - } - } - - rows, err = db.Query("SELECT value FROM foo WHERE value MATCH '今日* 天麩羅*'") - if err != nil { - t.Fatal("Unable to query foo table:", err) - } - defer rows.Close() - - var value string - if !rows.Next() { - t.Fatal("Result should be only one") - } - - if err := rows.Scan(&value); err != nil { - t.Fatal("Unable to scan results:", err) - } - - if value != `今日の 晩御飯は 天麩羅よ` { - t.Fatal("Value should be `今日の 晩御飯は 天麩羅よ`, but:", value) - } - - if rows.Next() { - t.Fatal("Result should be only one") - } -} - -func TestFTS4(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - _, err = db.Exec("DROP TABLE foo") - _, err = db.Exec("CREATE VIRTUAL TABLE foo USING fts4(tokenize=unicode61, id INTEGER PRIMARY KEY, value TEXT)") - switch { - case err != nil && err.Error() == "unknown tokenizer: unicode61": - t.Skip("FTS4 not supported") - case err != nil: - t.Fatal("Failed to create table:", err) - } - - _, err = db.Exec("INSERT INTO foo(id, value) VALUES(?, ?)", 1, `février`) - if err != nil { - t.Fatal("Failed to insert value:", err) - } - - rows, err := db.Query("SELECT value FROM foo WHERE value MATCH 'fevrier'") - if err != nil { - t.Fatal("Unable to query foo table:", err) - } - defer rows.Close() - - var value string - if !rows.Next() { - t.Fatal("Result should be only one") - } - - if err := rows.Scan(&value); err != nil { - t.Fatal("Unable to scan results:", err) - } - - if value != `février` { - t.Fatal("Value should be `février`, but:", value) - } - - if rows.Next() { - t.Fatal("Result should be only one") - } -} diff --git a/sqlite3_fts5.go b/sqlite3_fts5.go deleted file mode 100644 index 0e65d69..0000000 --- a/sqlite3_fts5.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (C) 2014 Yasuhiro Matsumoto . -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. -// +build fts5 - -package sqlite3 - -/* -#cgo CFLAGS: -DSQLITE_ENABLE_FTS5 -#cgo LDFLAGS: -lm -*/ -import "C" diff --git a/sqlite3_icu.go b/sqlite3_icu.go deleted file mode 100644 index e960626..0000000 --- a/sqlite3_icu.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (C) 2014 Yasuhiro Matsumoto . -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. -// +build icu - -package sqlite3 - -/* -#cgo LDFLAGS: -licuuc -licui18n -#cgo CFLAGS: -DSQLITE_ENABLE_ICU -*/ -import "C" diff --git a/sqlite3_json1.go b/sqlite3_json1.go deleted file mode 100644 index a7b2473..0000000 --- a/sqlite3_json1.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (C) 2014 Yasuhiro Matsumoto . -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. -// +build json1 - -package sqlite3 - -/* -#cgo CFLAGS: -DSQLITE_ENABLE_JSON1 -*/ -import "C" diff --git a/sqlite3_opt_fts3_test.go b/sqlite3_opt_fts3_test.go new file mode 100644 index 0000000..e06fc5d --- /dev/null +++ b/sqlite3_opt_fts3_test.go @@ -0,0 +1,130 @@ +// Copyright (C) 2015 Yasuhiro Matsumoto . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package sqlite3 + +import ( + "database/sql" + "os" + "testing" +) + +func TestFTS3(t *testing.T) { + tempFilename := TempFilename(t) + defer os.Remove(tempFilename) + db, err := sql.Open("sqlite3", tempFilename) + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer db.Close() + + _, err = db.Exec("DROP TABLE foo") + _, err = db.Exec("CREATE VIRTUAL TABLE foo USING fts3(id INTEGER PRIMARY KEY, value TEXT)") + if err != nil { + t.Fatal("Failed to create table:", err) + } + + _, err = db.Exec("INSERT INTO foo(id, value) VALUES(?, ?)", 1, `今日の 晩御飯は 天麩羅よ`) + if err != nil { + t.Fatal("Failed to insert value:", err) + } + + _, err = db.Exec("INSERT INTO foo(id, value) VALUES(?, ?)", 2, `今日は いい 天気だ`) + if err != nil { + t.Fatal("Failed to insert value:", err) + } + + rows, err := db.Query("SELECT id, value FROM foo WHERE value MATCH '今日* 天*'") + if err != nil { + t.Fatal("Unable to query foo table:", err) + } + defer rows.Close() + + for rows.Next() { + var id int + var value string + + if err := rows.Scan(&id, &value); err != nil { + t.Error("Unable to scan results:", err) + continue + } + + if id == 1 && value != `今日の 晩御飯は 天麩羅よ` { + t.Error("Value for id 1 should be `今日の 晩御飯は 天麩羅よ`, but:", value) + } else if id == 2 && value != `今日は いい 天気だ` { + t.Error("Value for id 2 should be `今日は いい 天気だ`, but:", value) + } + } + + rows, err = db.Query("SELECT value FROM foo WHERE value MATCH '今日* 天麩羅*'") + if err != nil { + t.Fatal("Unable to query foo table:", err) + } + defer rows.Close() + + var value string + if !rows.Next() { + t.Fatal("Result should be only one") + } + + if err := rows.Scan(&value); err != nil { + t.Fatal("Unable to scan results:", err) + } + + if value != `今日の 晩御飯は 天麩羅よ` { + t.Fatal("Value should be `今日の 晩御飯は 天麩羅よ`, but:", value) + } + + if rows.Next() { + t.Fatal("Result should be only one") + } +} + +func TestFTS4(t *testing.T) { + tempFilename := TempFilename(t) + defer os.Remove(tempFilename) + db, err := sql.Open("sqlite3", tempFilename) + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer db.Close() + + _, err = db.Exec("DROP TABLE foo") + _, err = db.Exec("CREATE VIRTUAL TABLE foo USING fts4(tokenize=unicode61, id INTEGER PRIMARY KEY, value TEXT)") + switch { + case err != nil && err.Error() == "unknown tokenizer: unicode61": + t.Skip("FTS4 not supported") + case err != nil: + t.Fatal("Failed to create table:", err) + } + + _, err = db.Exec("INSERT INTO foo(id, value) VALUES(?, ?)", 1, `février`) + if err != nil { + t.Fatal("Failed to insert value:", err) + } + + rows, err := db.Query("SELECT value FROM foo WHERE value MATCH 'fevrier'") + if err != nil { + t.Fatal("Unable to query foo table:", err) + } + defer rows.Close() + + var value string + if !rows.Next() { + t.Fatal("Result should be only one") + } + + if err := rows.Scan(&value); err != nil { + t.Fatal("Unable to scan results:", err) + } + + if value != `février` { + t.Fatal("Value should be `février`, but:", value) + } + + if rows.Next() { + t.Fatal("Result should be only one") + } +} diff --git a/sqlite3_opt_fts5.go b/sqlite3_opt_fts5.go new file mode 100644 index 0000000..0e65d69 --- /dev/null +++ b/sqlite3_opt_fts5.go @@ -0,0 +1,13 @@ +// Copyright (C) 2014 Yasuhiro Matsumoto . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. +// +build fts5 + +package sqlite3 + +/* +#cgo CFLAGS: -DSQLITE_ENABLE_FTS5 +#cgo LDFLAGS: -lm +*/ +import "C" diff --git a/sqlite3_opt_icu.go b/sqlite3_opt_icu.go new file mode 100644 index 0000000..e960626 --- /dev/null +++ b/sqlite3_opt_icu.go @@ -0,0 +1,13 @@ +// Copyright (C) 2014 Yasuhiro Matsumoto . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. +// +build icu + +package sqlite3 + +/* +#cgo LDFLAGS: -licuuc -licui18n +#cgo CFLAGS: -DSQLITE_ENABLE_ICU +*/ +import "C" diff --git a/sqlite3_opt_json1.go b/sqlite3_opt_json1.go new file mode 100644 index 0000000..a7b2473 --- /dev/null +++ b/sqlite3_opt_json1.go @@ -0,0 +1,12 @@ +// Copyright (C) 2014 Yasuhiro Matsumoto . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. +// +build json1 + +package sqlite3 + +/* +#cgo CFLAGS: -DSQLITE_ENABLE_JSON1 +*/ +import "C" diff --git a/sqlite3_opt_vtable.go b/sqlite3_opt_vtable.go new file mode 100644 index 0000000..8bef291 --- /dev/null +++ b/sqlite3_opt_vtable.go @@ -0,0 +1,646 @@ +// Copyright (C) 2014 Yasuhiro Matsumoto . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. +// +build vtable + +package sqlite3 + +/* +#cgo CFLAGS: -std=gnu99 +#cgo CFLAGS: -DSQLITE_ENABLE_RTREE -DSQLITE_THREADSAFE +#cgo CFLAGS: -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_FTS4_UNICODE61 +#cgo CFLAGS: -DSQLITE_TRACE_SIZE_LIMIT=15 +#cgo CFLAGS: -DSQLITE_ENABLE_COLUMN_METADATA=1 +#cgo CFLAGS: -Wno-deprecated-declarations + +#ifndef USE_LIBSQLITE3 +#include +#else +#include +#endif +#include +#include +#include + +static inline char *_sqlite3_mprintf(char *zFormat, char *arg) { + return sqlite3_mprintf(zFormat, arg); +} + +typedef struct goVTab goVTab; + +struct goVTab { + sqlite3_vtab base; + void *vTab; +}; + +uintptr_t goMInit(void *db, void *pAux, int argc, char **argv, char **pzErr, int isCreate); + +static int cXInit(sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char **pzErr, int isCreate) { + void *vTab = (void *)goMInit(db, pAux, argc, (char**)argv, pzErr, isCreate); + if (!vTab || *pzErr) { + return SQLITE_ERROR; + } + goVTab *pvTab = (goVTab *)sqlite3_malloc(sizeof(goVTab)); + if (!pvTab) { + *pzErr = sqlite3_mprintf("%s", "Out of memory"); + return SQLITE_NOMEM; + } + memset(pvTab, 0, sizeof(goVTab)); + pvTab->vTab = vTab; + + *ppVTab = (sqlite3_vtab *)pvTab; + *pzErr = 0; + return SQLITE_OK; +} + +static inline int cXCreate(sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char **pzErr) { + return cXInit(db, pAux, argc, argv, ppVTab, pzErr, 1); +} +static inline int cXConnect(sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char **pzErr) { + return cXInit(db, pAux, argc, argv, ppVTab, pzErr, 0); +} + +char* goVBestIndex(void *pVTab, void *icp); + +static inline int cXBestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *info) { + char *pzErr = goVBestIndex(((goVTab*)pVTab)->vTab, info); + if (pzErr) { + if (pVTab->zErrMsg) + sqlite3_free(pVTab->zErrMsg); + pVTab->zErrMsg = pzErr; + return SQLITE_ERROR; + } + return SQLITE_OK; +} + +char* goVRelease(void *pVTab, int isDestroy); + +static int cXRelease(sqlite3_vtab *pVTab, int isDestroy) { + char *pzErr = goVRelease(((goVTab*)pVTab)->vTab, isDestroy); + if (pzErr) { + if (pVTab->zErrMsg) + sqlite3_free(pVTab->zErrMsg); + pVTab->zErrMsg = pzErr; + return SQLITE_ERROR; + } + if (pVTab->zErrMsg) + sqlite3_free(pVTab->zErrMsg); + sqlite3_free(pVTab); + return SQLITE_OK; +} + +static inline int cXDisconnect(sqlite3_vtab *pVTab) { + return cXRelease(pVTab, 0); +} +static inline int cXDestroy(sqlite3_vtab *pVTab) { + return cXRelease(pVTab, 1); +} + +typedef struct goVTabCursor goVTabCursor; + +struct goVTabCursor { + sqlite3_vtab_cursor base; + void *vTabCursor; +}; + +uintptr_t goVOpen(void *pVTab, char **pzErr); + +static int cXOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) { + void *vTabCursor = (void *)goVOpen(((goVTab*)pVTab)->vTab, &(pVTab->zErrMsg)); + goVTabCursor *pCursor = (goVTabCursor *)sqlite3_malloc(sizeof(goVTabCursor)); + if (!pCursor) { + return SQLITE_NOMEM; + } + memset(pCursor, 0, sizeof(goVTabCursor)); + pCursor->vTabCursor = vTabCursor; + *ppCursor = (sqlite3_vtab_cursor *)pCursor; + return SQLITE_OK; +} + +static int setErrMsg(sqlite3_vtab_cursor *pCursor, char *pzErr) { + if (pCursor->pVtab->zErrMsg) + sqlite3_free(pCursor->pVtab->zErrMsg); + pCursor->pVtab->zErrMsg = pzErr; + return SQLITE_ERROR; +} + +char* goVClose(void *pCursor); + +static int cXClose(sqlite3_vtab_cursor *pCursor) { + char *pzErr = goVClose(((goVTabCursor*)pCursor)->vTabCursor); + if (pzErr) { + return setErrMsg(pCursor, pzErr); + } + sqlite3_free(pCursor); + return SQLITE_OK; +} + +char* goVFilter(void *pCursor, int idxNum, char* idxName, int argc, sqlite3_value **argv); + +static int cXFilter(sqlite3_vtab_cursor *pCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) { + char *pzErr = goVFilter(((goVTabCursor*)pCursor)->vTabCursor, idxNum, (char*)idxStr, argc, argv); + if (pzErr) { + return setErrMsg(pCursor, pzErr); + } + return SQLITE_OK; +} + +char* goVNext(void *pCursor); + +static int cXNext(sqlite3_vtab_cursor *pCursor) { + char *pzErr = goVNext(((goVTabCursor*)pCursor)->vTabCursor); + if (pzErr) { + return setErrMsg(pCursor, pzErr); + } + return SQLITE_OK; +} + +int goVEof(void *pCursor); + +static inline int cXEof(sqlite3_vtab_cursor *pCursor) { + return goVEof(((goVTabCursor*)pCursor)->vTabCursor); +} + +char* goVColumn(void *pCursor, void *cp, int col); + +static int cXColumn(sqlite3_vtab_cursor *pCursor, sqlite3_context *ctx, int i) { + char *pzErr = goVColumn(((goVTabCursor*)pCursor)->vTabCursor, ctx, i); + if (pzErr) { + return setErrMsg(pCursor, pzErr); + } + return SQLITE_OK; +} + +char* goVRowid(void *pCursor, sqlite3_int64 *pRowid); + +static int cXRowid(sqlite3_vtab_cursor *pCursor, sqlite3_int64 *pRowid) { + char *pzErr = goVRowid(((goVTabCursor*)pCursor)->vTabCursor, pRowid); + if (pzErr) { + return setErrMsg(pCursor, pzErr); + } + return SQLITE_OK; +} + +char* goVUpdate(void *pVTab, int argc, sqlite3_value **argv, sqlite3_int64 *pRowid); + +static int cXUpdate(sqlite3_vtab *pVTab, int argc, sqlite3_value **argv, sqlite3_int64 *pRowid) { + char *pzErr = goVUpdate(((goVTab*)pVTab)->vTab, argc, argv, pRowid); + if (pzErr) { + if (pVTab->zErrMsg) + sqlite3_free(pVTab->zErrMsg); + pVTab->zErrMsg = pzErr; + return SQLITE_ERROR; + } + return SQLITE_OK; +} + +static sqlite3_module goModule = { + 0, // iVersion + cXCreate, // xCreate - create a table + cXConnect, // xConnect - connect to an existing table + cXBestIndex, // xBestIndex - Determine search strategy + cXDisconnect, // xDisconnect - Disconnect from a table + cXDestroy, // xDestroy - Drop a table + cXOpen, // xOpen - open a cursor + cXClose, // xClose - close a cursor + cXFilter, // xFilter - configure scan constraints + cXNext, // xNext - advance a cursor + cXEof, // xEof + cXColumn, // xColumn - read data + cXRowid, // xRowid - read data + cXUpdate, // xUpdate - write data +// Not implemented + 0, // xBegin - begin transaction + 0, // xSync - sync transaction + 0, // xCommit - commit transaction + 0, // xRollback - rollback transaction + 0, // xFindFunction - function overloading + 0, // xRename - rename the table + 0, // xSavepoint + 0, // xRelease + 0 // xRollbackTo +}; + +void goMDestroy(void*); + +static int _sqlite3_create_module(sqlite3 *db, const char *zName, uintptr_t pClientData) { + return sqlite3_create_module_v2(db, zName, &goModule, (void*) pClientData, goMDestroy); +} +*/ +import "C" + +import ( + "fmt" + "math" + "reflect" + "unsafe" +) + +type sqliteModule struct { + c *SQLiteConn + name string + module Module +} + +type sqliteVTab struct { + module *sqliteModule + vTab VTab +} + +type sqliteVTabCursor struct { + vTab *sqliteVTab + vTabCursor VTabCursor +} + +// Op is type of operations. +type Op uint8 + +// Op mean identity of operations. +const ( + OpEQ Op = 2 + OpGT = 4 + OpLE = 8 + OpLT = 16 + OpGE = 32 + OpMATCH = 64 + OpLIKE = 65 /* 3.10.0 and later only */ + OpGLOB = 66 /* 3.10.0 and later only */ + OpREGEXP = 67 /* 3.10.0 and later only */ + OpScanUnique = 1 /* Scan visits at most 1 row */ +) + +// InfoConstraint give information of constraint. +type InfoConstraint struct { + Column int + Op Op + Usable bool +} + +// InfoOrderBy give information of order-by. +type InfoOrderBy struct { + Column int + Desc bool +} + +func constraints(info *C.sqlite3_index_info) []InfoConstraint { + l := info.nConstraint + slice := (*[1 << 30]C.struct_sqlite3_index_constraint)(unsafe.Pointer(info.aConstraint))[:l:l] + + cst := make([]InfoConstraint, 0, l) + for _, c := range slice { + var usable bool + if c.usable > 0 { + usable = true + } + cst = append(cst, InfoConstraint{ + Column: int(c.iColumn), + Op: Op(c.op), + Usable: usable, + }) + } + return cst +} + +func orderBys(info *C.sqlite3_index_info) []InfoOrderBy { + l := info.nOrderBy + slice := (*[1 << 30]C.struct_sqlite3_index_orderby)(unsafe.Pointer(info.aOrderBy))[:l:l] + + ob := make([]InfoOrderBy, 0, l) + for _, c := range slice { + var desc bool + if c.desc > 0 { + desc = true + } + ob = append(ob, InfoOrderBy{ + Column: int(c.iColumn), + Desc: desc, + }) + } + return ob +} + +// IndexResult is a Go struct representation of what eventually ends up in the +// output fields for `sqlite3_index_info` +// See: https://www.sqlite.org/c3ref/index_info.html +type IndexResult struct { + Used []bool // aConstraintUsage + IdxNum int + IdxStr string + AlreadyOrdered bool // orderByConsumed + EstimatedCost float64 + EstimatedRows float64 +} + +// mPrintf is a utility wrapper around sqlite3_mprintf +func mPrintf(format, arg string) *C.char { + cf := C.CString(format) + defer C.free(unsafe.Pointer(cf)) + ca := C.CString(arg) + defer C.free(unsafe.Pointer(ca)) + return C._sqlite3_mprintf(cf, ca) +} + +//export goMInit +func goMInit(db, pClientData unsafe.Pointer, argc C.int, argv **C.char, pzErr **C.char, isCreate C.int) C.uintptr_t { + m := lookupHandle(uintptr(pClientData)).(*sqliteModule) + if m.c.db != (*C.sqlite3)(db) { + *pzErr = mPrintf("%s", "Inconsistent db handles") + return 0 + } + args := make([]string, argc) + var A []*C.char + slice := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(argv)), Len: int(argc), Cap: int(argc)} + a := reflect.NewAt(reflect.TypeOf(A), unsafe.Pointer(&slice)).Elem().Interface() + for i, s := range a.([]*C.char) { + args[i] = C.GoString(s) + } + var vTab VTab + var err error + if isCreate == 1 { + vTab, err = m.module.Create(m.c, args) + } else { + vTab, err = m.module.Connect(m.c, args) + } + + if err != nil { + *pzErr = mPrintf("%s", err.Error()) + return 0 + } + vt := sqliteVTab{m, vTab} + *pzErr = nil + return C.uintptr_t(newHandle(m.c, &vt)) +} + +//export goVRelease +func goVRelease(pVTab unsafe.Pointer, isDestroy C.int) *C.char { + vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab) + var err error + if isDestroy == 1 { + err = vt.vTab.Destroy() + } else { + err = vt.vTab.Disconnect() + } + if err != nil { + return mPrintf("%s", err.Error()) + } + return nil +} + +//export goVOpen +func goVOpen(pVTab unsafe.Pointer, pzErr **C.char) C.uintptr_t { + vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab) + vTabCursor, err := vt.vTab.Open() + if err != nil { + *pzErr = mPrintf("%s", err.Error()) + return 0 + } + vtc := sqliteVTabCursor{vt, vTabCursor} + *pzErr = nil + return C.uintptr_t(newHandle(vt.module.c, &vtc)) +} + +//export goVBestIndex +func goVBestIndex(pVTab unsafe.Pointer, icp unsafe.Pointer) *C.char { + vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab) + info := (*C.sqlite3_index_info)(icp) + csts := constraints(info) + res, err := vt.vTab.BestIndex(csts, orderBys(info)) + if err != nil { + return mPrintf("%s", err.Error()) + } + if len(res.Used) != len(csts) { + return mPrintf("Result.Used != expected value", "") + } + + // Get a pointer to constraint_usage struct so we can update in place. + l := info.nConstraint + s := (*[1 << 30]C.struct_sqlite3_index_constraint_usage)(unsafe.Pointer(info.aConstraintUsage))[:l:l] + index := 1 + for i := C.int(0); i < info.nConstraint; i++ { + if res.Used[i] { + s[i].argvIndex = C.int(index) + s[i].omit = C.uchar(1) + index++ + } + } + + info.idxNum = C.int(res.IdxNum) + idxStr := C.CString(res.IdxStr) + defer C.free(unsafe.Pointer(idxStr)) + info.idxStr = idxStr + info.needToFreeIdxStr = C.int(0) + if res.AlreadyOrdered { + info.orderByConsumed = C.int(1) + } + info.estimatedCost = C.double(res.EstimatedCost) + info.estimatedRows = C.sqlite3_int64(res.EstimatedRows) + + return nil +} + +//export goVClose +func goVClose(pCursor unsafe.Pointer) *C.char { + vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor) + err := vtc.vTabCursor.Close() + if err != nil { + return mPrintf("%s", err.Error()) + } + return nil +} + +//export goMDestroy +func goMDestroy(pClientData unsafe.Pointer) { + m := lookupHandle(uintptr(pClientData)).(*sqliteModule) + m.module.DestroyModule() +} + +//export goVFilter +func goVFilter(pCursor unsafe.Pointer, idxNum C.int, idxName *C.char, argc C.int, argv **C.sqlite3_value) *C.char { + vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor) + args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc] + vals := make([]interface{}, 0, argc) + for _, v := range args { + conv, err := callbackArgGeneric(v) + if err != nil { + return mPrintf("%s", err.Error()) + } + vals = append(vals, conv.Interface()) + } + err := vtc.vTabCursor.Filter(int(idxNum), C.GoString(idxName), vals) + if err != nil { + return mPrintf("%s", err.Error()) + } + return nil +} + +//export goVNext +func goVNext(pCursor unsafe.Pointer) *C.char { + vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor) + err := vtc.vTabCursor.Next() + if err != nil { + return mPrintf("%s", err.Error()) + } + return nil +} + +//export goVEof +func goVEof(pCursor unsafe.Pointer) C.int { + vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor) + err := vtc.vTabCursor.EOF() + if err { + return 1 + } + return 0 +} + +//export goVColumn +func goVColumn(pCursor, cp unsafe.Pointer, col C.int) *C.char { + vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor) + c := (*SQLiteContext)(cp) + err := vtc.vTabCursor.Column(c, int(col)) + if err != nil { + return mPrintf("%s", err.Error()) + } + return nil +} + +//export goVRowid +func goVRowid(pCursor unsafe.Pointer, pRowid *C.sqlite3_int64) *C.char { + vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor) + rowid, err := vtc.vTabCursor.Rowid() + if err != nil { + return mPrintf("%s", err.Error()) + } + *pRowid = C.sqlite3_int64(rowid) + return nil +} + +//export goVUpdate +func goVUpdate(pVTab unsafe.Pointer, argc C.int, argv **C.sqlite3_value, pRowid *C.sqlite3_int64) *C.char { + vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab) + + var tname string + if n, ok := vt.vTab.(interface { + TableName() string + }); ok { + tname = n.TableName() + " " + } + + err := fmt.Errorf("virtual %s table %sis read-only", vt.module.name, tname) + if v, ok := vt.vTab.(VTabUpdater); ok { + // convert argv + args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc] + vals := make([]interface{}, 0, argc) + for _, v := range args { + conv, err := callbackArgGeneric(v) + if err != nil { + return mPrintf("%s", err.Error()) + } + + // work around for SQLITE_NULL + x := conv.Interface() + if z, ok := x.([]byte); ok && z == nil { + x = nil + } + + vals = append(vals, x) + } + + switch { + case argc == 1: + err = v.Delete(vals[0]) + + case argc > 1 && vals[0] == nil: + var id int64 + id, err = v.Insert(vals[1], vals[2:]) + if err == nil { + *pRowid = C.sqlite3_int64(id) + } + + case argc > 1: + err = v.Update(vals[1], vals[2:]) + } + } + + if err != nil { + return mPrintf("%s", err.Error()) + } + + return nil +} + +// Module is a "virtual table module", it defines the implementation of a +// virtual tables. See: http://sqlite.org/c3ref/module.html +type Module interface { + // http://sqlite.org/vtab.html#xcreate + Create(c *SQLiteConn, args []string) (VTab, error) + // http://sqlite.org/vtab.html#xconnect + Connect(c *SQLiteConn, args []string) (VTab, error) + // http://sqlite.org/c3ref/create_module.html + DestroyModule() +} + +// VTab describes a particular instance of the virtual table. +// See: http://sqlite.org/c3ref/vtab.html +type VTab interface { + // http://sqlite.org/vtab.html#xbestindex + BestIndex([]InfoConstraint, []InfoOrderBy) (*IndexResult, error) + // http://sqlite.org/vtab.html#xdisconnect + Disconnect() error + // http://sqlite.org/vtab.html#sqlite3_module.xDestroy + Destroy() error + // http://sqlite.org/vtab.html#xopen + Open() (VTabCursor, error) +} + +// VTabUpdater is a type that allows a VTab to be inserted, updated, or +// deleted. +// See: https://sqlite.org/vtab.html#xupdate +type VTabUpdater interface { + Delete(interface{}) error + Insert(interface{}, []interface{}) (int64, error) + Update(interface{}, []interface{}) error +} + +// VTabCursor describes cursors that point into the virtual table and are used +// to loop through the virtual table. See: http://sqlite.org/c3ref/vtab_cursor.html +type VTabCursor interface { + // http://sqlite.org/vtab.html#xclose + Close() error + // http://sqlite.org/vtab.html#xfilter + Filter(idxNum int, idxStr string, vals []interface{}) error + // http://sqlite.org/vtab.html#xnext + Next() error + // http://sqlite.org/vtab.html#xeof + EOF() bool + // http://sqlite.org/vtab.html#xcolumn + Column(c *SQLiteContext, col int) error + // http://sqlite.org/vtab.html#xrowid + Rowid() (int64, error) +} + +// DeclareVTab declares the Schema of a virtual table. +// See: http://sqlite.org/c3ref/declare_vtab.html +func (c *SQLiteConn) DeclareVTab(sql string) error { + zSQL := C.CString(sql) + defer C.free(unsafe.Pointer(zSQL)) + rv := C.sqlite3_declare_vtab(c.db, zSQL) + if rv != C.SQLITE_OK { + return c.lastError() + } + return nil +} + +// CreateModule registers a virtual table implementation. +// See: http://sqlite.org/c3ref/create_module.html +func (c *SQLiteConn) CreateModule(moduleName string, module Module) error { + mname := C.CString(moduleName) + defer C.free(unsafe.Pointer(mname)) + udm := sqliteModule{c, moduleName, module} + rv := C._sqlite3_create_module(c.db, mname, C.uintptr_t(newHandle(c, &udm))) + if rv != C.SQLITE_OK { + return c.lastError() + } + return nil +} diff --git a/sqlite3_opt_vtable_test.go b/sqlite3_opt_vtable_test.go new file mode 100644 index 0000000..ce6a48c --- /dev/null +++ b/sqlite3_opt_vtable_test.go @@ -0,0 +1,485 @@ +// Copyright (C) 2014 Yasuhiro Matsumoto . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. +// +build vtable + +package sqlite3 + +import ( + "database/sql" + "errors" + "fmt" + "os" + "reflect" + "strings" + "testing" +) + +type testModule struct { + t *testing.T + intarray []int +} + +type testVTab struct { + intarray []int +} + +type testVTabCursor struct { + vTab *testVTab + index int +} + +func (m testModule) Create(c *SQLiteConn, args []string) (VTab, error) { + if len(args) != 6 { + m.t.Fatal("six arguments expected") + } + if args[0] != "test" { + m.t.Fatal("module name") + } + if args[1] != "main" { + m.t.Fatal("db name") + } + if args[2] != "vtab" { + m.t.Fatal("table name") + } + if args[3] != "'1'" { + m.t.Fatal("first arg") + } + if args[4] != "2" { + m.t.Fatal("second arg") + } + if args[5] != "three" { + m.t.Fatal("third argsecond arg") + } + err := c.DeclareVTab("CREATE TABLE x(test TEXT)") + if err != nil { + return nil, err + } + return &testVTab{m.intarray}, nil +} + +func (m testModule) Connect(c *SQLiteConn, args []string) (VTab, error) { + return m.Create(c, args) +} + +func (m testModule) DestroyModule() {} + +func (v *testVTab) BestIndex(cst []InfoConstraint, ob []InfoOrderBy) (*IndexResult, error) { + used := make([]bool, 0, len(cst)) + for range cst { + used = append(used, false) + } + return &IndexResult{ + Used: used, + IdxNum: 0, + IdxStr: "test-index", + AlreadyOrdered: true, + EstimatedCost: 100, + EstimatedRows: 200, + }, nil +} + +func (v *testVTab) Disconnect() error { + return nil +} + +func (v *testVTab) Destroy() error { + return nil +} + +func (v *testVTab) Open() (VTabCursor, error) { + return &testVTabCursor{v, 0}, nil +} + +func (vc *testVTabCursor) Close() error { + return nil +} + +func (vc *testVTabCursor) Filter(idxNum int, idxStr string, vals []interface{}) error { + vc.index = 0 + return nil +} + +func (vc *testVTabCursor) Next() error { + vc.index++ + return nil +} + +func (vc *testVTabCursor) EOF() bool { + return vc.index >= len(vc.vTab.intarray) +} + +func (vc *testVTabCursor) Column(c *SQLiteContext, col int) error { + if col != 0 { + return fmt.Errorf("column index out of bounds: %d", col) + } + c.ResultInt(vc.vTab.intarray[vc.index]) + return nil +} + +func (vc *testVTabCursor) Rowid() (int64, error) { + return int64(vc.index), nil +} + +func TestCreateModule(t *testing.T) { + tempFilename := TempFilename(t) + defer os.Remove(tempFilename) + intarray := []int{1, 2, 3} + sql.Register("sqlite3_TestCreateModule", &SQLiteDriver{ + ConnectHook: func(conn *SQLiteConn) error { + return conn.CreateModule("test", testModule{t, intarray}) + }, + }) + db, err := sql.Open("sqlite3_TestCreateModule", tempFilename) + if err != nil { + t.Fatalf("could not open db: %v", err) + } + _, err = db.Exec("CREATE VIRTUAL TABLE vtab USING test('1', 2, three)") + if err != nil { + t.Fatalf("could not create vtable: %v", err) + } + + var i, value int + rows, err := db.Query("SELECT rowid, * FROM vtab WHERE test = '3'") + if err != nil { + t.Fatalf("couldn't select from virtual table: %v", err) + } + for rows.Next() { + rows.Scan(&i, &value) + if intarray[i] != value { + t.Fatalf("want %v but %v", intarray[i], value) + } + } + + _, err = db.Exec("DROP TABLE vtab") + if err != nil { + t.Fatalf("couldn't drop virtual table: %v", err) + } +} + +func TestVUpdate(t *testing.T) { + tempFilename := TempFilename(t) + defer os.Remove(tempFilename) + + // create module + updateMod := &vtabUpdateModule{t, make(map[string]*vtabUpdateTable)} + + // register module + sql.Register("sqlite3_TestVUpdate", &SQLiteDriver{ + ConnectHook: func(conn *SQLiteConn) error { + return conn.CreateModule("updatetest", updateMod) + }, + }) + + // connect + db, err := sql.Open("sqlite3_TestVUpdate", tempFilename) + if err != nil { + t.Fatalf("could not open db: %v", err) + } + + // create test table + _, err = db.Exec(`CREATE VIRTUAL TABLE vt USING updatetest(f1 integer, f2 text, f3 text)`) + if err != nil { + t.Fatalf("could not create updatetest vtable vt, got: %v", err) + } + + // check that table is defined properly + if len(updateMod.tables) != 1 { + t.Fatalf("expected exactly 1 table to exist, got: %d", len(updateMod.tables)) + } + if _, ok := updateMod.tables["vt"]; !ok { + t.Fatalf("expected table `vt` to exist in tables") + } + + // check nothing in updatetest + rows, err := db.Query(`select * from vt`) + if err != nil { + t.Fatalf("could not query vt, got: %v", err) + } + i, err := getRowCount(rows) + if err != nil { + t.Fatalf("expected no error, got: %v", err) + } + if i != 0 { + t.Fatalf("expected no rows in vt, got: %d", i) + } + + _, err = db.Exec(`delete from vt where f1 = 'yes'`) + if err != nil { + t.Fatalf("expected error on delete, got nil") + } + + // test bad column name + _, err = db.Exec(`insert into vt (f4) values('a')`) + if err == nil { + t.Fatalf("expected error on insert, got nil") + } + + // insert to vt + res, err := db.Exec(`insert into vt (f1, f2, f3) values (115, 'b', 'c'), (116, 'd', 'e')`) + if err != nil { + t.Fatalf("expected no error on insert, got: %v", err) + } + n, err := res.RowsAffected() + if err != nil { + t.Fatalf("expected no error, got: %v", err) + } + if n != 2 { + t.Fatalf("expected 1 row affected, got: %d", n) + } + + // check vt table + vt := updateMod.tables["vt"] + if len(vt.data) != 2 { + t.Fatalf("expected table vt to have exactly 2 rows, got: %d", len(vt.data)) + } + if !reflect.DeepEqual(vt.data[0], []interface{}{int64(115), "b", "c"}) { + t.Fatalf("expected table vt entry 0 to be [115 b c], instead: %v", vt.data[0]) + } + if !reflect.DeepEqual(vt.data[1], []interface{}{int64(116), "d", "e"}) { + t.Fatalf("expected table vt entry 1 to be [116 d e], instead: %v", vt.data[1]) + } + + // query vt + var f1 int + var f2, f3 string + err = db.QueryRow(`select * from vt where f1 = 115`).Scan(&f1, &f2, &f3) + if err != nil { + t.Fatalf("expected no error on vt query, got: %v", err) + } + + // check column values + if f1 != 115 || f2 != "b" || f3 != "c" { + t.Errorf("expected f1==115, f2==b, f3==c, got: %d, %q, %q", f1, f2, f3) + } + + // update vt + res, err = db.Exec(`update vt set f1=117, f2='f' where f3='e'`) + if err != nil { + t.Fatalf("expected no error, got: %v", err) + } + n, err = res.RowsAffected() + if err != nil { + t.Fatalf("expected no error, got: %v", err) + } + if n != 1 { + t.Fatalf("expected exactly one row updated, got: %d", n) + } + + // check vt table + if len(vt.data) != 2 { + t.Fatalf("expected table vt to have exactly 2 rows, got: %d", len(vt.data)) + } + if !reflect.DeepEqual(vt.data[0], []interface{}{int64(115), "b", "c"}) { + t.Fatalf("expected table vt entry 0 to be [115 b c], instead: %v", vt.data[0]) + } + if !reflect.DeepEqual(vt.data[1], []interface{}{int64(117), "f", "e"}) { + t.Fatalf("expected table vt entry 1 to be [117 f e], instead: %v", vt.data[1]) + } + + // delete from vt + res, err = db.Exec(`delete from vt where f1 = 117`) + if err != nil { + t.Fatalf("expected no error, got: %v", err) + } + n, err = res.RowsAffected() + if err != nil { + t.Fatalf("expected no error, got: %v", err) + } + if n != 1 { + t.Fatalf("expected exactly one row deleted, got: %d", n) + } + + // check vt table + if len(vt.data) != 1 { + t.Fatalf("expected table vt to have exactly 1 row, got: %d", len(vt.data)) + } + if !reflect.DeepEqual(vt.data[0], []interface{}{int64(115), "b", "c"}) { + t.Fatalf("expected table vt entry 0 to be [115 b c], instead: %v", vt.data[0]) + } + + // check updatetest has 1 result + rows, err = db.Query(`select * from vt`) + if err != nil { + t.Fatalf("could not query vt, got: %v", err) + } + i, err = getRowCount(rows) + if err != nil { + t.Fatalf("expected no error, got: %v", err) + } + if i != 1 { + t.Fatalf("expected 1 row in vt, got: %d", i) + } +} + +func getRowCount(rows *sql.Rows) (int, error) { + var i int + for rows.Next() { + i++ + } + return i, nil +} + +type vtabUpdateModule struct { + t *testing.T + tables map[string]*vtabUpdateTable +} + +func (m *vtabUpdateModule) Create(c *SQLiteConn, args []string) (VTab, error) { + if len(args) < 2 { + return nil, errors.New("must declare at least one column") + } + + // get database name, table name, and column declarations ... + dbname, tname, decls := args[1], args[2], args[3:] + + // extract column names + types from parameters declarations + cols, typs := make([]string, len(decls)), make([]string, len(decls)) + for i := 0; i < len(decls); i++ { + n, typ := decls[i], "" + if j := strings.IndexAny(n, " \t\n"); j != -1 { + typ, n = strings.TrimSpace(n[j+1:]), n[:j] + } + cols[i], typs[i] = n, typ + } + + // declare table + err := c.DeclareVTab(fmt.Sprintf(`CREATE TABLE "%s"."%s" (%s)`, dbname, tname, strings.Join(decls, ","))) + if err != nil { + return nil, err + } + + // create table + vtab := &vtabUpdateTable{m.t, dbname, tname, cols, typs, make([][]interface{}, 0)} + m.tables[tname] = vtab + return vtab, nil +} + +func (m *vtabUpdateModule) Connect(c *SQLiteConn, args []string) (VTab, error) { + return m.Create(c, args) +} + +func (m *vtabUpdateModule) DestroyModule() {} + +type vtabUpdateTable struct { + t *testing.T + db string + name string + cols []string + typs []string + data [][]interface{} +} + +func (t *vtabUpdateTable) Open() (VTabCursor, error) { + return &vtabUpdateCursor{t, 0}, nil +} + +func (t *vtabUpdateTable) BestIndex(cst []InfoConstraint, ob []InfoOrderBy) (*IndexResult, error) { + return &IndexResult{Used: make([]bool, len(cst))}, nil +} + +func (t *vtabUpdateTable) Disconnect() error { + return nil +} + +func (t *vtabUpdateTable) Destroy() error { + return nil +} + +func (t *vtabUpdateTable) Insert(id interface{}, vals []interface{}) (int64, error) { + var i int64 + if id == nil { + i, t.data = int64(len(t.data)), append(t.data, vals) + return i, nil + } + + var ok bool + i, ok = id.(int64) + if !ok { + return 0, fmt.Errorf("id is invalid type: %T", id) + } + + t.data[i] = vals + + return i, nil +} + +func (t *vtabUpdateTable) Update(id interface{}, vals []interface{}) error { + i, ok := id.(int64) + if !ok { + return fmt.Errorf("id is invalid type: %T", id) + } + + if int(i) >= len(t.data) || i < 0 { + return fmt.Errorf("invalid row id %d", i) + } + + t.data[int(i)] = vals + + return nil +} + +func (t *vtabUpdateTable) Delete(id interface{}) error { + i, ok := id.(int64) + if !ok { + return fmt.Errorf("id is invalid type: %T", id) + } + + if int(i) >= len(t.data) || i < 0 { + return fmt.Errorf("invalid row id %d", i) + } + + t.data = append(t.data[:i], t.data[i+1:]...) + + return nil +} + +type vtabUpdateCursor struct { + t *vtabUpdateTable + i int +} + +func (c *vtabUpdateCursor) Column(ctxt *SQLiteContext, col int) error { + switch x := c.t.data[c.i][col].(type) { + case []byte: + ctxt.ResultBlob(x) + case bool: + ctxt.ResultBool(x) + case float64: + ctxt.ResultDouble(x) + case int: + ctxt.ResultInt(x) + case int64: + ctxt.ResultInt64(x) + case nil: + ctxt.ResultNull() + case string: + ctxt.ResultText(x) + default: + ctxt.ResultText(fmt.Sprintf("%v", x)) + } + + return nil +} + +func (c *vtabUpdateCursor) Filter(ixNum int, ixName string, vals []interface{}) error { + return nil +} + +func (c *vtabUpdateCursor) Next() error { + c.i++ + return nil +} + +func (c *vtabUpdateCursor) EOF() bool { + return c.i >= len(c.t.data) +} + +func (c *vtabUpdateCursor) Rowid() (int64, error) { + return int64(c.i), nil +} + +func (c *vtabUpdateCursor) Close() error { + return nil +} diff --git a/sqlite3_vtable.go b/sqlite3_vtable.go deleted file mode 100644 index 8bef291..0000000 --- a/sqlite3_vtable.go +++ /dev/null @@ -1,646 +0,0 @@ -// Copyright (C) 2014 Yasuhiro Matsumoto . -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. -// +build vtable - -package sqlite3 - -/* -#cgo CFLAGS: -std=gnu99 -#cgo CFLAGS: -DSQLITE_ENABLE_RTREE -DSQLITE_THREADSAFE -#cgo CFLAGS: -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_FTS4_UNICODE61 -#cgo CFLAGS: -DSQLITE_TRACE_SIZE_LIMIT=15 -#cgo CFLAGS: -DSQLITE_ENABLE_COLUMN_METADATA=1 -#cgo CFLAGS: -Wno-deprecated-declarations - -#ifndef USE_LIBSQLITE3 -#include -#else -#include -#endif -#include -#include -#include - -static inline char *_sqlite3_mprintf(char *zFormat, char *arg) { - return sqlite3_mprintf(zFormat, arg); -} - -typedef struct goVTab goVTab; - -struct goVTab { - sqlite3_vtab base; - void *vTab; -}; - -uintptr_t goMInit(void *db, void *pAux, int argc, char **argv, char **pzErr, int isCreate); - -static int cXInit(sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char **pzErr, int isCreate) { - void *vTab = (void *)goMInit(db, pAux, argc, (char**)argv, pzErr, isCreate); - if (!vTab || *pzErr) { - return SQLITE_ERROR; - } - goVTab *pvTab = (goVTab *)sqlite3_malloc(sizeof(goVTab)); - if (!pvTab) { - *pzErr = sqlite3_mprintf("%s", "Out of memory"); - return SQLITE_NOMEM; - } - memset(pvTab, 0, sizeof(goVTab)); - pvTab->vTab = vTab; - - *ppVTab = (sqlite3_vtab *)pvTab; - *pzErr = 0; - return SQLITE_OK; -} - -static inline int cXCreate(sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char **pzErr) { - return cXInit(db, pAux, argc, argv, ppVTab, pzErr, 1); -} -static inline int cXConnect(sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char **pzErr) { - return cXInit(db, pAux, argc, argv, ppVTab, pzErr, 0); -} - -char* goVBestIndex(void *pVTab, void *icp); - -static inline int cXBestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *info) { - char *pzErr = goVBestIndex(((goVTab*)pVTab)->vTab, info); - if (pzErr) { - if (pVTab->zErrMsg) - sqlite3_free(pVTab->zErrMsg); - pVTab->zErrMsg = pzErr; - return SQLITE_ERROR; - } - return SQLITE_OK; -} - -char* goVRelease(void *pVTab, int isDestroy); - -static int cXRelease(sqlite3_vtab *pVTab, int isDestroy) { - char *pzErr = goVRelease(((goVTab*)pVTab)->vTab, isDestroy); - if (pzErr) { - if (pVTab->zErrMsg) - sqlite3_free(pVTab->zErrMsg); - pVTab->zErrMsg = pzErr; - return SQLITE_ERROR; - } - if (pVTab->zErrMsg) - sqlite3_free(pVTab->zErrMsg); - sqlite3_free(pVTab); - return SQLITE_OK; -} - -static inline int cXDisconnect(sqlite3_vtab *pVTab) { - return cXRelease(pVTab, 0); -} -static inline int cXDestroy(sqlite3_vtab *pVTab) { - return cXRelease(pVTab, 1); -} - -typedef struct goVTabCursor goVTabCursor; - -struct goVTabCursor { - sqlite3_vtab_cursor base; - void *vTabCursor; -}; - -uintptr_t goVOpen(void *pVTab, char **pzErr); - -static int cXOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) { - void *vTabCursor = (void *)goVOpen(((goVTab*)pVTab)->vTab, &(pVTab->zErrMsg)); - goVTabCursor *pCursor = (goVTabCursor *)sqlite3_malloc(sizeof(goVTabCursor)); - if (!pCursor) { - return SQLITE_NOMEM; - } - memset(pCursor, 0, sizeof(goVTabCursor)); - pCursor->vTabCursor = vTabCursor; - *ppCursor = (sqlite3_vtab_cursor *)pCursor; - return SQLITE_OK; -} - -static int setErrMsg(sqlite3_vtab_cursor *pCursor, char *pzErr) { - if (pCursor->pVtab->zErrMsg) - sqlite3_free(pCursor->pVtab->zErrMsg); - pCursor->pVtab->zErrMsg = pzErr; - return SQLITE_ERROR; -} - -char* goVClose(void *pCursor); - -static int cXClose(sqlite3_vtab_cursor *pCursor) { - char *pzErr = goVClose(((goVTabCursor*)pCursor)->vTabCursor); - if (pzErr) { - return setErrMsg(pCursor, pzErr); - } - sqlite3_free(pCursor); - return SQLITE_OK; -} - -char* goVFilter(void *pCursor, int idxNum, char* idxName, int argc, sqlite3_value **argv); - -static int cXFilter(sqlite3_vtab_cursor *pCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) { - char *pzErr = goVFilter(((goVTabCursor*)pCursor)->vTabCursor, idxNum, (char*)idxStr, argc, argv); - if (pzErr) { - return setErrMsg(pCursor, pzErr); - } - return SQLITE_OK; -} - -char* goVNext(void *pCursor); - -static int cXNext(sqlite3_vtab_cursor *pCursor) { - char *pzErr = goVNext(((goVTabCursor*)pCursor)->vTabCursor); - if (pzErr) { - return setErrMsg(pCursor, pzErr); - } - return SQLITE_OK; -} - -int goVEof(void *pCursor); - -static inline int cXEof(sqlite3_vtab_cursor *pCursor) { - return goVEof(((goVTabCursor*)pCursor)->vTabCursor); -} - -char* goVColumn(void *pCursor, void *cp, int col); - -static int cXColumn(sqlite3_vtab_cursor *pCursor, sqlite3_context *ctx, int i) { - char *pzErr = goVColumn(((goVTabCursor*)pCursor)->vTabCursor, ctx, i); - if (pzErr) { - return setErrMsg(pCursor, pzErr); - } - return SQLITE_OK; -} - -char* goVRowid(void *pCursor, sqlite3_int64 *pRowid); - -static int cXRowid(sqlite3_vtab_cursor *pCursor, sqlite3_int64 *pRowid) { - char *pzErr = goVRowid(((goVTabCursor*)pCursor)->vTabCursor, pRowid); - if (pzErr) { - return setErrMsg(pCursor, pzErr); - } - return SQLITE_OK; -} - -char* goVUpdate(void *pVTab, int argc, sqlite3_value **argv, sqlite3_int64 *pRowid); - -static int cXUpdate(sqlite3_vtab *pVTab, int argc, sqlite3_value **argv, sqlite3_int64 *pRowid) { - char *pzErr = goVUpdate(((goVTab*)pVTab)->vTab, argc, argv, pRowid); - if (pzErr) { - if (pVTab->zErrMsg) - sqlite3_free(pVTab->zErrMsg); - pVTab->zErrMsg = pzErr; - return SQLITE_ERROR; - } - return SQLITE_OK; -} - -static sqlite3_module goModule = { - 0, // iVersion - cXCreate, // xCreate - create a table - cXConnect, // xConnect - connect to an existing table - cXBestIndex, // xBestIndex - Determine search strategy - cXDisconnect, // xDisconnect - Disconnect from a table - cXDestroy, // xDestroy - Drop a table - cXOpen, // xOpen - open a cursor - cXClose, // xClose - close a cursor - cXFilter, // xFilter - configure scan constraints - cXNext, // xNext - advance a cursor - cXEof, // xEof - cXColumn, // xColumn - read data - cXRowid, // xRowid - read data - cXUpdate, // xUpdate - write data -// Not implemented - 0, // xBegin - begin transaction - 0, // xSync - sync transaction - 0, // xCommit - commit transaction - 0, // xRollback - rollback transaction - 0, // xFindFunction - function overloading - 0, // xRename - rename the table - 0, // xSavepoint - 0, // xRelease - 0 // xRollbackTo -}; - -void goMDestroy(void*); - -static int _sqlite3_create_module(sqlite3 *db, const char *zName, uintptr_t pClientData) { - return sqlite3_create_module_v2(db, zName, &goModule, (void*) pClientData, goMDestroy); -} -*/ -import "C" - -import ( - "fmt" - "math" - "reflect" - "unsafe" -) - -type sqliteModule struct { - c *SQLiteConn - name string - module Module -} - -type sqliteVTab struct { - module *sqliteModule - vTab VTab -} - -type sqliteVTabCursor struct { - vTab *sqliteVTab - vTabCursor VTabCursor -} - -// Op is type of operations. -type Op uint8 - -// Op mean identity of operations. -const ( - OpEQ Op = 2 - OpGT = 4 - OpLE = 8 - OpLT = 16 - OpGE = 32 - OpMATCH = 64 - OpLIKE = 65 /* 3.10.0 and later only */ - OpGLOB = 66 /* 3.10.0 and later only */ - OpREGEXP = 67 /* 3.10.0 and later only */ - OpScanUnique = 1 /* Scan visits at most 1 row */ -) - -// InfoConstraint give information of constraint. -type InfoConstraint struct { - Column int - Op Op - Usable bool -} - -// InfoOrderBy give information of order-by. -type InfoOrderBy struct { - Column int - Desc bool -} - -func constraints(info *C.sqlite3_index_info) []InfoConstraint { - l := info.nConstraint - slice := (*[1 << 30]C.struct_sqlite3_index_constraint)(unsafe.Pointer(info.aConstraint))[:l:l] - - cst := make([]InfoConstraint, 0, l) - for _, c := range slice { - var usable bool - if c.usable > 0 { - usable = true - } - cst = append(cst, InfoConstraint{ - Column: int(c.iColumn), - Op: Op(c.op), - Usable: usable, - }) - } - return cst -} - -func orderBys(info *C.sqlite3_index_info) []InfoOrderBy { - l := info.nOrderBy - slice := (*[1 << 30]C.struct_sqlite3_index_orderby)(unsafe.Pointer(info.aOrderBy))[:l:l] - - ob := make([]InfoOrderBy, 0, l) - for _, c := range slice { - var desc bool - if c.desc > 0 { - desc = true - } - ob = append(ob, InfoOrderBy{ - Column: int(c.iColumn), - Desc: desc, - }) - } - return ob -} - -// IndexResult is a Go struct representation of what eventually ends up in the -// output fields for `sqlite3_index_info` -// See: https://www.sqlite.org/c3ref/index_info.html -type IndexResult struct { - Used []bool // aConstraintUsage - IdxNum int - IdxStr string - AlreadyOrdered bool // orderByConsumed - EstimatedCost float64 - EstimatedRows float64 -} - -// mPrintf is a utility wrapper around sqlite3_mprintf -func mPrintf(format, arg string) *C.char { - cf := C.CString(format) - defer C.free(unsafe.Pointer(cf)) - ca := C.CString(arg) - defer C.free(unsafe.Pointer(ca)) - return C._sqlite3_mprintf(cf, ca) -} - -//export goMInit -func goMInit(db, pClientData unsafe.Pointer, argc C.int, argv **C.char, pzErr **C.char, isCreate C.int) C.uintptr_t { - m := lookupHandle(uintptr(pClientData)).(*sqliteModule) - if m.c.db != (*C.sqlite3)(db) { - *pzErr = mPrintf("%s", "Inconsistent db handles") - return 0 - } - args := make([]string, argc) - var A []*C.char - slice := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(argv)), Len: int(argc), Cap: int(argc)} - a := reflect.NewAt(reflect.TypeOf(A), unsafe.Pointer(&slice)).Elem().Interface() - for i, s := range a.([]*C.char) { - args[i] = C.GoString(s) - } - var vTab VTab - var err error - if isCreate == 1 { - vTab, err = m.module.Create(m.c, args) - } else { - vTab, err = m.module.Connect(m.c, args) - } - - if err != nil { - *pzErr = mPrintf("%s", err.Error()) - return 0 - } - vt := sqliteVTab{m, vTab} - *pzErr = nil - return C.uintptr_t(newHandle(m.c, &vt)) -} - -//export goVRelease -func goVRelease(pVTab unsafe.Pointer, isDestroy C.int) *C.char { - vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab) - var err error - if isDestroy == 1 { - err = vt.vTab.Destroy() - } else { - err = vt.vTab.Disconnect() - } - if err != nil { - return mPrintf("%s", err.Error()) - } - return nil -} - -//export goVOpen -func goVOpen(pVTab unsafe.Pointer, pzErr **C.char) C.uintptr_t { - vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab) - vTabCursor, err := vt.vTab.Open() - if err != nil { - *pzErr = mPrintf("%s", err.Error()) - return 0 - } - vtc := sqliteVTabCursor{vt, vTabCursor} - *pzErr = nil - return C.uintptr_t(newHandle(vt.module.c, &vtc)) -} - -//export goVBestIndex -func goVBestIndex(pVTab unsafe.Pointer, icp unsafe.Pointer) *C.char { - vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab) - info := (*C.sqlite3_index_info)(icp) - csts := constraints(info) - res, err := vt.vTab.BestIndex(csts, orderBys(info)) - if err != nil { - return mPrintf("%s", err.Error()) - } - if len(res.Used) != len(csts) { - return mPrintf("Result.Used != expected value", "") - } - - // Get a pointer to constraint_usage struct so we can update in place. - l := info.nConstraint - s := (*[1 << 30]C.struct_sqlite3_index_constraint_usage)(unsafe.Pointer(info.aConstraintUsage))[:l:l] - index := 1 - for i := C.int(0); i < info.nConstraint; i++ { - if res.Used[i] { - s[i].argvIndex = C.int(index) - s[i].omit = C.uchar(1) - index++ - } - } - - info.idxNum = C.int(res.IdxNum) - idxStr := C.CString(res.IdxStr) - defer C.free(unsafe.Pointer(idxStr)) - info.idxStr = idxStr - info.needToFreeIdxStr = C.int(0) - if res.AlreadyOrdered { - info.orderByConsumed = C.int(1) - } - info.estimatedCost = C.double(res.EstimatedCost) - info.estimatedRows = C.sqlite3_int64(res.EstimatedRows) - - return nil -} - -//export goVClose -func goVClose(pCursor unsafe.Pointer) *C.char { - vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor) - err := vtc.vTabCursor.Close() - if err != nil { - return mPrintf("%s", err.Error()) - } - return nil -} - -//export goMDestroy -func goMDestroy(pClientData unsafe.Pointer) { - m := lookupHandle(uintptr(pClientData)).(*sqliteModule) - m.module.DestroyModule() -} - -//export goVFilter -func goVFilter(pCursor unsafe.Pointer, idxNum C.int, idxName *C.char, argc C.int, argv **C.sqlite3_value) *C.char { - vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor) - args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc] - vals := make([]interface{}, 0, argc) - for _, v := range args { - conv, err := callbackArgGeneric(v) - if err != nil { - return mPrintf("%s", err.Error()) - } - vals = append(vals, conv.Interface()) - } - err := vtc.vTabCursor.Filter(int(idxNum), C.GoString(idxName), vals) - if err != nil { - return mPrintf("%s", err.Error()) - } - return nil -} - -//export goVNext -func goVNext(pCursor unsafe.Pointer) *C.char { - vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor) - err := vtc.vTabCursor.Next() - if err != nil { - return mPrintf("%s", err.Error()) - } - return nil -} - -//export goVEof -func goVEof(pCursor unsafe.Pointer) C.int { - vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor) - err := vtc.vTabCursor.EOF() - if err { - return 1 - } - return 0 -} - -//export goVColumn -func goVColumn(pCursor, cp unsafe.Pointer, col C.int) *C.char { - vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor) - c := (*SQLiteContext)(cp) - err := vtc.vTabCursor.Column(c, int(col)) - if err != nil { - return mPrintf("%s", err.Error()) - } - return nil -} - -//export goVRowid -func goVRowid(pCursor unsafe.Pointer, pRowid *C.sqlite3_int64) *C.char { - vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor) - rowid, err := vtc.vTabCursor.Rowid() - if err != nil { - return mPrintf("%s", err.Error()) - } - *pRowid = C.sqlite3_int64(rowid) - return nil -} - -//export goVUpdate -func goVUpdate(pVTab unsafe.Pointer, argc C.int, argv **C.sqlite3_value, pRowid *C.sqlite3_int64) *C.char { - vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab) - - var tname string - if n, ok := vt.vTab.(interface { - TableName() string - }); ok { - tname = n.TableName() + " " - } - - err := fmt.Errorf("virtual %s table %sis read-only", vt.module.name, tname) - if v, ok := vt.vTab.(VTabUpdater); ok { - // convert argv - args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc] - vals := make([]interface{}, 0, argc) - for _, v := range args { - conv, err := callbackArgGeneric(v) - if err != nil { - return mPrintf("%s", err.Error()) - } - - // work around for SQLITE_NULL - x := conv.Interface() - if z, ok := x.([]byte); ok && z == nil { - x = nil - } - - vals = append(vals, x) - } - - switch { - case argc == 1: - err = v.Delete(vals[0]) - - case argc > 1 && vals[0] == nil: - var id int64 - id, err = v.Insert(vals[1], vals[2:]) - if err == nil { - *pRowid = C.sqlite3_int64(id) - } - - case argc > 1: - err = v.Update(vals[1], vals[2:]) - } - } - - if err != nil { - return mPrintf("%s", err.Error()) - } - - return nil -} - -// Module is a "virtual table module", it defines the implementation of a -// virtual tables. See: http://sqlite.org/c3ref/module.html -type Module interface { - // http://sqlite.org/vtab.html#xcreate - Create(c *SQLiteConn, args []string) (VTab, error) - // http://sqlite.org/vtab.html#xconnect - Connect(c *SQLiteConn, args []string) (VTab, error) - // http://sqlite.org/c3ref/create_module.html - DestroyModule() -} - -// VTab describes a particular instance of the virtual table. -// See: http://sqlite.org/c3ref/vtab.html -type VTab interface { - // http://sqlite.org/vtab.html#xbestindex - BestIndex([]InfoConstraint, []InfoOrderBy) (*IndexResult, error) - // http://sqlite.org/vtab.html#xdisconnect - Disconnect() error - // http://sqlite.org/vtab.html#sqlite3_module.xDestroy - Destroy() error - // http://sqlite.org/vtab.html#xopen - Open() (VTabCursor, error) -} - -// VTabUpdater is a type that allows a VTab to be inserted, updated, or -// deleted. -// See: https://sqlite.org/vtab.html#xupdate -type VTabUpdater interface { - Delete(interface{}) error - Insert(interface{}, []interface{}) (int64, error) - Update(interface{}, []interface{}) error -} - -// VTabCursor describes cursors that point into the virtual table and are used -// to loop through the virtual table. See: http://sqlite.org/c3ref/vtab_cursor.html -type VTabCursor interface { - // http://sqlite.org/vtab.html#xclose - Close() error - // http://sqlite.org/vtab.html#xfilter - Filter(idxNum int, idxStr string, vals []interface{}) error - // http://sqlite.org/vtab.html#xnext - Next() error - // http://sqlite.org/vtab.html#xeof - EOF() bool - // http://sqlite.org/vtab.html#xcolumn - Column(c *SQLiteContext, col int) error - // http://sqlite.org/vtab.html#xrowid - Rowid() (int64, error) -} - -// DeclareVTab declares the Schema of a virtual table. -// See: http://sqlite.org/c3ref/declare_vtab.html -func (c *SQLiteConn) DeclareVTab(sql string) error { - zSQL := C.CString(sql) - defer C.free(unsafe.Pointer(zSQL)) - rv := C.sqlite3_declare_vtab(c.db, zSQL) - if rv != C.SQLITE_OK { - return c.lastError() - } - return nil -} - -// CreateModule registers a virtual table implementation. -// See: http://sqlite.org/c3ref/create_module.html -func (c *SQLiteConn) CreateModule(moduleName string, module Module) error { - mname := C.CString(moduleName) - defer C.free(unsafe.Pointer(mname)) - udm := sqliteModule{c, moduleName, module} - rv := C._sqlite3_create_module(c.db, mname, C.uintptr_t(newHandle(c, &udm))) - if rv != C.SQLITE_OK { - return c.lastError() - } - return nil -} diff --git a/sqlite3_vtable_test.go b/sqlite3_vtable_test.go deleted file mode 100644 index ce6a48c..0000000 --- a/sqlite3_vtable_test.go +++ /dev/null @@ -1,485 +0,0 @@ -// Copyright (C) 2014 Yasuhiro Matsumoto . -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. -// +build vtable - -package sqlite3 - -import ( - "database/sql" - "errors" - "fmt" - "os" - "reflect" - "strings" - "testing" -) - -type testModule struct { - t *testing.T - intarray []int -} - -type testVTab struct { - intarray []int -} - -type testVTabCursor struct { - vTab *testVTab - index int -} - -func (m testModule) Create(c *SQLiteConn, args []string) (VTab, error) { - if len(args) != 6 { - m.t.Fatal("six arguments expected") - } - if args[0] != "test" { - m.t.Fatal("module name") - } - if args[1] != "main" { - m.t.Fatal("db name") - } - if args[2] != "vtab" { - m.t.Fatal("table name") - } - if args[3] != "'1'" { - m.t.Fatal("first arg") - } - if args[4] != "2" { - m.t.Fatal("second arg") - } - if args[5] != "three" { - m.t.Fatal("third argsecond arg") - } - err := c.DeclareVTab("CREATE TABLE x(test TEXT)") - if err != nil { - return nil, err - } - return &testVTab{m.intarray}, nil -} - -func (m testModule) Connect(c *SQLiteConn, args []string) (VTab, error) { - return m.Create(c, args) -} - -func (m testModule) DestroyModule() {} - -func (v *testVTab) BestIndex(cst []InfoConstraint, ob []InfoOrderBy) (*IndexResult, error) { - used := make([]bool, 0, len(cst)) - for range cst { - used = append(used, false) - } - return &IndexResult{ - Used: used, - IdxNum: 0, - IdxStr: "test-index", - AlreadyOrdered: true, - EstimatedCost: 100, - EstimatedRows: 200, - }, nil -} - -func (v *testVTab) Disconnect() error { - return nil -} - -func (v *testVTab) Destroy() error { - return nil -} - -func (v *testVTab) Open() (VTabCursor, error) { - return &testVTabCursor{v, 0}, nil -} - -func (vc *testVTabCursor) Close() error { - return nil -} - -func (vc *testVTabCursor) Filter(idxNum int, idxStr string, vals []interface{}) error { - vc.index = 0 - return nil -} - -func (vc *testVTabCursor) Next() error { - vc.index++ - return nil -} - -func (vc *testVTabCursor) EOF() bool { - return vc.index >= len(vc.vTab.intarray) -} - -func (vc *testVTabCursor) Column(c *SQLiteContext, col int) error { - if col != 0 { - return fmt.Errorf("column index out of bounds: %d", col) - } - c.ResultInt(vc.vTab.intarray[vc.index]) - return nil -} - -func (vc *testVTabCursor) Rowid() (int64, error) { - return int64(vc.index), nil -} - -func TestCreateModule(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - intarray := []int{1, 2, 3} - sql.Register("sqlite3_TestCreateModule", &SQLiteDriver{ - ConnectHook: func(conn *SQLiteConn) error { - return conn.CreateModule("test", testModule{t, intarray}) - }, - }) - db, err := sql.Open("sqlite3_TestCreateModule", tempFilename) - if err != nil { - t.Fatalf("could not open db: %v", err) - } - _, err = db.Exec("CREATE VIRTUAL TABLE vtab USING test('1', 2, three)") - if err != nil { - t.Fatalf("could not create vtable: %v", err) - } - - var i, value int - rows, err := db.Query("SELECT rowid, * FROM vtab WHERE test = '3'") - if err != nil { - t.Fatalf("couldn't select from virtual table: %v", err) - } - for rows.Next() { - rows.Scan(&i, &value) - if intarray[i] != value { - t.Fatalf("want %v but %v", intarray[i], value) - } - } - - _, err = db.Exec("DROP TABLE vtab") - if err != nil { - t.Fatalf("couldn't drop virtual table: %v", err) - } -} - -func TestVUpdate(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - - // create module - updateMod := &vtabUpdateModule{t, make(map[string]*vtabUpdateTable)} - - // register module - sql.Register("sqlite3_TestVUpdate", &SQLiteDriver{ - ConnectHook: func(conn *SQLiteConn) error { - return conn.CreateModule("updatetest", updateMod) - }, - }) - - // connect - db, err := sql.Open("sqlite3_TestVUpdate", tempFilename) - if err != nil { - t.Fatalf("could not open db: %v", err) - } - - // create test table - _, err = db.Exec(`CREATE VIRTUAL TABLE vt USING updatetest(f1 integer, f2 text, f3 text)`) - if err != nil { - t.Fatalf("could not create updatetest vtable vt, got: %v", err) - } - - // check that table is defined properly - if len(updateMod.tables) != 1 { - t.Fatalf("expected exactly 1 table to exist, got: %d", len(updateMod.tables)) - } - if _, ok := updateMod.tables["vt"]; !ok { - t.Fatalf("expected table `vt` to exist in tables") - } - - // check nothing in updatetest - rows, err := db.Query(`select * from vt`) - if err != nil { - t.Fatalf("could not query vt, got: %v", err) - } - i, err := getRowCount(rows) - if err != nil { - t.Fatalf("expected no error, got: %v", err) - } - if i != 0 { - t.Fatalf("expected no rows in vt, got: %d", i) - } - - _, err = db.Exec(`delete from vt where f1 = 'yes'`) - if err != nil { - t.Fatalf("expected error on delete, got nil") - } - - // test bad column name - _, err = db.Exec(`insert into vt (f4) values('a')`) - if err == nil { - t.Fatalf("expected error on insert, got nil") - } - - // insert to vt - res, err := db.Exec(`insert into vt (f1, f2, f3) values (115, 'b', 'c'), (116, 'd', 'e')`) - if err != nil { - t.Fatalf("expected no error on insert, got: %v", err) - } - n, err := res.RowsAffected() - if err != nil { - t.Fatalf("expected no error, got: %v", err) - } - if n != 2 { - t.Fatalf("expected 1 row affected, got: %d", n) - } - - // check vt table - vt := updateMod.tables["vt"] - if len(vt.data) != 2 { - t.Fatalf("expected table vt to have exactly 2 rows, got: %d", len(vt.data)) - } - if !reflect.DeepEqual(vt.data[0], []interface{}{int64(115), "b", "c"}) { - t.Fatalf("expected table vt entry 0 to be [115 b c], instead: %v", vt.data[0]) - } - if !reflect.DeepEqual(vt.data[1], []interface{}{int64(116), "d", "e"}) { - t.Fatalf("expected table vt entry 1 to be [116 d e], instead: %v", vt.data[1]) - } - - // query vt - var f1 int - var f2, f3 string - err = db.QueryRow(`select * from vt where f1 = 115`).Scan(&f1, &f2, &f3) - if err != nil { - t.Fatalf("expected no error on vt query, got: %v", err) - } - - // check column values - if f1 != 115 || f2 != "b" || f3 != "c" { - t.Errorf("expected f1==115, f2==b, f3==c, got: %d, %q, %q", f1, f2, f3) - } - - // update vt - res, err = db.Exec(`update vt set f1=117, f2='f' where f3='e'`) - if err != nil { - t.Fatalf("expected no error, got: %v", err) - } - n, err = res.RowsAffected() - if err != nil { - t.Fatalf("expected no error, got: %v", err) - } - if n != 1 { - t.Fatalf("expected exactly one row updated, got: %d", n) - } - - // check vt table - if len(vt.data) != 2 { - t.Fatalf("expected table vt to have exactly 2 rows, got: %d", len(vt.data)) - } - if !reflect.DeepEqual(vt.data[0], []interface{}{int64(115), "b", "c"}) { - t.Fatalf("expected table vt entry 0 to be [115 b c], instead: %v", vt.data[0]) - } - if !reflect.DeepEqual(vt.data[1], []interface{}{int64(117), "f", "e"}) { - t.Fatalf("expected table vt entry 1 to be [117 f e], instead: %v", vt.data[1]) - } - - // delete from vt - res, err = db.Exec(`delete from vt where f1 = 117`) - if err != nil { - t.Fatalf("expected no error, got: %v", err) - } - n, err = res.RowsAffected() - if err != nil { - t.Fatalf("expected no error, got: %v", err) - } - if n != 1 { - t.Fatalf("expected exactly one row deleted, got: %d", n) - } - - // check vt table - if len(vt.data) != 1 { - t.Fatalf("expected table vt to have exactly 1 row, got: %d", len(vt.data)) - } - if !reflect.DeepEqual(vt.data[0], []interface{}{int64(115), "b", "c"}) { - t.Fatalf("expected table vt entry 0 to be [115 b c], instead: %v", vt.data[0]) - } - - // check updatetest has 1 result - rows, err = db.Query(`select * from vt`) - if err != nil { - t.Fatalf("could not query vt, got: %v", err) - } - i, err = getRowCount(rows) - if err != nil { - t.Fatalf("expected no error, got: %v", err) - } - if i != 1 { - t.Fatalf("expected 1 row in vt, got: %d", i) - } -} - -func getRowCount(rows *sql.Rows) (int, error) { - var i int - for rows.Next() { - i++ - } - return i, nil -} - -type vtabUpdateModule struct { - t *testing.T - tables map[string]*vtabUpdateTable -} - -func (m *vtabUpdateModule) Create(c *SQLiteConn, args []string) (VTab, error) { - if len(args) < 2 { - return nil, errors.New("must declare at least one column") - } - - // get database name, table name, and column declarations ... - dbname, tname, decls := args[1], args[2], args[3:] - - // extract column names + types from parameters declarations - cols, typs := make([]string, len(decls)), make([]string, len(decls)) - for i := 0; i < len(decls); i++ { - n, typ := decls[i], "" - if j := strings.IndexAny(n, " \t\n"); j != -1 { - typ, n = strings.TrimSpace(n[j+1:]), n[:j] - } - cols[i], typs[i] = n, typ - } - - // declare table - err := c.DeclareVTab(fmt.Sprintf(`CREATE TABLE "%s"."%s" (%s)`, dbname, tname, strings.Join(decls, ","))) - if err != nil { - return nil, err - } - - // create table - vtab := &vtabUpdateTable{m.t, dbname, tname, cols, typs, make([][]interface{}, 0)} - m.tables[tname] = vtab - return vtab, nil -} - -func (m *vtabUpdateModule) Connect(c *SQLiteConn, args []string) (VTab, error) { - return m.Create(c, args) -} - -func (m *vtabUpdateModule) DestroyModule() {} - -type vtabUpdateTable struct { - t *testing.T - db string - name string - cols []string - typs []string - data [][]interface{} -} - -func (t *vtabUpdateTable) Open() (VTabCursor, error) { - return &vtabUpdateCursor{t, 0}, nil -} - -func (t *vtabUpdateTable) BestIndex(cst []InfoConstraint, ob []InfoOrderBy) (*IndexResult, error) { - return &IndexResult{Used: make([]bool, len(cst))}, nil -} - -func (t *vtabUpdateTable) Disconnect() error { - return nil -} - -func (t *vtabUpdateTable) Destroy() error { - return nil -} - -func (t *vtabUpdateTable) Insert(id interface{}, vals []interface{}) (int64, error) { - var i int64 - if id == nil { - i, t.data = int64(len(t.data)), append(t.data, vals) - return i, nil - } - - var ok bool - i, ok = id.(int64) - if !ok { - return 0, fmt.Errorf("id is invalid type: %T", id) - } - - t.data[i] = vals - - return i, nil -} - -func (t *vtabUpdateTable) Update(id interface{}, vals []interface{}) error { - i, ok := id.(int64) - if !ok { - return fmt.Errorf("id is invalid type: %T", id) - } - - if int(i) >= len(t.data) || i < 0 { - return fmt.Errorf("invalid row id %d", i) - } - - t.data[int(i)] = vals - - return nil -} - -func (t *vtabUpdateTable) Delete(id interface{}) error { - i, ok := id.(int64) - if !ok { - return fmt.Errorf("id is invalid type: %T", id) - } - - if int(i) >= len(t.data) || i < 0 { - return fmt.Errorf("invalid row id %d", i) - } - - t.data = append(t.data[:i], t.data[i+1:]...) - - return nil -} - -type vtabUpdateCursor struct { - t *vtabUpdateTable - i int -} - -func (c *vtabUpdateCursor) Column(ctxt *SQLiteContext, col int) error { - switch x := c.t.data[c.i][col].(type) { - case []byte: - ctxt.ResultBlob(x) - case bool: - ctxt.ResultBool(x) - case float64: - ctxt.ResultDouble(x) - case int: - ctxt.ResultInt(x) - case int64: - ctxt.ResultInt64(x) - case nil: - ctxt.ResultNull() - case string: - ctxt.ResultText(x) - default: - ctxt.ResultText(fmt.Sprintf("%v", x)) - } - - return nil -} - -func (c *vtabUpdateCursor) Filter(ixNum int, ixName string, vals []interface{}) error { - return nil -} - -func (c *vtabUpdateCursor) Next() error { - c.i++ - return nil -} - -func (c *vtabUpdateCursor) EOF() bool { - return c.i >= len(c.t.data) -} - -func (c *vtabUpdateCursor) Rowid() (int64, error) { - return int64(c.i), nil -} - -func (c *vtabUpdateCursor) Close() error { - return nil -} -- cgit v1.2.3