| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539 |
- package vrtxsql
- // database/sql 驱动测试: 走标准库 API 全链路 (Open/Exec/Query/Tx/Prepare),
- // 覆盖 M1 (INSERT/SELECT/WHERE/LIMIT) + M2 (UPDATE/DELETE/事务/IS NULL)
- // + M3 (CREATE/DROP TABLE + 聚合)。
- import (
- "context"
- "database/sql"
- "errors"
- "fmt"
- "os"
- "path/filepath"
- "reflect"
- "strings"
- "testing"
- "time"
- "git.ycl.pub/yl32940/TsDB2/DB/core"
- )
- func openTestDB(t *testing.T) *sql.DB {
- t.Helper()
- dsn := filepath.Join(t.TempDir(), "driver_test.db")
- db, err := sql.Open("vrtx", dsn)
- if err != nil {
- t.Fatalf("open: %v", err)
- }
- t.Cleanup(func() { _ = db.Close() })
- return db
- }
- func TestDriverCRUDAndQuery(t *testing.T) {
- db := openTestDB(t)
- // M3: DDL
- if _, err := db.Exec(`CREATE TABLE users (
- id BIGINT PRIMARY KEY,
- name VARCHAR(32) NOT NULL,
- age INT,
- email VARCHAR(64) UNIQUE
- )`); err != nil {
- t.Fatalf("create table: %v", err)
- }
- // M1: INSERT (LastInsertId)
- res, err := db.Exec(`INSERT INTO users (id, name, age, email) VALUES (?, ?, ?, ?)`,
- 1, "alice", 30, "a@x.com")
- if err != nil {
- t.Fatalf("insert: %v", err)
- }
- if id, _ := res.LastInsertId(); id != 1 {
- t.Fatalf("LastInsertId=%d, 想要 1", id)
- }
- if n, _ := res.RowsAffected(); n != 1 {
- t.Fatalf("RowsAffected=%d, 想要 1", n)
- }
- // 省略列 (email NULL)
- if _, err := db.Exec(`INSERT INTO users (id, name, age) VALUES (?, ?, ?)`, 2, "bob", 25); err != nil {
- t.Fatalf("insert 2: %v", err)
- }
- // 唯一索引: 重复 email 必须报错
- if _, err := db.Exec(`INSERT INTO users (id, name, email) VALUES (?, ?, ?)`, 3, "carl", "a@x.com"); err == nil {
- t.Fatalf("唯一索引应拒绝重复 email")
- }
- // M1: SELECT 投影 + WHERE + ORDER BY + LIMIT
- rows, err := db.Query(`SELECT id, name, age FROM users WHERE age >= ? ORDER BY age DESC LIMIT 10`, 18)
- if err != nil {
- t.Fatalf("query: %v", err)
- }
- defer rows.Close()
- type u struct {
- id int64
- name string
- age int64
- hasAge bool
- }
- var got []u
- for rows.Next() {
- var r u
- var name []byte
- var age sql.NullInt64
- if err := rows.Scan(&r.id, &name, &age); err != nil {
- t.Fatalf("scan: %v", err)
- }
- r.name = string(name)
- if age.Valid {
- r.age = age.Int64
- r.hasAge = true
- }
- got = append(got, r)
- }
- if len(got) != 2 || got[0].name != "alice" || got[1].name != "bob" {
- t.Fatalf("查询结果 %+v 不对", got)
- }
- if !got[0].hasAge || got[0].age != 30 {
- t.Fatalf("age 投影不对: %+v", got[0])
- }
- // IN / OR / NOT
- var cnt int
- if err := db.QueryRow(`SELECT COUNT(*) FROM users WHERE id IN (?, ?)`, 1, 2).Scan(&cnt); err != nil || cnt != 2 {
- t.Fatalf("IN 查询: cnt=%d err=%v", cnt, err)
- }
- if err := db.QueryRow(`SELECT COUNT(*) FROM users WHERE NOT (id = ?)`, 1).Scan(&cnt); err != nil || cnt != 1 {
- t.Fatalf("NOT 查询: cnt=%d err=%v", cnt, err)
- }
- // M2: IS NULL (email 为 NULL 的 bob)
- if err := db.QueryRow(`SELECT COUNT(*) FROM users WHERE email IS NULL`).Scan(&cnt); err != nil || cnt != 1 {
- t.Fatalf("IS NULL: cnt=%d err=%v", cnt, err)
- }
- if err := db.QueryRow(`SELECT COUNT(*) FROM users WHERE email IS NOT NULL`).Scan(&cnt); err != nil || cnt != 1 {
- t.Fatalf("IS NOT NULL: cnt=%d err=%v", cnt, err)
- }
- // M2: UPDATE (RowsAffected + 唯一冲突传播)
- res, err = db.Exec(`UPDATE users SET age = ? WHERE name = ?`, 31, "alice")
- if err != nil {
- t.Fatalf("update: %v", err)
- }
- if n, _ := res.RowsAffected(); n != 1 {
- t.Fatalf("update affected=%d", n)
- }
- var age int64
- if err := db.QueryRow(`SELECT age FROM users WHERE id = ?`, 1).Scan(&age); err != nil || age != 31 {
- t.Fatalf("update 未生效: age=%d err=%v", age, err)
- }
- // M2: DELETE
- res, err = db.Exec(`DELETE FROM users WHERE id = ?`, 2)
- if err != nil {
- t.Fatalf("delete: %v", err)
- }
- if n, _ := res.RowsAffected(); n != 1 {
- t.Fatalf("delete affected=%d", n)
- }
- // M3: 聚合 SUM/MIN/MAX/AVG
- var sum float64
- if err := db.QueryRow(`SELECT SUM(age) FROM users`).Scan(&sum); err != nil || sum != 31 {
- t.Fatalf("SUM: %v %v", sum, err)
- }
- var avg float64
- if err := db.QueryRow(`SELECT AVG(age) FROM users`).Scan(&avg); err != nil || avg != 31 {
- t.Fatalf("AVG: %v %v", avg, err)
- }
- // Prepare 路径 (database/sql stmt)
- stmt, err := db.Prepare(`SELECT name FROM users WHERE id = ?`)
- if err != nil {
- t.Fatalf("prepare: %v", err)
- }
- defer stmt.Close()
- var nm string
- if err := stmt.QueryRow(1).Scan(&nm); err != nil || nm != "alice" {
- t.Fatalf("prepared query: %q %v", nm, err)
- }
- }
- func TestDriverTransaction(t *testing.T) {
- db := openTestDB(t)
- if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
- t.Fatalf("create: %v", err)
- }
- tx, err := db.Begin()
- if err != nil {
- t.Fatalf("begin: %v", err)
- }
- if _, err := tx.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 1, 100); err != nil {
- t.Fatalf("tx insert: %v", err)
- }
- if err := tx.Rollback(); err != nil {
- t.Fatalf("rollback: %v", err)
- }
- var cnt int
- if err := db.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil || cnt != 0 {
- t.Fatalf("rollback 后应有 0 行, 实际 %d (%v)", cnt, err)
- }
- tx2, err := db.Begin()
- if err != nil {
- t.Fatalf("begin2: %v", err)
- }
- if _, err := tx2.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 2, 200); err != nil {
- t.Fatalf("tx2 insert: %v", err)
- }
- if err := tx2.Commit(); err != nil {
- t.Fatalf("commit: %v", err)
- }
- if err := db.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil || cnt != 1 {
- t.Fatalf("commit 后应有 1 行, 实际 %d (%v)", cnt, err)
- }
- }
- func TestDriverUnsupportedAndErrors(t *testing.T) {
- db := openTestDB(t)
- if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
- t.Fatalf("create: %v", err)
- }
- // 不支持语法: 明确报错而非静默
- if _, err := db.Query(`SELECT * FROM t GROUP BY v`); err == nil {
- t.Fatalf("GROUP BY 应报错")
- }
- if _, err := db.Exec(`DROP TABLE t`); err != nil {
- t.Fatalf("drop: %v", err)
- }
- // IF EXISTS
- if _, err := db.Exec(`DROP TABLE IF EXISTS t`); err != nil {
- t.Fatalf("drop if exists: %v", err)
- }
- // SELECT 走 Exec / INSERT 走 Query 的错路提示
- if _, err := db.Exec(`SELECT * FROM t`); err == nil {
- t.Fatalf("SELECT 用 Exec 应提示错路")
- }
- }
- // TestDriverSharedEngine 同一路径的两个 *sql.DB 共享同一引擎实例
- // (注册表语义): 经 db1 建表, db2 立即可见。
- func TestDriverSharedEngine(t *testing.T) {
- dsn := filepath.Join(t.TempDir(), "shared.db")
- db1, err := sql.Open("vrtx", dsn)
- if err != nil {
- t.Fatal(err)
- }
- defer db1.Close()
- db2, err := sql.Open("vrtx", dsn)
- if err != nil {
- t.Fatal(err)
- }
- defer db2.Close()
- if _, err := db1.Exec(`CREATE TABLE s (id BIGINT PRIMARY KEY)`); err != nil {
- t.Fatalf("db1 create: %v", err)
- }
- if _, err := db2.Exec(`INSERT INTO s (id) VALUES (?)`, 1); err != nil {
- t.Fatalf("db2 insert (db1 建的表不可见 = 引擎没共享): %v", err)
- }
- _ = os.Remove(dsn)
- }
- // openCore 打开一个裸引擎供包内钻取 (不经 database/sql)。
- func openCore(t *testing.T) *core.DB {
- t.Helper()
- db, err := core.Open(t.TempDir() + "/exec_drill.db")
- if err != nil {
- t.Fatalf("open: %v", err)
- }
- t.Cleanup(func() { _ = db.Close() })
- return db
- }
- // TestDriverContextCancelWhileLocked 验证 ctx 在"排队等锁"阶段被尊重:
- // db1 用未提交事务持锁, db2 的短超时 Query 必须在约 ctx 超时后返回
- // DeadlineExceeded, 而不是无视超时一直阻塞到事务结束。
- func TestDriverContextCancelWhileLocked(t *testing.T) {
- dsn := filepath.Join(t.TempDir(), "ctx.db")
- db1, err := sql.Open("vrtx", dsn)
- if err != nil {
- t.Fatal(err)
- }
- defer db1.Close()
- db2, err := sql.Open("vrtx", dsn)
- if err != nil {
- t.Fatal(err)
- }
- defer db2.Close()
- if _, err := db1.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
- t.Fatal(err)
- }
- tx, err := db1.Begin()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := tx.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 1, 1); err != nil {
- t.Fatal(err)
- }
- defer tx.Rollback()
- ctx, cancel := context.WithTimeout(context.Background(), 80*time.Millisecond)
- defer cancel()
- start := time.Now()
- _, err = db2.QueryContext(ctx, `SELECT * FROM t`)
- elapsed := time.Since(start)
- if err == nil {
- t.Fatal("期望 ctx 超时报错, 实际查询成功")
- }
- if !errors.Is(err, context.DeadlineExceeded) {
- t.Fatalf("期望 context.DeadlineExceeded, 实际 %v", err)
- }
- if elapsed > 800*time.Millisecond {
- t.Fatalf("ctx 未被尊重: 等了 %v 才返回", elapsed)
- }
- }
- // TestDriverStreamingAndColumnTypes 验证普通 SELECT 流式返回 (行数/顺序/内容)
- // 及 RowsColumnType* 元数据 (DatabaseTypeName/ScanType/Length)。
- func TestDriverStreamingAndColumnTypes(t *testing.T) {
- db := openTestDB(t)
- if _, err := db.Exec(`CREATE TABLE items (
- id BIGINT PRIMARY KEY,
- name VARCHAR(40) NOT NULL,
- price DOUBLE,
- tag VARCHAR(20)
- )`); err != nil {
- t.Fatal(err)
- }
- const n = 10000
- for b := 0; b < n; b += 500 {
- var sb strings.Builder
- sb.WriteString(`INSERT INTO items (id, name, price, tag) VALUES `)
- args := make([]interface{}, 0, 500*4)
- rows := n - b
- if rows > 500 {
- rows = 500
- }
- for i := 0; i < rows; i++ {
- if i > 0 {
- sb.WriteString(",")
- }
- sb.WriteString("(?,?,?,?)")
- id := b + i + 1
- args = append(args, id, fmt.Sprintf("item%d", id), float64(id)*1.5, nil)
- }
- if _, err := db.Exec(sb.String(), args...); err != nil {
- t.Fatalf("batch @%d: %v", b, err)
- }
- }
- rs, err := db.Query(`SELECT id, name, price FROM items ORDER BY id ASC`)
- if err != nil {
- t.Fatal(err)
- }
- defer rs.Close()
- cts, err := rs.ColumnTypes()
- if err != nil {
- t.Fatal(err)
- }
- if len(cts) != 3 {
- t.Fatalf("列类型数=%d, 想要 3", len(cts))
- }
- if dbn := cts[0].DatabaseTypeName(); dbn != "BIGINT" {
- t.Fatalf("id DatabaseTypeName=%q, 想要 BIGINT", dbn)
- }
- if st := cts[1].ScanType(); st.Kind() != reflect.String {
- t.Fatalf("name ScanType=%v, 想要 string", st)
- }
- if dbn := cts[2].DatabaseTypeName(); dbn != "DOUBLE" {
- t.Fatalf("price DatabaseTypeName=%q, 想要 DOUBLE", dbn)
- }
- if l, ok := cts[1].Length(); !ok || l < 40 {
- t.Fatalf("name Length=%d ok=%v (想要 >=40)", l, ok)
- }
- count := 0
- for rs.Next() {
- var id int64
- var name []byte
- var price float64
- if err := rs.Scan(&id, &name, &price); err != nil {
- t.Fatal(err)
- }
- count++
- if id != int64(count) {
- t.Fatalf("顺序错位: 第 %d 行 id=%d", count, id)
- }
- if string(name) != fmt.Sprintf("item%d", id) {
- t.Fatalf("name 错位: id=%d name=%s", id, name)
- }
- if price != float64(id)*1.5 {
- t.Fatalf("price 错位: id=%d price=%v", id, price)
- }
- }
- if err := rs.Err(); err != nil {
- t.Fatal(err)
- }
- if count != n {
- t.Fatalf("流式行数=%d, 想要 %d", count, n)
- }
- }
- // TestDriverPreparedQueryContext 验证 prepared 语句走 driver.StmtQueryContext
- // (公开 QueryContext, 响应 ctx), 结果正确。
- func TestDriverPreparedQueryContext(t *testing.T) {
- db := openTestDB(t)
- if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
- t.Fatal(err)
- }
- if _, err := db.Exec(`INSERT INTO t (id, v) VALUES (?,?),(?,?)`, 1, 10, 2, 20); err != nil {
- t.Fatal(err)
- }
- stmt, err := db.Prepare(`SELECT v FROM t WHERE id = ?`)
- if err != nil {
- t.Fatal(err)
- }
- defer stmt.Close()
- ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
- defer cancel()
- rs, err := stmt.QueryContext(ctx, 2)
- if err != nil {
- t.Fatalf("prepared QueryContext: %v", err)
- }
- defer rs.Close()
- if !rs.Next() {
- t.Fatal("prepared 查询无行")
- }
- var v int64
- if err := rs.Scan(&v); err != nil {
- t.Fatal(err)
- }
- if v != 20 {
- t.Fatalf("v=%d, 想要 20", v)
- }
- }
- // TestDriverDateTimeSymmetry 时间列读写对称: 写 time.Time, SELECT 读回也是
- // time.Time; 覆盖 time.Time 参数、时间字符串字面量、范围、MIN/MAX 与列类型元数据。
- func TestDriverDateTimeSymmetry(t *testing.T) {
- db := openTestDB(t)
- if _, err := db.Exec(`CREATE TABLE events (
- id BIGINT PRIMARY KEY,
- name VARCHAR(32),
- created_at DATETIME
- )`); err != nil {
- t.Fatalf("create: %v", err)
- }
- // 毫秒对齐的固定时间 (本地时区)
- t1 := time.Date(2026, 9, 29, 12, 0, 0, 0, time.Local)
- t2 := time.Date(2026, 9, 29, 18, 30, 45, 0, time.Local)
- t3 := time.Date(2026, 10, 1, 0, 0, 0, 0, time.Local)
- for i, tv := range []time.Time{t1, t2, t3} {
- if _, err := db.Exec(`INSERT INTO events (id, name, created_at) VALUES (?, ?, ?)`,
- i+1, fmt.Sprintf("e%d", i+1), tv); err != nil {
- t.Fatalf("insert%d: %v", i+1, err)
- }
- }
- // 读回对称 + 列类型元数据
- rs, err := db.Query(`SELECT id, created_at FROM events ORDER BY id ASC`)
- if err != nil {
- t.Fatalf("query: %v", err)
- }
- defer rs.Close()
- cts, err := rs.ColumnTypes()
- if err != nil {
- t.Fatalf("column types: %v", err)
- }
- if dbn := cts[1].DatabaseTypeName(); dbn != "DATETIME" {
- t.Fatalf("created_at DatabaseTypeName=%q, 想要 DATETIME", dbn)
- }
- if st := cts[1].ScanType(); st != reflect.TypeOf(time.Time{}) {
- t.Fatalf("created_at ScanType=%v, 想要 time.Time", st)
- }
- want := []time.Time{t1, t2, t3}
- i := 0
- for rs.Next() {
- var id int64
- var created time.Time
- if err := rs.Scan(&id, &created); err != nil {
- t.Fatalf("scan: %v", err)
- }
- if !created.Equal(want[i]) {
- t.Fatalf("行 %d 时间不对称: 写 %v 读 %v", i, want[i], created)
- }
- i++
- }
- if err := rs.Err(); err != nil {
- t.Fatal(err)
- }
- if i != 3 {
- t.Fatalf("读到 %d 行, 想要 3", i)
- }
- // WHERE 用 time.Time 参数
- var name []byte
- if err := db.QueryRow(`SELECT name FROM events WHERE created_at = ?`, t2).Scan(&name); err != nil {
- t.Fatalf("WHERE time.Time 参数: %v", err)
- }
- if string(name) != "e2" {
- t.Fatalf("命中 %q, 想要 e2", name)
- }
- // WHERE 用时间字符串字面量 (MySQL 格式, 本地时区)
- var id int64
- if err := db.QueryRow(`SELECT id FROM events WHERE created_at = '2026-09-29 12:00:00'`).Scan(&id); err != nil {
- t.Fatalf("WHERE 时间字面量: %v", err)
- }
- if id != 1 {
- t.Fatalf("命中 id=%d, 想要 1", id)
- }
- // 时间范围
- var cnt int
- if err := db.QueryRow(`SELECT COUNT(*) FROM events WHERE created_at > ?`, t1).Scan(&cnt); err != nil {
- t.Fatalf("时间范围: %v", err)
- }
- if cnt != 2 {
- t.Fatalf("范围计数=%d, 想要 2", cnt)
- }
- // MIN/MAX 返回 time.Time
- var mn, mx time.Time
- if err := db.QueryRow(`SELECT MIN(created_at) FROM events`).Scan(&mn); err != nil {
- t.Fatalf("MIN: %v", err)
- }
- if !mn.Equal(t1) {
- t.Fatalf("MIN=%v, 想要 %v", mn, t1)
- }
- if err := db.QueryRow(`SELECT MAX(created_at) FROM events`).Scan(&mx); err != nil {
- t.Fatalf("MAX: %v", err)
- }
- if !mx.Equal(t3) {
- t.Fatalf("MAX=%v, 想要 %v", mx, t3)
- }
- }
- // TestDriverTableOptions GORM/xorm 风格的表选项 (ENGINE/CHARSET/...) 解析并忽略。
- func TestDriverTableOptions(t *testing.T) {
- db := openTestDB(t)
- _, err := db.Exec(`CREATE TABLE t (
- id BIGINT PRIMARY KEY,
- name VARCHAR(32)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表' AUTO_INCREMENT=1 ROW_FORMAT=DYNAMIC`)
- if err != nil {
- t.Fatalf("带表选项的 CREATE TABLE: %v", err)
- }
- if _, err := db.Exec(`INSERT INTO t (id, name) VALUES (?, ?)`, 1, "a"); err != nil {
- t.Fatalf("insert: %v", err)
- }
- // 白名单之外的选项名必须报错
- if _, err := db.Exec(`CREATE TABLE t2 (id BIGINT PRIMARY KEY) FROBNICATOR=1`); err == nil {
- t.Fatalf("未知表选项应报错")
- }
- }
|