Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

add support for defining an "eponymous only" virtual table #885

Merged
merged 3 commits into from Dec 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 33 additions & 0 deletions _example/vtable_eponymous_only/main.go
@@ -0,0 +1,33 @@
package main

import (
"database/sql"
"fmt"
"log"

"github.com/mattn/go-sqlite3"
)

func main() {
sql.Register("sqlite3_with_extensions", &sqlite3.SQLiteDriver{
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
return conn.CreateModule("series", &seriesModule{})
},
})
db, err := sql.Open("sqlite3_with_extensions", ":memory:")
if err != nil {
log.Fatal(err)
}
defer db.Close()

rows, err := db.Query("select * from series")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var value int
rows.Scan(&value)
fmt.Printf("value: %d\n", value)
}
}
118 changes: 118 additions & 0 deletions _example/vtable_eponymous_only/vtable.go
@@ -0,0 +1,118 @@
package main

import (
"fmt"

"github.com/mattn/go-sqlite3"
)

type seriesModule struct{}

func (m *seriesModule) EponymousOnlyModule() {}

func (m *seriesModule) Create(c *sqlite3.SQLiteConn, args []string) (sqlite3.VTab, error) {
err := c.DeclareVTab(fmt.Sprintf(`
CREATE TABLE %s (
value INT,
start HIDDEN,
stop HIDDEN,
step HIDDEN
)`, args[0]))
if err != nil {
return nil, err
}
return &seriesTable{0, 0, 1}, nil
}

func (m *seriesModule) Connect(c *sqlite3.SQLiteConn, args []string) (sqlite3.VTab, error) {
return m.Create(c, args)
}

func (m *seriesModule) DestroyModule() {}

type seriesTable struct {
start int64
stop int64
step int64
}

func (v *seriesTable) Open() (sqlite3.VTabCursor, error) {
return &seriesCursor{v, 0}, nil
}

func (v *seriesTable) BestIndex(csts []sqlite3.InfoConstraint, ob []sqlite3.InfoOrderBy) (*sqlite3.IndexResult, error) {
used := make([]bool, len(csts))
for c, cst := range csts {
if cst.Usable && cst.Op == sqlite3.OpEQ {
used[c] = true
}
}

return &sqlite3.IndexResult{
IdxNum: 0,
IdxStr: "default",
Used: used,
}, nil
}

func (v *seriesTable) Disconnect() error { return nil }
func (v *seriesTable) Destroy() error { return nil }

type seriesCursor struct {
*seriesTable
value int64
}

func (vc *seriesCursor) Column(c *sqlite3.SQLiteContext, col int) error {
switch col {
case 0:
c.ResultInt64(vc.value)
case 1:
c.ResultInt64(vc.seriesTable.start)
case 2:
c.ResultInt64(vc.seriesTable.stop)
case 3:
c.ResultInt64(vc.seriesTable.step)
}
return nil
}

func (vc *seriesCursor) Filter(idxNum int, idxStr string, vals []interface{}) error {
switch {
case len(vals) < 1:
vc.seriesTable.start = 0
vc.seriesTable.stop = 1000
vc.value = vc.seriesTable.start
case len(vals) < 2:
vc.seriesTable.start = vals[0].(int64)
vc.seriesTable.stop = 1000
vc.value = vc.seriesTable.start
case len(vals) < 3:
vc.seriesTable.start = vals[0].(int64)
vc.seriesTable.stop = vals[1].(int64)
vc.value = vc.seriesTable.start
case len(vals) < 4:
vc.seriesTable.start = vals[0].(int64)
vc.seriesTable.stop = vals[1].(int64)
vc.seriesTable.step = vals[2].(int64)
}

return nil
}

func (vc *seriesCursor) Next() error {
vc.value += vc.step
return nil
}

func (vc *seriesCursor) EOF() bool {
return vc.value > vc.stop
}

func (vc *seriesCursor) Rowid() (int64, error) {
return int64(vc.value), nil
}

func (vc *seriesCursor) Close() error {
return nil
}
55 changes: 52 additions & 3 deletions sqlite3_opt_vtable.go
Expand Up @@ -226,11 +226,43 @@ static sqlite3_module goModule = {
0 // xRollbackTo
};

// See https://sqlite.org/vtab.html#eponymous_only_virtual_tables
static sqlite3_module goModuleEponymousOnly = {
0, // iVersion
0, // xCreate - create a table, which here is null
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);
}

static int _sqlite3_create_module_eponymous_only(sqlite3 *db, const char *zName, uintptr_t pClientData) {
return sqlite3_create_module_v2(db, zName, &goModuleEponymousOnly, (void*) pClientData, goMDestroy);
}
*/
import "C"

Expand Down Expand Up @@ -595,6 +627,13 @@ type Module interface {
DestroyModule()
}

// EponymousOnlyModule is a "virtual table module" (as above), but
// for defining "eponymous only" virtual tables See: https://sqlite.org/vtab.html#eponymous_only_virtual_tables
type EponymousOnlyModule interface {
Module
EponymousOnlyModule()
}

// VTab describes a particular instance of the virtual table.
// See: http://sqlite.org/c3ref/vtab.html
type VTab interface {
Expand Down Expand Up @@ -652,9 +691,19 @@ 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(uintptr(newHandle(c, &udm))))
if rv != C.SQLITE_OK {
return c.lastError()
switch module.(type) {
case EponymousOnlyModule:
rv := C._sqlite3_create_module_eponymous_only(c.db, mname, C.uintptr_t(uintptr(newHandle(c, &udm))))
if rv != C.SQLITE_OK {
return c.lastError()
}
return nil
case Module:
rv := C._sqlite3_create_module(c.db, mname, C.uintptr_t(uintptr(newHandle(c, &udm))))
if rv != C.SQLITE_OK {
return c.lastError()
}
return nil
}
return nil
}
122 changes: 122 additions & 0 deletions sqlite3_opt_vtable_test.go
Expand Up @@ -484,3 +484,125 @@ func (c *vtabUpdateCursor) Rowid() (int64, error) {
func (c *vtabUpdateCursor) Close() error {
return nil
}

type testModuleEponymousOnly struct {
t *testing.T
intarray []int
}

type testVTabEponymousOnly struct {
intarray []int
}

type testVTabCursorEponymousOnly struct {
vTab *testVTabEponymousOnly
index int
}

func (m testModuleEponymousOnly) EponymousOnlyModule() {}

func (m testModuleEponymousOnly) Create(c *SQLiteConn, args []string) (VTab, error) {
err := c.DeclareVTab("CREATE TABLE x(test INT)")
if err != nil {
return nil, err
}
return &testVTabEponymousOnly{m.intarray}, nil
}

func (m testModuleEponymousOnly) Connect(c *SQLiteConn, args []string) (VTab, error) {
return m.Create(c, args)
}

func (m testModuleEponymousOnly) DestroyModule() {}

func (v *testVTabEponymousOnly) 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 *testVTabEponymousOnly) Disconnect() error {
return nil
}

func (v *testVTabEponymousOnly) Destroy() error {
return nil
}

func (v *testVTabEponymousOnly) Open() (VTabCursor, error) {
return &testVTabCursorEponymousOnly{v, 0}, nil
}

func (vc *testVTabCursorEponymousOnly) Close() error {
return nil
}

func (vc *testVTabCursorEponymousOnly) Filter(idxNum int, idxStr string, vals []interface{}) error {
vc.index = 0
return nil
}

func (vc *testVTabCursorEponymousOnly) Next() error {
vc.index++
return nil
}

func (vc *testVTabCursorEponymousOnly) EOF() bool {
return vc.index >= len(vc.vTab.intarray)
}

func (vc *testVTabCursorEponymousOnly) 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 *testVTabCursorEponymousOnly) Rowid() (int64, error) {
return int64(vc.index), nil
}

func TestCreateModuleEponymousOnly(t *testing.T) {
tempFilename := TempFilename(t)
defer os.Remove(tempFilename)
intarray := []int{1, 2, 3}
sql.Register("sqlite3_TestCreateModuleEponymousOnly", &SQLiteDriver{
ConnectHook: func(conn *SQLiteConn) error {
return conn.CreateModule("test", testModuleEponymousOnly{t, intarray})
},
})
db, err := sql.Open("sqlite3_TestCreateModuleEponymousOnly", tempFilename)
if err != nil {
t.Fatalf("could not open db: %v", err)
}

var i, value int
rows, err := db.Query("SELECT rowid, * FROM test")
if err != nil {
t.Fatalf("couldn't select from virtual table: %v", err)
}
for rows.Next() {
err := rows.Scan(&i, &value)
if err != nil {
t.Fatal(err)
}
if intarray[i] != value {
t.Fatalf("want %v but %v", intarray[i], value)
}
}

_, err = db.Exec("DROP TABLE test")
if err != nil {
t.Fatalf("couldn't drop virtual table: %v", err)
}
}