mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-12 14:09:13 +00:00
368 lines
7.0 KiB
Go
368 lines
7.0 KiB
Go
package driver
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"math"
|
|
"net/url"
|
|
"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/internal/util"
|
|
"github.com/ncruces/go-sqlite3/vfs/memdb"
|
|
)
|
|
|
|
func Test_Open_error(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, err := Open("", nil, nil, nil)
|
|
if err == nil {
|
|
t.Error("want error")
|
|
}
|
|
if !errors.Is(err, sqlite3.MISUSE) {
|
|
t.Errorf("got %v, want sqlite3.MISUSE", err)
|
|
}
|
|
}
|
|
|
|
func Test_Open_dir(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db, err := sql.Open("sqlite3", ".")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
|
|
_, err = db.Conn(context.TODO())
|
|
if err == nil {
|
|
t.Fatal("want error")
|
|
}
|
|
if !errors.Is(err, sqlite3.CANTOPEN) {
|
|
t.Errorf("got %v, want sqlite3.CANTOPEN", err)
|
|
}
|
|
}
|
|
|
|
func Test_Open_pragma(t *testing.T) {
|
|
t.Parallel()
|
|
tmp := memdb.TestDB(t, url.Values{
|
|
"_pragma": {"busy_timeout(1000)"},
|
|
})
|
|
|
|
db, err := sql.Open("sqlite3", tmp)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
|
|
var timeout int
|
|
err = db.QueryRow(`PRAGMA busy_timeout`).Scan(&timeout)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if timeout != 1000 {
|
|
t.Errorf("got %v, want 1000", timeout)
|
|
}
|
|
}
|
|
|
|
func Test_Open_pragma_invalid(t *testing.T) {
|
|
t.Parallel()
|
|
tmp := memdb.TestDB(t, url.Values{
|
|
"_pragma": {"busy_timeout 1000"},
|
|
})
|
|
|
|
db, err := sql.Open("sqlite3", tmp)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
|
|
_, err = db.Conn(context.TODO())
|
|
if err == nil {
|
|
t.Fatal("want error")
|
|
}
|
|
var serr *sqlite3.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: invalid _pragma: sqlite3: SQL logic error: near "1000": syntax error` {
|
|
t.Error("got message:", got)
|
|
}
|
|
}
|
|
|
|
func Test_Open_txLock(t *testing.T) {
|
|
t.Parallel()
|
|
tmp := memdb.TestDB(t, url.Values{
|
|
"_txlock": {"exclusive"},
|
|
"_pragma": {"busy_timeout(1000)"},
|
|
})
|
|
|
|
db, err := sql.Open("sqlite3", tmp)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
|
|
tx1, err := db.Begin()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = db.Begin()
|
|
if err == nil {
|
|
t.Error("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)
|
|
}
|
|
|
|
err = tx1.Commit()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func Test_Open_txLock_invalid(t *testing.T) {
|
|
t.Parallel()
|
|
tmp := memdb.TestDB(t, url.Values{
|
|
"_txlock": {"xclusive"},
|
|
})
|
|
|
|
_, err := sql.Open("sqlite3", tmp+"_txlock=xclusive")
|
|
if err == nil {
|
|
t.Fatal("want error")
|
|
}
|
|
if got := err.Error(); got != `sqlite3: invalid _txlock: xclusive` {
|
|
t.Error("got message:", got)
|
|
}
|
|
}
|
|
|
|
func Test_BeginTx(t *testing.T) {
|
|
t.Parallel()
|
|
tmp := memdb.TestDB(t, url.Values{
|
|
"_txlock": {"exclusive"},
|
|
"_pragma": {"busy_timeout(0)"},
|
|
})
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
db, err := sql.Open("sqlite3", tmp)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
|
|
_, err = db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
|
|
if err.Error() != string(util.IsolationErr) {
|
|
t.Error("want isolationErr")
|
|
}
|
|
|
|
tx1, err := db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
tx2, err := db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = tx1.Exec(`CREATE TABLE test (col)`)
|
|
if err == nil {
|
|
t.Error("want error")
|
|
}
|
|
if !errors.Is(err, sqlite3.READONLY) {
|
|
t.Errorf("got %v, want sqlite3.READONLY", err)
|
|
}
|
|
|
|
err = tx2.Commit()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = tx1.Commit()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func Test_Prepare(t *testing.T) {
|
|
t.Parallel()
|
|
tmp := memdb.TestDB(t)
|
|
|
|
db, err := sql.Open("sqlite3", tmp)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
|
|
var serr *sqlite3.Error
|
|
_, err = db.Prepare(`SELECT`)
|
|
if err == nil {
|
|
t.Error("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 1; `)
|
|
if err.Error() != string(util.TailErr) {
|
|
t.Error("want tailErr")
|
|
}
|
|
|
|
_, err = db.Prepare(`SELECT 1; SELECT`)
|
|
if err.Error() != string(util.TailErr) {
|
|
t.Error("want tailErr")
|
|
}
|
|
|
|
_, err = db.Prepare(`SELECT 1; SELECT 2`)
|
|
if err.Error() != string(util.TailErr) {
|
|
t.Error("want tailErr")
|
|
}
|
|
}
|
|
|
|
func Test_QueryRow_named(t *testing.T) {
|
|
t.Parallel()
|
|
tmp := memdb.TestDB(t)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
db, err := sql.Open("sqlite3", tmp)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
|
|
conn, err := db.Conn(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
stmt, err := conn.PrepareContext(ctx, `SELECT ?, ?5, :AAA, @AAA, $AAA`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer stmt.Close()
|
|
|
|
date := time.Now()
|
|
row := stmt.QueryRow(true, sql.Named("AAA", math.Pi), nil /*3*/, nil /*4*/, date /*5*/)
|
|
|
|
var first bool
|
|
var fifth time.Time
|
|
var colon, at, dollar float32
|
|
err = row.Scan(&first, &fifth, &colon, &at, &dollar)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if first != true {
|
|
t.Errorf("want true, got %v", first)
|
|
}
|
|
if colon != math.Pi {
|
|
t.Errorf("want π, got %v", colon)
|
|
}
|
|
if at != math.Pi {
|
|
t.Errorf("want π, got %v", at)
|
|
}
|
|
if dollar != math.Pi {
|
|
t.Errorf("want π, got %v", dollar)
|
|
}
|
|
if !fifth.Equal(date) {
|
|
t.Errorf("want %v, got %v", date, fifth)
|
|
}
|
|
}
|
|
|
|
func Test_QueryRow_blob_null(t *testing.T) {
|
|
t.Parallel()
|
|
tmp := memdb.TestDB(t)
|
|
|
|
db, err := sql.Open("sqlite3", tmp)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
|
|
rows, err := db.Query(`
|
|
SELECT NULL UNION ALL
|
|
SELECT x'cafe' UNION ALL
|
|
SELECT x'babe' UNION ALL
|
|
SELECT NULL
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
want := [][]byte{nil, {0xca, 0xfe}, {0xba, 0xbe}, nil}
|
|
for i := 0; rows.Next(); i++ {
|
|
var buf sql.RawBytes
|
|
err = rows.Scan(&buf)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !bytes.Equal(buf, want[i]) {
|
|
t.Errorf("got %q, want %q", buf, want[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_time(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, fmt := range []string{"auto", "sqlite", "rfc3339", time.ANSIC} {
|
|
t.Run(fmt, func(t *testing.T) {
|
|
tmp := memdb.TestDB(t, url.Values{
|
|
"_timefmt": {fmt},
|
|
})
|
|
|
|
db, err := sql.Open("sqlite3", tmp)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
|
|
twosday := time.Date(2022, 2, 22, 22, 22, 22, 0, time.UTC)
|
|
|
|
_, err = db.Exec(`CREATE TABLE test (at DATETIME)`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = db.Exec(`INSERT INTO test VALUES (?)`, twosday)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var got time.Time
|
|
err = db.QueryRow(`SELECT * FROM test`).Scan(&got)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !got.Equal(twosday) {
|
|
t.Errorf("got: %v", got)
|
|
}
|
|
})
|
|
}
|
|
}
|