blob: 2470b0011408af9bddad87693e75e7115197274e [file]
//go:build sqlite_vtable || vtable
// +build sqlite_vtable vtable
package sqlite3
import (
"database/sql"
"errors"
"fmt"
"path/filepath"
"sync/atomic"
"testing"
)
type destroyErrModule struct{}
type destroyErrVTab struct{}
func (m destroyErrModule) Create(c *SQLiteConn, args []string) (VTab, error) {
if err := c.DeclareVTab("CREATE TABLE x(test TEXT)"); err != nil {
return nil, err
}
return &destroyErrVTab{}, nil
}
func (m destroyErrModule) Connect(c *SQLiteConn, args []string) (VTab, error) {
return m.Create(c, args)
}
func (m destroyErrModule) DestroyModule() {}
func (v *destroyErrVTab) BestIndex(cst []InfoConstraint, ob []InfoOrderBy) (*IndexResult, error) {
return &IndexResult{Used: make([]bool, len(cst)), EstimatedCost: 100}, nil
}
func (v *destroyErrVTab) Disconnect() error { return nil }
func (v *destroyErrVTab) Destroy() error { return errors.New("boom: destroy refused") }
func (v *destroyErrVTab) Open() (VTabCursor, error) {
return &destroyErrCursor{}, nil
}
type destroyErrCursor struct{}
func (c *destroyErrCursor) Close() error { return nil }
func (c *destroyErrCursor) Filter(idxNum int, idxStr string, vals []any) error {
return nil
}
func (c *destroyErrCursor) Next() error { return nil }
func (c *destroyErrCursor) EOF() bool { return true }
func (c *destroyErrCursor) Column(*SQLiteContext, int) error { return nil }
func (c *destroyErrCursor) Rowid() (int64, error) { return 0, nil }
var destroyErrDriverSeq int32
// TestVTabDestroyErrorThenClose: when xDestroy fails, SQLite retains the
// vtab object and later calls xDisconnect on it during close. The handle
// must stay registered for that call; deleting it on the error path made
// Close panic with a failed type assertion.
func TestVTabDestroyErrorThenClose(t *testing.T) {
// Unique driver name so repeated runs (e.g. -count=2) do not panic on
// duplicate registration.
driverName := fmt.Sprintf("sqlite3_destroy_err_%d", atomic.AddInt32(&destroyErrDriverSeq, 1))
sql.Register(driverName, &SQLiteDriver{
ConnectHook: func(conn *SQLiteConn) error {
return conn.CreateModule("boom", destroyErrModule{})
},
})
db, err := sql.Open(driverName, filepath.Join(t.TempDir(), "vtab.db"))
if err != nil {
t.Fatal(err)
}
if _, err := db.Exec("CREATE VIRTUAL TABLE vtab USING boom()"); err != nil {
t.Fatal(err)
}
rows, err := db.Query("SELECT * FROM vtab")
if err != nil {
t.Fatal(err)
}
rows.Close()
if _, err := db.Exec("DROP TABLE vtab"); err == nil {
t.Fatal("DROP TABLE unexpectedly succeeded with an erroring Destroy")
}
// Must not panic.
if err := db.Close(); err != nil {
t.Logf("close err: %v", err)
}
}