package tests import ( "context" "errors" "os" "path/filepath" "strings" "testing" "time" "github.com/ncruces/go-sqlite3" _ "github.com/ncruces/go-sqlite3/embed" _ "github.com/ncruces/go-sqlite3/internal/testcfg" "github.com/ncruces/go-sqlite3/vfs/memdb" ) func TestConn_Open_dir(t *testing.T) { t.Parallel() _, err := sqlite3.OpenFlags(".", 0) if err == nil { t.Fatal("want error") } if !errors.Is(err, sqlite3.CANTOPEN_ISDIR) { t.Errorf("got %v, want sqlite3.CANTOPEN_ISDIR", err) } } func TestConn_Open_notfound(t *testing.T) { t.Parallel() _, err := sqlite3.OpenFlags("test.db", sqlite3.OPEN_READONLY) if err == nil { t.Fatal("want error") } if !errors.Is(err, sqlite3.CANTOPEN) { t.Errorf("got %v, want sqlite3.CANTOPEN", err) } } func TestConn_Open_modeof(t *testing.T) { t.Parallel() dir := t.TempDir() file := filepath.Join(dir, "test.db") mode := filepath.Join(dir, "modeof.txt") fd, err := os.OpenFile(mode, os.O_CREATE, 0624) if err != nil { t.Fatal(err) } fi, err := fd.Stat() if err != nil { t.Fatal(err) } fd.Close() db, err := sqlite3.Open("file:" + file + "?modeof=" + mode) if err != nil { t.Fatal(err) } di, err := os.Stat(file) if err != nil { t.Fatal(err) } db.Close() if di.Mode() != fi.Mode() { t.Errorf("got %v, want %v", di.Mode(), fi.Mode()) } _, err = sqlite3.Open("file:" + file + "?modeof=" + mode + "2") if err == nil { t.Fatal("want error") } } func TestConn_Close(t *testing.T) { var db *sqlite3.Conn db.Close() } func TestConn_Close_BUSY(t *testing.T) { t.Parallel() db, err := sqlite3.Open(":memory:") if err != nil { t.Fatal(err) } defer db.Close() stmt, _, err := db.Prepare(`BEGIN`) if err != nil { t.Fatal(err) } defer stmt.Close() err = db.Close() if err == nil { t.Fatal("want error") } if !errors.Is(err, sqlite3.BUSY) { t.Errorf("got %v, want sqlite3.BUSY", err) } var terr interface{ Temporary() bool } if !errors.As(err, &terr) || !terr.Temporary() { t.Error("not temporary", err) } if got := err.Error(); got != `sqlite3: database is locked: unable to close due to unfinalized statements or unfinished backups` { t.Error("got message:", got) } } func TestConn_BusyHandler(t *testing.T) { t.Parallel() dsn := memdb.TestDB(t) db1, err := sqlite3.Open(dsn) if err != nil { t.Fatal(err) } defer db1.Close() db2, err := sqlite3.Open(dsn) if err != nil { t.Fatal(err) } defer db2.Close() var called bool err = db2.BusyHandler(func(ctx context.Context, count int) (retry bool) { called = true return count < 1 }) if err != nil { t.Fatal(err) } tx, err := db1.BeginExclusive() if err != nil { t.Fatal(err) } defer tx.End(&err) _, err = db2.BeginExclusive() if !errors.Is(err, sqlite3.BUSY) { t.Errorf("got %v, want sqlite3.BUSY", err) } if !called { t.Error("busy handler not called") } } func TestConn_SetInterrupt(t *testing.T) { t.Parallel() db, err := sqlite3.Open(":memory:") if err != nil { t.Fatal(err) } defer db.Close() ctx, cancel := context.WithCancel(t.Context()) db.SetInterrupt(ctx) // Interrupt doesn't interrupt this. err = db.Exec(`SELECT 1`) if err != nil { t.Fatal(err) } stmt, _, err := db.Prepare(` WITH RECURSIVE fibonacci (curr, next) AS ( SELECT 0, 1 UNION ALL SELECT next, curr + next FROM fibonacci LIMIT 1e7 ) SELECT min(curr) FROM fibonacci `) if err != nil { t.Fatal(err) } defer stmt.Close() time.AfterFunc(time.Millisecond, cancel) // Interrupting works. err = stmt.Exec() if !errors.Is(err, sqlite3.INTERRUPT) { t.Errorf("got %v, want sqlite3.INTERRUPT", err) } // Interrupting sticks. err = db.Exec(`SELECT 1`) if !errors.Is(err, sqlite3.INTERRUPT) { t.Errorf("got %v, want sqlite3.INTERRUPT", err) } db.SetInterrupt(t.Context()) // Interrupting can be cleared. err = db.Exec(`SELECT 1`) if err != nil { t.Fatal(err) } if got := db.GetInterrupt(); got != t.Context() { t.Errorf("got %v, want %v", got, t.Context()) } } func TestConn_Prepare_empty(t *testing.T) { t.Parallel() db, err := sqlite3.Open(":memory:") if err != nil { t.Fatal(err) } defer db.Close() stmt, _, err := db.Prepare(``) if err != nil { t.Fatal(err) } defer stmt.Close() if stmt != nil { t.Error("want nil") } } func TestConn_Prepare_tail(t *testing.T) { t.Parallel() db, err := sqlite3.Open(":memory:") if err != nil { t.Fatal(err) } defer db.Close() stmt, tail, err := db.Prepare(`SELECT 1; -- HERE`) if err != nil { t.Fatal(err) } defer stmt.Close() if !strings.Contains(tail, "-- HERE") { t.Errorf("got %q", tail) } } func TestConn_Prepare_invalid(t *testing.T) { t.Parallel() db, err := sqlite3.Open(":memory:") if err != nil { t.Fatal(err) } defer db.Close() var serr *sqlite3.Error _, _, err = db.Prepare(`SELECT`) if err == nil { t.Fatal("want error") } if !errors.As(err, &serr) { t.Fatalf("got %T, want sqlite3.Error", err) } if rc := serr.Code(); rc != sqlite3.ERROR { t.Errorf("got %d, want sqlite3.ERROR", rc) } if got := err.Error(); got != `sqlite3: SQL logic error: incomplete input` { t.Error("got message:", got) } _, _, err = db.Prepare(`SELECT * FRM sqlite_schema`) if err == nil { t.Fatal("want error") } if !errors.As(err, &serr) { t.Fatalf("got %T, want sqlite3.ERROR", err) } if rc := serr.Code(); rc != sqlite3.ERROR { t.Errorf("got %d, want sqlite3.ERROR", rc) } if got := serr.SQL(); got != `FRM sqlite_schema` { t.Error("got SQL:", got) } if got := serr.Error(); got != `sqlite3: SQL logic error: near "FRM": syntax error` { t.Error("got message:", got) } } func TestConn_ReleaseMemory(t *testing.T) { t.Parallel() db, err := sqlite3.Open(":memory:") if err != nil { t.Fatal(err) } defer db.Close() err = db.ReleaseMemory() if err != nil { t.Fatal(err) } } func TestConn_SetLastInsertRowID(t *testing.T) { t.Parallel() db, err := sqlite3.Open(":memory:") if err != nil { t.Fatal(err) } defer db.Close() db.SetLastInsertRowID(42) got := db.LastInsertRowID() if got != 42 { t.Errorf("got %d, want 42", got) } } func TestConn_Filename(t *testing.T) { t.Parallel() file := filepath.Join(t.TempDir(), "test.db") f, err := os.Create(file) if err != nil { t.Fatal(err) } f.Close() file, err = filepath.EvalSymlinks(file) if err != nil { t.Fatal(err) } db, err := sqlite3.Open(file) if err != nil { t.Fatal(err) } defer db.Close() n := db.Filename("") if n.String() != file { t.Errorf("got %v", n) } if n.Database() != file { t.Errorf("got %v", n) } if n.DatabaseFile() == nil { t.Errorf("got %v", n) } n = db.Filename("xpto") if n != nil { t.Errorf("got %v", n) } if n.String() != "" { t.Errorf("got %v", n) } if n.Database() != "" { t.Errorf("got %v", n) } if n.Journal() != "" { t.Errorf("got %v", n) } if n.WAL() != "" { t.Errorf("got %v", n) } if n.DatabaseFile() != nil { t.Errorf("got %v", n) } } func TestConn_ReadOnly(t *testing.T) { t.Parallel() db, err := sqlite3.Open(":memory:") if err != nil { t.Fatal(err) } defer db.Close() if ro, ok := db.ReadOnly(""); ro != false || ok != false { t.Errorf("got %v,%v", ro, ok) } if ro, ok := db.ReadOnly("xpto"); ro != false || ok != true { t.Errorf("got %v,%v", ro, ok) } } func TestConn_DBName(t *testing.T) { t.Parallel() db, err := sqlite3.Open(":memory:") if err != nil { t.Fatal(err) } defer db.Close() if name := db.DBName(0); name != "main" { t.Errorf("got %s", name) } if name := db.DBName(5); name != "" { t.Errorf("got %s", name) } } func TestConn_Status(t *testing.T) { t.Parallel() db, err := sqlite3.Open(":memory:") if err != nil { t.Fatal(err) } defer db.Close() err = db.Exec(`CREATE TABLE test (col)`) if err != nil { t.Fatal(err) } cr, hi, err := db.Status(sqlite3.DBSTATUS_SCHEMA_USED, true) if err != nil { t.Error("want nil") } if cr == 0 { t.Error("want something") } if hi != 0 { t.Error("want zero") } cr, hi, err = db.Status(sqlite3.DBSTATUS_LOOKASIDE_HIT, true) if err != nil { t.Error("want nil") } if cr != 0 { t.Error("want zero") } if hi == 0 { t.Error("want something") } cr, hi, err = db.Status(sqlite3.DBSTATUS_LOOKASIDE_HIT, true) if err != nil { t.Error("want nil") } if cr != 0 { t.Error("want zero") } if hi != 0 { t.Error("want zero") } } func TestConn_TableColumnMetadata(t *testing.T) { t.Parallel() db, err := sqlite3.Open(":memory:") if err != nil { t.Fatal(err) } defer db.Close() err = db.Exec(`CREATE TABLE test (col)`) if err != nil { t.Fatal(err) } _, _, _, _, _, err = db.TableColumnMetadata("", "table", "") if err == nil { t.Error("want error") } _, _, _, _, _, err = db.TableColumnMetadata("", "test", "") if err != nil { t.Error("want nil") } typ, ord, nn, pk, ai, err := db.TableColumnMetadata("main", "test", "rowid") if err != nil { t.Error("want nil") } if typ != "INTEGER" { t.Error("want INTEGER") } if ord != "BINARY" { t.Error("want BINARY") } if nn != false { t.Error("want false") } if pk != true { t.Error("want true") } if ai != false { t.Error("want false") } }