Files
sqlite3/driver/driver_test.go

470 lines
9.3 KiB
Go
Raw Normal View History

2023-02-20 13:30:01 +00:00
package driver
import (
"bytes"
"context"
"database/sql"
"errors"
"math"
2023-12-12 16:55:17 +00:00
"net/url"
"reflect"
2023-02-20 13:30:01 +00:00
"testing"
"time"
"github.com/ncruces/go-sqlite3"
2023-11-23 03:28:56 +00:00
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
2023-03-29 15:01:25 +01:00
"github.com/ncruces/go-sqlite3/internal/util"
2024-08-05 21:25:47 +01:00
"github.com/ncruces/go-sqlite3/vfs/memdb"
2023-02-20 13:30:01 +00:00
)
2024-08-12 17:50:23 +01:00
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)
}
}
2023-02-20 13:30:01 +00:00
func Test_Open_dir(t *testing.T) {
2023-02-27 12:07:48 +00:00
t.Parallel()
2023-02-20 13:30:01 +00:00
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")
}
2023-02-25 15:11:07 +00:00
if !errors.Is(err, sqlite3.CANTOPEN) {
t.Errorf("got %v, want sqlite3.CANTOPEN", err)
2023-02-20 13:30:01 +00:00
}
}
func Test_Open_pragma(t *testing.T) {
2023-02-27 12:07:48 +00:00
t.Parallel()
2024-08-05 21:25:47 +01:00
tmp := memdb.TestDB(t, url.Values{
"_pragma": {"busy_timeout(1000)"},
})
2023-02-27 12:07:48 +00:00
2024-08-05 21:25:47 +01:00
db, err := sql.Open("sqlite3", tmp)
2023-02-20 13:30:01 +00:00
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) {
2023-02-27 12:07:48 +00:00
t.Parallel()
2024-08-05 21:25:47 +01:00
tmp := memdb.TestDB(t, url.Values{
"_pragma": {"busy_timeout 1000"},
})
2023-02-27 12:07:48 +00:00
2024-08-05 21:25:47 +01:00
db, err := sql.Open("sqlite3", tmp)
2023-02-20 13:30:01 +00:00
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` {
2023-02-28 14:50:15 +00:00
t.Error("got message:", got)
2023-02-20 13:30:01 +00:00
}
}
func Test_Open_txLock(t *testing.T) {
2023-02-27 12:07:48 +00:00
t.Parallel()
2024-08-05 21:25:47 +01:00
tmp := memdb.TestDB(t, url.Values{
"_txlock": {"exclusive"},
"_pragma": {"busy_timeout(1000)"},
})
2023-02-27 12:07:48 +00:00
2024-08-05 21:25:47 +01:00
db, err := sql.Open("sqlite3", tmp)
2023-02-20 13:30:01 +00:00
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")
}
2023-02-25 15:11:07 +00:00
if !errors.Is(err, sqlite3.BUSY) {
t.Errorf("got %v, want sqlite3.BUSY", err)
2023-02-20 13:30:01 +00:00
}
2023-02-20 13:38:03 +00:00
var terr interface{ Temporary() bool }
if !errors.As(err, &terr) || !terr.Temporary() {
t.Error("not temporary", err)
}
2023-02-20 13:30:01 +00:00
err = tx1.Commit()
if err != nil {
t.Fatal(err)
}
}
2023-02-20 13:38:03 +00:00
func Test_Open_txLock_invalid(t *testing.T) {
2023-02-27 12:07:48 +00:00
t.Parallel()
2024-08-05 21:25:47 +01:00
tmp := memdb.TestDB(t, url.Values{
"_txlock": {"xclusive"},
})
2023-02-27 12:07:48 +00:00
2024-08-05 21:25:47 +01:00
_, err := sql.Open("sqlite3", tmp+"_txlock=xclusive")
2023-02-20 13:38:03 +00:00
if err == nil {
t.Fatal("want error")
}
if got := err.Error(); got != `sqlite3: invalid _txlock: xclusive` {
2023-02-28 14:50:15 +00:00
t.Error("got message:", got)
2023-02-20 13:38:03 +00:00
}
}
2023-02-20 13:30:01 +00:00
func Test_BeginTx(t *testing.T) {
2023-02-27 12:07:48 +00:00
t.Parallel()
2024-08-05 21:25:47 +01:00
tmp := memdb.TestDB(t, url.Values{
"_txlock": {"exclusive"},
"_pragma": {"busy_timeout(0)"},
})
2023-02-27 12:07:48 +00:00
2023-02-20 13:30:01 +00:00
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
2024-08-05 21:25:47 +01:00
db, err := sql.Open("sqlite3", tmp)
2023-02-20 13:30:01 +00:00
if err != nil {
t.Fatal(err)
}
defer db.Close()
_, err = db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
2023-03-29 15:01:25 +01:00
if err.Error() != string(util.IsolationErr) {
2023-02-20 13:30:01 +00:00
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)
}
2024-04-04 01:25:52 +01:00
_, err = tx1.Exec(`CREATE TABLE test (col)`)
2023-02-20 13:30:01 +00:00
if err == nil {
t.Error("want error")
}
2023-02-25 15:11:07 +00:00
if !errors.Is(err, sqlite3.READONLY) {
t.Errorf("got %v, want sqlite3.READONLY", err)
2023-02-20 13:30:01 +00:00
}
err = tx2.Commit()
if err != nil {
t.Fatal(err)
}
err = tx1.Commit()
if err != nil {
t.Fatal(err)
}
}
func Test_Prepare(t *testing.T) {
2023-02-27 12:07:48 +00:00
t.Parallel()
2024-08-05 21:25:47 +01:00
tmp := memdb.TestDB(t)
2023-02-27 12:07:48 +00:00
2024-08-05 21:25:47 +01:00
db, err := sql.Open("sqlite3", tmp)
2023-02-20 13:30:01 +00:00
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` {
2023-02-28 14:50:15 +00:00
t.Error("got message:", got)
2023-02-20 13:30:01 +00:00
}
2023-11-30 12:26:15 +00:00
_, err = db.Prepare(`SELECT 1; `)
if err.Error() != string(util.TailErr) {
t.Error("want tailErr")
2023-02-20 13:30:01 +00:00
}
2023-11-30 12:26:15 +00:00
_, err = db.Prepare(`SELECT 1; SELECT`)
if err.Error() != string(util.TailErr) {
t.Error("want tailErr")
2023-02-20 13:30:01 +00:00
}
_, err = db.Prepare(`SELECT 1; SELECT 2`)
2023-03-29 15:01:25 +01:00
if err.Error() != string(util.TailErr) {
2023-02-20 13:30:01 +00:00
t.Error("want tailErr")
}
}
func Test_QueryRow_named(t *testing.T) {
2023-02-27 12:07:48 +00:00
t.Parallel()
2024-08-05 21:25:47 +01:00
tmp := memdb.TestDB(t)
2023-02-27 12:07:48 +00:00
2023-02-20 13:30:01 +00:00
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
2024-08-05 21:25:47 +01:00
db, err := sql.Open("sqlite3", tmp)
2023-02-20 13:30:01 +00:00
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) {
2023-02-27 12:07:48 +00:00
t.Parallel()
2024-08-05 21:25:47 +01:00
tmp := memdb.TestDB(t)
2023-02-27 12:07:48 +00:00
2024-08-05 21:25:47 +01:00
db, err := sql.Open("sqlite3", tmp)
2023-02-20 13:30:01 +00:00
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)
}
2023-11-23 15:32:28 +00:00
defer rows.Close()
2023-02-20 13:30:01 +00:00
want := [][]byte{nil, {0xca, 0xfe}, {0xba, 0xbe}, nil}
for i := 0; rows.Next(); i++ {
2023-03-20 02:16:42 +00:00
var buf sql.RawBytes
2023-02-20 13:30:01 +00:00
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])
}
}
}
2023-12-12 16:55:17 +00:00
func Test_time(t *testing.T) {
t.Parallel()
for _, fmt := range []string{"auto", "sqlite", "rfc3339", time.ANSIC} {
t.Run(fmt, func(t *testing.T) {
2024-08-05 21:25:47 +01:00
tmp := memdb.TestDB(t, url.Values{
"_timefmt": {fmt},
})
db, err := sql.Open("sqlite3", tmp)
2023-12-12 16:55:17 +00:00
if err != nil {
t.Fatal(err)
}
defer db.Close()
twosday := time.Date(2022, 2, 22, 22, 22, 22, 0, time.UTC)
2024-04-04 01:25:52 +01:00
_, err = db.Exec(`CREATE TABLE test (at DATETIME)`)
2023-12-12 16:55:17 +00:00
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)
}
})
}
}
func Test_ColumnType_ScanType(t *testing.T) {
var (
INT = reflect.TypeOf(int64(0))
REAL = reflect.TypeOf(float64(0))
TEXT = reflect.TypeOf("")
BLOB = reflect.TypeOf([]byte{})
BOOL = reflect.TypeOf(false)
TIME = reflect.TypeOf(time.Time{})
ANY = reflect.TypeOf((*any)(nil)).Elem()
)
t.Parallel()
tmp := memdb.TestDB(t)
db, err := sql.Open("sqlite3", tmp)
if err != nil {
t.Fatal(err)
}
defer db.Close()
_, err = db.Exec(`
CREATE TABLE test (
col_int INTEGER,
col_real REAL,
col_text TEXT,
col_blob BLOB,
col_bool BOOLEAN,
col_time DATETIME,
col_decimal DECIMAL
);
INSERT INTO test VALUES
(1, 1, 1, 1, 1, 1, 1),
(2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0),
('1', '1', '1', '1', '1', '1', '1'),
('x', 'x', 'x', 'x', 'x', 'x', 'x'),
(x'', x'', x'', x'', x'', x'', x''),
('2006-01-02T15:04:05Z', '2006-01-02T15:04:05Z', '2006-01-02T15:04:05Z', '2006-01-02T15:04:05Z',
'2006-01-02T15:04:05Z', '2006-01-02T15:04:05Z', '2006-01-02T15:04:05Z'),
(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE),
(NULL, NULL, NULL, NULL, NULL, NULL, NULL);
`)
if err != nil {
t.Fatal(err)
}
rows, err := db.Query(`SELECT * FROM test`)
if err != nil {
t.Fatal(err)
}
defer rows.Close()
cols, err := rows.ColumnTypes()
if err != nil {
t.Fatal(err)
}
want := [][]reflect.Type{
{INT, REAL, TEXT, BLOB, BOOL, TIME, ANY},
{INT, REAL, TEXT, INT, BOOL, TIME, INT},
{INT, REAL, TEXT, REAL, INT, TIME, INT},
{INT, REAL, TEXT, TEXT, BOOL, TIME, INT},
{TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT},
{BLOB, BLOB, BLOB, BLOB, BLOB, BLOB, BLOB},
{TEXT, TEXT, TEXT, TEXT, TEXT, TIME, TEXT},
{INT, REAL, TEXT, INT, BOOL, TIME, INT},
{ANY, ANY, ANY, BLOB, ANY, ANY, ANY},
}
for j, c := range cols {
got := c.ScanType()
if got != want[0][j] {
t.Errorf("want %v, got %v, at column %d", want[0][j], got, j)
}
}
dest := make([]any, len(cols))
for i := 1; rows.Next(); i++ {
cols, err := rows.ColumnTypes()
if err != nil {
t.Fatal(err)
}
for j, c := range cols {
got := c.ScanType()
if got != want[i][j] {
t.Errorf("want %v, got %v, at row %d column %d", want[i][j], got, i, j)
}
dest[j] = reflect.New(got).Interface()
}
err = rows.Scan(dest...)
if err != nil {
t.Error(err)
}
}
err = rows.Err()
if err != nil {
t.Fatal(err)
}
}