| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 |
- package vrtxsql
- // P1 功能测试: ALTER TABLE ADD/DROP COLUMN、SHOW TABLES/COLUMNS、DESCRIBE、
- // TRUNCATE、DISTINCT、LIMIT offset,count、SQLSTATE、DSN mode=ro/cache=private。
- import (
- "database/sql"
- "errors"
- "fmt"
- "path/filepath"
- "sort"
- "testing"
- )
- // sqlState 提取错误的 SQLSTATE; 不是 *SQLError 返回 ""。
- func sqlState(err error) string {
- var se *SQLError
- if errors.As(err, &se) {
- return se.State
- }
- return ""
- }
- // TestP1AlterTable ADD/DROP COLUMN: 新列对旧行为 NULL, DROP 后列消失。
- func TestP1AlterTable(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, 100); err != nil {
- t.Fatal(err)
- }
- // ADD COLUMN: 旧行的新列应为 NULL
- if _, err := db.Exec(`ALTER TABLE t ADD COLUMN nickname VARCHAR(20)`); err != nil {
- t.Fatalf("add column: %v", err)
- }
- var nick sql.NullString
- if err := db.QueryRow(`SELECT nickname FROM t WHERE id = ?`, 1).Scan(&nick); err != nil {
- t.Fatalf("read new column: %v", err)
- }
- if nick.Valid {
- t.Fatalf("旧行新列应为 NULL, 实际 %q", nick.String)
- }
- // 新列可写可读
- if _, err := db.Exec(`UPDATE t SET nickname = ? WHERE id = ?`, "neo", 1); err != nil {
- t.Fatalf("update new column: %v", err)
- }
- if err := db.QueryRow(`SELECT nickname FROM t WHERE id = ?`, 1).Scan(&nick); err != nil {
- t.Fatal(err)
- }
- if !nick.Valid || nick.String != "neo" {
- t.Fatalf("新列写入读回不对: %+v", nick)
- }
- // DROP COLUMN: v 列消失
- if _, err := db.Exec(`ALTER TABLE t DROP COLUMN v`); err != nil {
- t.Fatalf("drop column: %v", err)
- }
- rs, err := db.Query(`SELECT * FROM t`)
- if err != nil {
- t.Fatal(err)
- }
- cols, _ := rs.Columns()
- if len(cols) != 2 { // id + nickname
- t.Fatalf("DROP 后应剩 2 列, 实际 %d (%v)", len(cols), cols)
- }
- rs.Close()
- if _, err := db.Query(`SELECT v FROM t`); err == nil {
- t.Fatal("查询已删除列应报错")
- } else if st := sqlState(err); st != "42S22" {
- t.Fatalf("已删除列错误状态=%q, 想要 42S22", st)
- }
- }
- // TestP1ShowAndDescribe SHOW TABLES / SHOW COLUMNS / DESCRIBE 结果集。
- func TestP1ShowAndDescribe(t *testing.T) {
- db := openTestDB(t)
- if _, err := db.Exec(`CREATE TABLE t (
- id BIGINT PRIMARY KEY,
- name VARCHAR(32) NOT NULL,
- age INT
- )`); err != nil {
- t.Fatal(err)
- }
- // SHOW TABLES: 单列, 含 t
- rs, err := db.Query(`SHOW TABLES`)
- if err != nil {
- t.Fatalf("show tables: %v", err)
- }
- found := false
- for rs.Next() {
- var nm []byte
- if err := rs.Scan(&nm); err != nil {
- t.Fatal(err)
- }
- if string(nm) == "t" {
- found = true
- }
- }
- rs.Close()
- if !found {
- t.Fatal("SHOW TABLES 未列出 t")
- }
- // SHOW COLUMNS FROM t: 六列, 三行; id 行 Key=PRI、name 行 Null=NO
- checkColumns := func(query string) {
- c, err := db.Query(query)
- if err != nil {
- t.Fatalf("%s: %v", query, err)
- }
- defer c.Close()
- cols, _ := c.Columns()
- if len(cols) != 6 {
- t.Fatalf("%s 应返回 6 列, 实际 %d", query, len(cols))
- }
- rows := 0
- for c.Next() {
- var field, typ, null, key, def, extra []byte
- if err := c.Scan(&field, &typ, &null, &key, &def, &extra); err != nil {
- t.Fatal(err)
- }
- switch string(field) {
- case "id":
- if string(key) != "PRI" {
- t.Fatalf("id Key=%q, 想要 PRI", key)
- }
- case "name":
- if string(null) != "NO" {
- t.Fatalf("name Null=%q, 想要 NO", null)
- }
- if string(typ) != "varchar(32)" {
- t.Fatalf("name Type=%q, 想要 varchar(32)", typ)
- }
- }
- rows++
- }
- if rows != 3 {
- t.Fatalf("%s 应描述 3 列, 实际 %d", query, rows)
- }
- }
- checkColumns(`SHOW COLUMNS FROM t`)
- checkColumns(`DESCRIBE t`)
- checkColumns(`DESC t`)
- }
- // TestP1Truncate 清空数据但保留结构, 清空后可继续插入。
- func TestP1Truncate(t *testing.T) {
- db := openTestDB(t)
- if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
- t.Fatal(err)
- }
- for i := 1; i <= 5; i++ {
- if _, err := db.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, i, i*10); err != nil {
- t.Fatal(err)
- }
- }
- if _, err := db.Exec(`TRUNCATE TABLE t`); err != nil {
- t.Fatalf("truncate: %v", err)
- }
- var cnt int
- if err := db.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil {
- t.Fatal(err)
- }
- if cnt != 0 {
- t.Fatalf("TRUNCATE 后应 0 行, 实际 %d", cnt)
- }
- // 表结构仍在, 可继续插入
- if _, err := db.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 100, 1); err != nil {
- t.Fatalf("TRUNCATE 后插入失败: %v", err)
- }
- }
- // TestP1Distinct 投影列去重。
- func TestP1Distinct(t *testing.T) {
- db := openTestDB(t)
- if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, cat VARCHAR(10))`); err != nil {
- t.Fatal(err)
- }
- cats := []string{"a", "b", "a", "c", "b"}
- for i, c := range cats {
- if _, err := db.Exec(`INSERT INTO t (id, cat) VALUES (?, ?)`, i+1, c); err != nil {
- t.Fatal(err)
- }
- }
- rs, err := db.Query(`SELECT DISTINCT cat FROM t`)
- if err != nil {
- t.Fatalf("distinct: %v", err)
- }
- defer rs.Close()
- var got []string
- for rs.Next() {
- var c []byte
- if err := rs.Scan(&c); err != nil {
- t.Fatal(err)
- }
- got = append(got, string(c))
- }
- sort.Strings(got)
- if len(got) != 3 || got[0] != "a" || got[1] != "b" || got[2] != "c" {
- t.Fatalf("DISTINCT 结果 %v, 想要 [a b c]", got)
- }
- }
- // TestP1LimitOffsetComma LIMIT offset,count 语法: 跳过 offset 行取 count 行。
- func TestP1LimitOffsetComma(t *testing.T) {
- db := openTestDB(t)
- if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY)`); err != nil {
- t.Fatal(err)
- }
- for i := 1; i <= 10; i++ {
- if _, err := db.Exec(`INSERT INTO t (id) VALUES (?)`, i); err != nil {
- t.Fatal(err)
- }
- }
- rs, err := db.Query(`SELECT id FROM t ORDER BY id LIMIT 2,3`)
- if err != nil {
- t.Fatalf("limit n,m: %v", err)
- }
- defer rs.Close()
- var got []int64
- for rs.Next() {
- var id int64
- if err := rs.Scan(&id); err != nil {
- t.Fatal(err)
- }
- got = append(got, id)
- }
- want := []int64{3, 4, 5}
- if len(got) != 3 || got[0] != want[0] || got[1] != want[1] || got[2] != want[2] {
- t.Fatalf("LIMIT 2,3 结果 %v, 想要 %v", got, want)
- }
- }
- // TestP1SQLState 关键错误的 SQLSTATE 映射。
- func TestP1SQLState(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) VALUES (?)`, 1); err != nil {
- t.Fatal(err)
- }
- // 表不存在 → 42S02
- _, err := db.Query(`SELECT * FROM nope`)
- if st := sqlState(err); st != "42S02" {
- t.Fatalf("表不存在 state=%q, 想要 42S02 (err=%v)", st, err)
- }
- // 主键重复 → 23000
- _, err = db.Exec(`INSERT INTO t (id) VALUES (?)`, 1)
- if st := sqlState(err); st != "23000" {
- t.Fatalf("主键重复 state=%q, 想要 23000 (err=%v)", st, err)
- }
- // 语法错误 → 42000
- _, err = db.Exec(`SELEC bad`)
- if st := sqlState(err); st != "42000" {
- t.Fatalf("语法错误 state=%q, 想要 42000 (err=%v)", st, err)
- }
- // 列不存在 → 42S22
- _, err = db.Query(`SELECT nope FROM t`)
- if st := sqlState(err); st != "42S22" {
- t.Fatalf("列不存在 state=%q, 想要 42S22 (err=%v)", st, err)
- }
- }
- // TestP1ReadOnlyDSN mode=ro: 可读不可写 (写报 25008); cache=private 独立实例持久化。
- func TestP1ReadOnlyDSN(t *testing.T) {
- path := filepath.Join(t.TempDir(), "ro.db")
- // 先写数据再关闭
- w, err := sql.Open("vrtx", path)
- if err != nil {
- t.Fatal(err)
- }
- if _, err := w.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
- t.Fatal(err)
- }
- if _, err := w.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 1, 10); err != nil {
- t.Fatal(err)
- }
- if err := w.Close(); err != nil {
- t.Fatal(err)
- }
- // 只读打开
- ro, err := sql.Open("vrtx", path+"?mode=ro")
- if err != nil {
- t.Fatal(err)
- }
- defer ro.Close()
- var cnt int
- if err := ro.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil {
- t.Fatalf("只读 SELECT 失败: %v", err)
- }
- if cnt != 1 {
- t.Fatalf("只读读到 %d 行, 想要 1", cnt)
- }
- _, err = ro.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 2, 20)
- if st := sqlState(err); st != "25008" {
- t.Fatalf("只读写入 state=%q, 想要 25008 (err=%v)", st, err)
- }
- if err := ro.Close(); err != nil {
- t.Fatal(err)
- }
- // cache=private: 独立实例, 数据持久化、可继续写
- pv, err := sql.Open("vrtx", path+"?cache=private")
- if err != nil {
- t.Fatal(err)
- }
- if _, err := pv.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 2, 20); err != nil {
- t.Fatalf("private 实例写入: %v", err)
- }
- if err := pv.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil {
- t.Fatal(err)
- }
- if cnt != 2 {
- t.Fatalf("private 实例应读到 2 行, 实际 %d", cnt)
- }
- pv.Close()
- // 关闭后普通连接能看到 private 写入 (独立实例也正确落盘)
- plain, err := sql.Open("vrtx", path)
- if err != nil {
- t.Fatal(err)
- }
- defer plain.Close()
- if err := plain.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil {
- t.Fatal(err)
- }
- if cnt != 2 {
- t.Fatalf("持久化后应 2 行, 实际 %d (%s)", cnt, fmt.Sprint(cnt))
- }
- }
- // TestP1Vacuum VACUUM 语句: 整库重建后行数据原样、文件可继续读写。
- // 顺带验证三种典型调用形态: 裸语句 / 多行空白 / 大小写混合。
- func TestP1Vacuum(t *testing.T) {
- db := openTestDB(t)
- if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
- t.Fatal(err)
- }
- for i := 1; i <= 10; i++ {
- if _, err := db.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, i, i*10); err != nil {
- t.Fatal(err)
- }
- }
- // 删掉一半再 VACUUM: 有垃圾可收, 更接近真实场景
- if _, err := db.Exec(`DELETE FROM t WHERE id > ?`, 5); err != nil {
- t.Fatal(err)
- }
- res, err := db.Exec(`VACUUM`)
- if err != nil {
- t.Fatalf("vacuum: %v", err)
- }
- if n, err := res.RowsAffected(); err != nil || n != 5 {
- t.Fatalf("RowsAffected 应为复制的 5 行, 实际 %d (%v)", n, err)
- }
- // 行数据原样
- var cnt int
- if err := db.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil {
- t.Fatal(err)
- }
- if cnt != 5 {
- t.Fatalf("VACUUM 后应 5 行, 实际 %d", cnt)
- }
- var v int
- if err := db.QueryRow(`SELECT v FROM t WHERE id = ?`, 3).Scan(&v); err != nil {
- t.Fatal(err)
- }
- if v != 30 {
- t.Fatalf("行数据应为 30, 实际 %d", v)
- }
- // 重建后可继续读写
- if _, err := db.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 100, 1); err != nil {
- t.Fatalf("VACUUM 后插入失败: %v", err)
- }
- // 大小写/空白形态
- if _, err := db.Exec(" vacuum "); err != nil {
- t.Fatalf("小写带空白 VACUUM: %v", err)
- }
- if err := db.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil {
- t.Fatal(err)
- }
- if cnt != 6 {
- t.Fatalf("第二次 VACUUM 后应 6 行, 实际 %d", cnt)
- }
- }
- // TestP1VacuumInTxn 活跃事务中 VACUUM 必须被拒 (引擎层 ErrTxnActive 透传)。
- func TestP1VacuumInTxn(t *testing.T) {
- db := openTestDB(t)
- if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
- t.Fatal(err)
- }
- tx, err := db.Begin()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := tx.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 1, 1); err != nil {
- t.Fatal(err)
- }
- if _, err := tx.Exec(`VACUUM`); err == nil {
- t.Fatal("事务中 VACUUM 应被拒绝")
- }
- if err := tx.Rollback(); err != nil {
- t.Fatal(err)
- }
- // 回滚后 VACUUM 恢复可用
- if _, err := db.Exec(`VACUUM`); err != nil {
- t.Fatalf("回滚后 VACUUM: %v", err)
- }
- }
|