Skip to content

Commit 92d2371

Browse files
add support for defining an "eponymous only" virtual table (#885)
* add support for defining an "eponymous only" virtual table As suggested here: #846 (comment) * add an example of an eponymous only vtab module * add a test case for an eponymous only vtab module
1 parent 66ff625 commit 92d2371

File tree

4 files changed

+325
-3
lines changed

4 files changed

+325
-3
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package main
2+
3+
import (
4+
"database/sql"
5+
"fmt"
6+
"log"
7+
8+
"github.com/mattn/go-sqlite3"
9+
)
10+
11+
func main() {
12+
sql.Register("sqlite3_with_extensions", &sqlite3.SQLiteDriver{
13+
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
14+
return conn.CreateModule("series", &seriesModule{})
15+
},
16+
})
17+
db, err := sql.Open("sqlite3_with_extensions", ":memory:")
18+
if err != nil {
19+
log.Fatal(err)
20+
}
21+
defer db.Close()
22+
23+
rows, err := db.Query("select * from series")
24+
if err != nil {
25+
log.Fatal(err)
26+
}
27+
defer rows.Close()
28+
for rows.Next() {
29+
var value int
30+
rows.Scan(&value)
31+
fmt.Printf("value: %d\n", value)
32+
}
33+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/mattn/go-sqlite3"
7+
)
8+
9+
type seriesModule struct{}
10+
11+
func (m *seriesModule) EponymousOnlyModule() {}
12+
13+
func (m *seriesModule) Create(c *sqlite3.SQLiteConn, args []string) (sqlite3.VTab, error) {
14+
err := c.DeclareVTab(fmt.Sprintf(`
15+
CREATE TABLE %s (
16+
value INT,
17+
start HIDDEN,
18+
stop HIDDEN,
19+
step HIDDEN
20+
)`, args[0]))
21+
if err != nil {
22+
return nil, err
23+
}
24+
return &seriesTable{0, 0, 1}, nil
25+
}
26+
27+
func (m *seriesModule) Connect(c *sqlite3.SQLiteConn, args []string) (sqlite3.VTab, error) {
28+
return m.Create(c, args)
29+
}
30+
31+
func (m *seriesModule) DestroyModule() {}
32+
33+
type seriesTable struct {
34+
start int64
35+
stop int64
36+
step int64
37+
}
38+
39+
func (v *seriesTable) Open() (sqlite3.VTabCursor, error) {
40+
return &seriesCursor{v, 0}, nil
41+
}
42+
43+
func (v *seriesTable) BestIndex(csts []sqlite3.InfoConstraint, ob []sqlite3.InfoOrderBy) (*sqlite3.IndexResult, error) {
44+
used := make([]bool, len(csts))
45+
for c, cst := range csts {
46+
if cst.Usable && cst.Op == sqlite3.OpEQ {
47+
used[c] = true
48+
}
49+
}
50+
51+
return &sqlite3.IndexResult{
52+
IdxNum: 0,
53+
IdxStr: "default",
54+
Used: used,
55+
}, nil
56+
}
57+
58+
func (v *seriesTable) Disconnect() error { return nil }
59+
func (v *seriesTable) Destroy() error { return nil }
60+
61+
type seriesCursor struct {
62+
*seriesTable
63+
value int64
64+
}
65+
66+
func (vc *seriesCursor) Column(c *sqlite3.SQLiteContext, col int) error {
67+
switch col {
68+
case 0:
69+
c.ResultInt64(vc.value)
70+
case 1:
71+
c.ResultInt64(vc.seriesTable.start)
72+
case 2:
73+
c.ResultInt64(vc.seriesTable.stop)
74+
case 3:
75+
c.ResultInt64(vc.seriesTable.step)
76+
}
77+
return nil
78+
}
79+
80+
func (vc *seriesCursor) Filter(idxNum int, idxStr string, vals []interface{}) error {
81+
switch {
82+
case len(vals) < 1:
83+
vc.seriesTable.start = 0
84+
vc.seriesTable.stop = 1000
85+
vc.value = vc.seriesTable.start
86+
case len(vals) < 2:
87+
vc.seriesTable.start = vals[0].(int64)
88+
vc.seriesTable.stop = 1000
89+
vc.value = vc.seriesTable.start
90+
case len(vals) < 3:
91+
vc.seriesTable.start = vals[0].(int64)
92+
vc.seriesTable.stop = vals[1].(int64)
93+
vc.value = vc.seriesTable.start
94+
case len(vals) < 4:
95+
vc.seriesTable.start = vals[0].(int64)
96+
vc.seriesTable.stop = vals[1].(int64)
97+
vc.seriesTable.step = vals[2].(int64)
98+
}
99+
100+
return nil
101+
}
102+
103+
func (vc *seriesCursor) Next() error {
104+
vc.value += vc.step
105+
return nil
106+
}
107+
108+
func (vc *seriesCursor) EOF() bool {
109+
return vc.value > vc.stop
110+
}
111+
112+
func (vc *seriesCursor) Rowid() (int64, error) {
113+
return int64(vc.value), nil
114+
}
115+
116+
func (vc *seriesCursor) Close() error {
117+
return nil
118+
}

sqlite3_opt_vtable.go

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,43 @@ static sqlite3_module goModule = {
226226
0 // xRollbackTo
227227
};
228228
229+
// See https://sqlite.org/vtab.html#eponymous_only_virtual_tables
230+
static sqlite3_module goModuleEponymousOnly = {
231+
0, // iVersion
232+
0, // xCreate - create a table, which here is null
233+
cXConnect, // xConnect - connect to an existing table
234+
cXBestIndex, // xBestIndex - Determine search strategy
235+
cXDisconnect, // xDisconnect - Disconnect from a table
236+
cXDestroy, // xDestroy - Drop a table
237+
cXOpen, // xOpen - open a cursor
238+
cXClose, // xClose - close a cursor
239+
cXFilter, // xFilter - configure scan constraints
240+
cXNext, // xNext - advance a cursor
241+
cXEof, // xEof
242+
cXColumn, // xColumn - read data
243+
cXRowid, // xRowid - read data
244+
cXUpdate, // xUpdate - write data
245+
// Not implemented
246+
0, // xBegin - begin transaction
247+
0, // xSync - sync transaction
248+
0, // xCommit - commit transaction
249+
0, // xRollback - rollback transaction
250+
0, // xFindFunction - function overloading
251+
0, // xRename - rename the table
252+
0, // xSavepoint
253+
0, // xRelease
254+
0 // xRollbackTo
255+
};
256+
229257
void goMDestroy(void*);
230258
231259
static int _sqlite3_create_module(sqlite3 *db, const char *zName, uintptr_t pClientData) {
232260
return sqlite3_create_module_v2(db, zName, &goModule, (void*) pClientData, goMDestroy);
233261
}
262+
263+
static int _sqlite3_create_module_eponymous_only(sqlite3 *db, const char *zName, uintptr_t pClientData) {
264+
return sqlite3_create_module_v2(db, zName, &goModuleEponymousOnly, (void*) pClientData, goMDestroy);
265+
}
234266
*/
235267
import "C"
236268

@@ -595,6 +627,13 @@ type Module interface {
595627
DestroyModule()
596628
}
597629

630+
// EponymousOnlyModule is a "virtual table module" (as above), but
631+
// for defining "eponymous only" virtual tables See: https://sqlite.org/vtab.html#eponymous_only_virtual_tables
632+
type EponymousOnlyModule interface {
633+
Module
634+
EponymousOnlyModule()
635+
}
636+
598637
// VTab describes a particular instance of the virtual table.
599638
// See: http://sqlite.org/c3ref/vtab.html
600639
type VTab interface {
@@ -652,9 +691,19 @@ func (c *SQLiteConn) CreateModule(moduleName string, module Module) error {
652691
mname := C.CString(moduleName)
653692
defer C.free(unsafe.Pointer(mname))
654693
udm := sqliteModule{c, moduleName, module}
655-
rv := C._sqlite3_create_module(c.db, mname, C.uintptr_t(uintptr(newHandle(c, &udm))))
656-
if rv != C.SQLITE_OK {
657-
return c.lastError()
694+
switch module.(type) {
695+
case EponymousOnlyModule:
696+
rv := C._sqlite3_create_module_eponymous_only(c.db, mname, C.uintptr_t(uintptr(newHandle(c, &udm))))
697+
if rv != C.SQLITE_OK {
698+
return c.lastError()
699+
}
700+
return nil
701+
case Module:
702+
rv := C._sqlite3_create_module(c.db, mname, C.uintptr_t(uintptr(newHandle(c, &udm))))
703+
if rv != C.SQLITE_OK {
704+
return c.lastError()
705+
}
706+
return nil
658707
}
659708
return nil
660709
}

sqlite3_opt_vtable_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,3 +484,125 @@ func (c *vtabUpdateCursor) Rowid() (int64, error) {
484484
func (c *vtabUpdateCursor) Close() error {
485485
return nil
486486
}
487+
488+
type testModuleEponymousOnly struct {
489+
t *testing.T
490+
intarray []int
491+
}
492+
493+
type testVTabEponymousOnly struct {
494+
intarray []int
495+
}
496+
497+
type testVTabCursorEponymousOnly struct {
498+
vTab *testVTabEponymousOnly
499+
index int
500+
}
501+
502+
func (m testModuleEponymousOnly) EponymousOnlyModule() {}
503+
504+
func (m testModuleEponymousOnly) Create(c *SQLiteConn, args []string) (VTab, error) {
505+
err := c.DeclareVTab("CREATE TABLE x(test INT)")
506+
if err != nil {
507+
return nil, err
508+
}
509+
return &testVTabEponymousOnly{m.intarray}, nil
510+
}
511+
512+
func (m testModuleEponymousOnly) Connect(c *SQLiteConn, args []string) (VTab, error) {
513+
return m.Create(c, args)
514+
}
515+
516+
func (m testModuleEponymousOnly) DestroyModule() {}
517+
518+
func (v *testVTabEponymousOnly) BestIndex(cst []InfoConstraint, ob []InfoOrderBy) (*IndexResult, error) {
519+
used := make([]bool, 0, len(cst))
520+
for range cst {
521+
used = append(used, false)
522+
}
523+
return &IndexResult{
524+
Used: used,
525+
IdxNum: 0,
526+
IdxStr: "test-index",
527+
AlreadyOrdered: true,
528+
EstimatedCost: 100,
529+
EstimatedRows: 200,
530+
}, nil
531+
}
532+
533+
func (v *testVTabEponymousOnly) Disconnect() error {
534+
return nil
535+
}
536+
537+
func (v *testVTabEponymousOnly) Destroy() error {
538+
return nil
539+
}
540+
541+
func (v *testVTabEponymousOnly) Open() (VTabCursor, error) {
542+
return &testVTabCursorEponymousOnly{v, 0}, nil
543+
}
544+
545+
func (vc *testVTabCursorEponymousOnly) Close() error {
546+
return nil
547+
}
548+
549+
func (vc *testVTabCursorEponymousOnly) Filter(idxNum int, idxStr string, vals []interface{}) error {
550+
vc.index = 0
551+
return nil
552+
}
553+
554+
func (vc *testVTabCursorEponymousOnly) Next() error {
555+
vc.index++
556+
return nil
557+
}
558+
559+
func (vc *testVTabCursorEponymousOnly) EOF() bool {
560+
return vc.index >= len(vc.vTab.intarray)
561+
}
562+
563+
func (vc *testVTabCursorEponymousOnly) Column(c *SQLiteContext, col int) error {
564+
if col != 0 {
565+
return fmt.Errorf("column index out of bounds: %d", col)
566+
}
567+
c.ResultInt(vc.vTab.intarray[vc.index])
568+
return nil
569+
}
570+
571+
func (vc *testVTabCursorEponymousOnly) Rowid() (int64, error) {
572+
return int64(vc.index), nil
573+
}
574+
575+
func TestCreateModuleEponymousOnly(t *testing.T) {
576+
tempFilename := TempFilename(t)
577+
defer os.Remove(tempFilename)
578+
intarray := []int{1, 2, 3}
579+
sql.Register("sqlite3_TestCreateModuleEponymousOnly", &SQLiteDriver{
580+
ConnectHook: func(conn *SQLiteConn) error {
581+
return conn.CreateModule("test", testModuleEponymousOnly{t, intarray})
582+
},
583+
})
584+
db, err := sql.Open("sqlite3_TestCreateModuleEponymousOnly", tempFilename)
585+
if err != nil {
586+
t.Fatalf("could not open db: %v", err)
587+
}
588+
589+
var i, value int
590+
rows, err := db.Query("SELECT rowid, * FROM test")
591+
if err != nil {
592+
t.Fatalf("couldn't select from virtual table: %v", err)
593+
}
594+
for rows.Next() {
595+
err := rows.Scan(&i, &value)
596+
if err != nil {
597+
t.Fatal(err)
598+
}
599+
if intarray[i] != value {
600+
t.Fatalf("want %v but %v", intarray[i], value)
601+
}
602+
}
603+
604+
_, err = db.Exec("DROP TABLE test")
605+
if err != nil {
606+
t.Fatalf("couldn't drop virtual table: %v", err)
607+
}
608+
}

0 commit comments

Comments
 (0)