// Copyright (C) 2018 The Go-SQLite3 Authors. // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. // +build sqlite_vtable vtable package sqlite3 /* #cgo CFLAGS: -std=gnu99 #cgo CFLAGS: -DSQLITE_ENABLE_RTREE #cgo CFLAGS: -DSQLITE_THREADSAFE #cgo CFLAGS: -DSQLITE_ENABLE_FTS3 #cgo CFLAGS: -DSQLITE_ENABLE_FTS3_PARENTHESIS #cgo CFLAGS: -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 }