From d4d0d0c95346c7d81484ba519ca3e8dfa4328ecf Mon Sep 17 00:00:00 2001 From: theimpostor Date: Sun, 27 Apr 2025 12:52:21 -0500 Subject: [PATCH 1/2] Add columns used parameter to vtable BestIndex callback --- _example/vtable/vtable.go | 2 +- _example/vtable_eponymous_only/vtable.go | 2 +- sqlite3_opt_vtable.go | 4 ++-- sqlite3_opt_vtable_test.go | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/_example/vtable/vtable.go b/_example/vtable/vtable.go index c65535b7b..9aeb96f4f 100644 --- a/_example/vtable/vtable.go +++ b/_example/vtable/vtable.go @@ -62,7 +62,7 @@ func (v *ghRepoTable) Open() (sqlite3.VTabCursor, error) { return &ghRepoCursor{0, repos}, nil } -func (v *ghRepoTable) BestIndex(csts []sqlite3.InfoConstraint, ob []sqlite3.InfoOrderBy) (*sqlite3.IndexResult, error) { +func (v *ghRepoTable) BestIndex(csts []sqlite3.InfoConstraint, ob []sqlite3.InfoOrderBy, colsUsed uint64) (*sqlite3.IndexResult, error) { used := make([]bool, len(csts)) return &sqlite3.IndexResult{ IdxNum: 0, diff --git a/_example/vtable_eponymous_only/vtable.go b/_example/vtable_eponymous_only/vtable.go index 9f22ebccc..dd22868cc 100644 --- a/_example/vtable_eponymous_only/vtable.go +++ b/_example/vtable_eponymous_only/vtable.go @@ -40,7 +40,7 @@ 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) { +func (v *seriesTable) BestIndex(csts []sqlite3.InfoConstraint, ob []sqlite3.InfoOrderBy, colsUsed uint64) (*sqlite3.IndexResult, error) { used := make([]bool, len(csts)) for c, cst := range csts { if cst.Usable && cst.Op == sqlite3.OpEQ { diff --git a/sqlite3_opt_vtable.go b/sqlite3_opt_vtable.go index 9b164b3e0..5182ed656 100644 --- a/sqlite3_opt_vtable.go +++ b/sqlite3_opt_vtable.go @@ -448,7 +448,7 @@ func goVBestIndex(pVTab unsafe.Pointer, icp unsafe.Pointer) *C.char { vt := lookupHandle(pVTab).(*sqliteVTab) info := (*C.sqlite3_index_info)(icp) csts := constraints(info) - res, err := vt.vTab.BestIndex(csts, orderBys(info)) + res, err := vt.vTab.BestIndex(csts, orderBys(info), uint64(info.colUsed)) if err != nil { return mPrintf("%s", err.Error()) } @@ -650,7 +650,7 @@ type EponymousOnlyModule interface { // See: http://sqlite.org/c3ref/vtab.html type VTab interface { // http://sqlite.org/vtab.html#xbestindex - BestIndex([]InfoConstraint, []InfoOrderBy) (*IndexResult, error) + BestIndex([]InfoConstraint, []InfoOrderBy, uint64) (*IndexResult, error) // http://sqlite.org/vtab.html#xdisconnect Disconnect() error // http://sqlite.org/vtab.html#sqlite3_module.xDestroy diff --git a/sqlite3_opt_vtable_test.go b/sqlite3_opt_vtable_test.go index 64511e23d..ddd3cadea 100644 --- a/sqlite3_opt_vtable_test.go +++ b/sqlite3_opt_vtable_test.go @@ -67,7 +67,7 @@ func (m testModule) Connect(c *SQLiteConn, args []string) (VTab, error) { func (m testModule) DestroyModule() {} -func (v *testVTab) BestIndex(cst []InfoConstraint, ob []InfoOrderBy) (*IndexResult, error) { +func (v *testVTab) BestIndex(cst []InfoConstraint, ob []InfoOrderBy, colsUsed uint64) (*IndexResult, error) { used := make([]bool, 0, len(cst)) for range cst { used = append(used, false) @@ -377,7 +377,7 @@ func (t *vtabUpdateTable) Open() (VTabCursor, error) { return &vtabUpdateCursor{t, 0}, nil } -func (t *vtabUpdateTable) BestIndex(cst []InfoConstraint, ob []InfoOrderBy) (*IndexResult, error) { +func (t *vtabUpdateTable) BestIndex(cst []InfoConstraint, ob []InfoOrderBy, colsUsed uint64) (*IndexResult, error) { return &IndexResult{Used: make([]bool, len(cst))}, nil } @@ -516,7 +516,7 @@ func (m testModuleEponymousOnly) Connect(c *SQLiteConn, args []string) (VTab, er func (m testModuleEponymousOnly) DestroyModule() {} -func (v *testVTabEponymousOnly) BestIndex(cst []InfoConstraint, ob []InfoOrderBy) (*IndexResult, error) { +func (v *testVTabEponymousOnly) BestIndex(cst []InfoConstraint, ob []InfoOrderBy, colsUsed uint64) (*IndexResult, error) { used := make([]bool, 0, len(cst)) for range cst { used = append(used, false) From b28aa5e7f79a47f857a9c5eb87c20bd515f3af6b Mon Sep 17 00:00:00 2001 From: theimpostor Date: Wed, 18 Mar 2026 17:40:14 -0500 Subject: [PATCH 2/2] test: verify BestIndex colsUsed mask --- sqlite3_opt_vtable_test.go | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/sqlite3_opt_vtable_test.go b/sqlite3_opt_vtable_test.go index ddd3cadea..aadc15add 100644 --- a/sqlite3_opt_vtable_test.go +++ b/sqlite3_opt_vtable_test.go @@ -19,12 +19,14 @@ import ( ) type testModule struct { - t *testing.T - intarray []int + t *testing.T + intarray []int + bestIndexLog []uint64 } type testVTab struct { intarray []int + log *[]uint64 } type testVTabCursor struct { @@ -32,7 +34,7 @@ type testVTabCursor struct { index int } -func (m testModule) Create(c *SQLiteConn, args []string) (VTab, error) { +func (m *testModule) Create(c *SQLiteConn, args []string) (VTab, error) { if len(args) != 6 { m.t.Fatal("six arguments expected") } @@ -58,16 +60,19 @@ func (m testModule) Create(c *SQLiteConn, args []string) (VTab, error) { if err != nil { return nil, err } - return &testVTab{m.intarray}, nil + return &testVTab{intarray: m.intarray, log: &m.bestIndexLog}, nil } -func (m testModule) Connect(c *SQLiteConn, args []string) (VTab, error) { +func (m *testModule) Connect(c *SQLiteConn, args []string) (VTab, error) { return m.Create(c, args) } -func (m testModule) DestroyModule() {} +func (m *testModule) DestroyModule() {} func (v *testVTab) BestIndex(cst []InfoConstraint, ob []InfoOrderBy, colsUsed uint64) (*IndexResult, error) { + if v.log != nil { + *v.log = append(*v.log, colsUsed) + } used := make([]bool, 0, len(cst)) for range cst { used = append(used, false) @@ -128,9 +133,10 @@ func TestCreateModule(t *testing.T) { tempFilename := TempFilename(t) defer os.Remove(tempFilename) intarray := []int{1, 2, 3} + module := &testModule{t: t, intarray: intarray} sql.Register("sqlite3_TestCreateModule", &SQLiteDriver{ ConnectHook: func(conn *SQLiteConn) error { - return conn.CreateModule("test", testModule{t, intarray}) + return conn.CreateModule("test", module) }, }) db, err := sql.Open("sqlite3_TestCreateModule", tempFilename) @@ -141,6 +147,7 @@ func TestCreateModule(t *testing.T) { if err != nil { t.Fatalf("could not create vtable: %v", err) } + bestIndexCallsBeforeQuery := len(module.bestIndexLog) var i, value int rows, err := db.Query("SELECT rowid, * FROM vtab WHERE test = '3'") @@ -153,6 +160,15 @@ func TestCreateModule(t *testing.T) { t.Fatalf("want %v but %v", intarray[i], value) } } + bestIndexCalls := module.bestIndexLog[bestIndexCallsBeforeQuery:] + if len(bestIndexCalls) == 0 { + t.Fatal("expected BestIndex to be called during query planning") + } + for _, colsUsed := range bestIndexCalls { + if colsUsed != 1 { + t.Fatalf("expected colsUsed mask 1, got %d", colsUsed) + } + } _, err = db.Exec("DROP TABLE vtab") if err != nil {