p1_features_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. package vrtxsql
  2. // P1 功能测试: ALTER TABLE ADD/DROP COLUMN、SHOW TABLES/COLUMNS、DESCRIBE、
  3. // TRUNCATE、DISTINCT、LIMIT offset,count、SQLSTATE、DSN mode=ro/cache=private。
  4. import (
  5. "database/sql"
  6. "errors"
  7. "fmt"
  8. "path/filepath"
  9. "sort"
  10. "testing"
  11. )
  12. // sqlState 提取错误的 SQLSTATE; 不是 *SQLError 返回 ""。
  13. func sqlState(err error) string {
  14. var se *SQLError
  15. if errors.As(err, &se) {
  16. return se.State
  17. }
  18. return ""
  19. }
  20. // TestP1AlterTable ADD/DROP COLUMN: 新列对旧行为 NULL, DROP 后列消失。
  21. func TestP1AlterTable(t *testing.T) {
  22. db := openTestDB(t)
  23. if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
  24. t.Fatal(err)
  25. }
  26. if _, err := db.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 1, 100); err != nil {
  27. t.Fatal(err)
  28. }
  29. // ADD COLUMN: 旧行的新列应为 NULL
  30. if _, err := db.Exec(`ALTER TABLE t ADD COLUMN nickname VARCHAR(20)`); err != nil {
  31. t.Fatalf("add column: %v", err)
  32. }
  33. var nick sql.NullString
  34. if err := db.QueryRow(`SELECT nickname FROM t WHERE id = ?`, 1).Scan(&nick); err != nil {
  35. t.Fatalf("read new column: %v", err)
  36. }
  37. if nick.Valid {
  38. t.Fatalf("旧行新列应为 NULL, 实际 %q", nick.String)
  39. }
  40. // 新列可写可读
  41. if _, err := db.Exec(`UPDATE t SET nickname = ? WHERE id = ?`, "neo", 1); err != nil {
  42. t.Fatalf("update new column: %v", err)
  43. }
  44. if err := db.QueryRow(`SELECT nickname FROM t WHERE id = ?`, 1).Scan(&nick); err != nil {
  45. t.Fatal(err)
  46. }
  47. if !nick.Valid || nick.String != "neo" {
  48. t.Fatalf("新列写入读回不对: %+v", nick)
  49. }
  50. // DROP COLUMN: v 列消失
  51. if _, err := db.Exec(`ALTER TABLE t DROP COLUMN v`); err != nil {
  52. t.Fatalf("drop column: %v", err)
  53. }
  54. rs, err := db.Query(`SELECT * FROM t`)
  55. if err != nil {
  56. t.Fatal(err)
  57. }
  58. cols, _ := rs.Columns()
  59. if len(cols) != 2 { // id + nickname
  60. t.Fatalf("DROP 后应剩 2 列, 实际 %d (%v)", len(cols), cols)
  61. }
  62. rs.Close()
  63. if _, err := db.Query(`SELECT v FROM t`); err == nil {
  64. t.Fatal("查询已删除列应报错")
  65. } else if st := sqlState(err); st != "42S22" {
  66. t.Fatalf("已删除列错误状态=%q, 想要 42S22", st)
  67. }
  68. }
  69. // TestP1ShowAndDescribe SHOW TABLES / SHOW COLUMNS / DESCRIBE 结果集。
  70. func TestP1ShowAndDescribe(t *testing.T) {
  71. db := openTestDB(t)
  72. if _, err := db.Exec(`CREATE TABLE t (
  73. id BIGINT PRIMARY KEY,
  74. name VARCHAR(32) NOT NULL,
  75. age INT
  76. )`); err != nil {
  77. t.Fatal(err)
  78. }
  79. // SHOW TABLES: 单列, 含 t
  80. rs, err := db.Query(`SHOW TABLES`)
  81. if err != nil {
  82. t.Fatalf("show tables: %v", err)
  83. }
  84. found := false
  85. for rs.Next() {
  86. var nm []byte
  87. if err := rs.Scan(&nm); err != nil {
  88. t.Fatal(err)
  89. }
  90. if string(nm) == "t" {
  91. found = true
  92. }
  93. }
  94. rs.Close()
  95. if !found {
  96. t.Fatal("SHOW TABLES 未列出 t")
  97. }
  98. // SHOW COLUMNS FROM t: 六列, 三行; id 行 Key=PRI、name 行 Null=NO
  99. checkColumns := func(query string) {
  100. c, err := db.Query(query)
  101. if err != nil {
  102. t.Fatalf("%s: %v", query, err)
  103. }
  104. defer c.Close()
  105. cols, _ := c.Columns()
  106. if len(cols) != 6 {
  107. t.Fatalf("%s 应返回 6 列, 实际 %d", query, len(cols))
  108. }
  109. rows := 0
  110. for c.Next() {
  111. var field, typ, null, key, def, extra []byte
  112. if err := c.Scan(&field, &typ, &null, &key, &def, &extra); err != nil {
  113. t.Fatal(err)
  114. }
  115. switch string(field) {
  116. case "id":
  117. if string(key) != "PRI" {
  118. t.Fatalf("id Key=%q, 想要 PRI", key)
  119. }
  120. case "name":
  121. if string(null) != "NO" {
  122. t.Fatalf("name Null=%q, 想要 NO", null)
  123. }
  124. if string(typ) != "varchar(32)" {
  125. t.Fatalf("name Type=%q, 想要 varchar(32)", typ)
  126. }
  127. }
  128. rows++
  129. }
  130. if rows != 3 {
  131. t.Fatalf("%s 应描述 3 列, 实际 %d", query, rows)
  132. }
  133. }
  134. checkColumns(`SHOW COLUMNS FROM t`)
  135. checkColumns(`DESCRIBE t`)
  136. checkColumns(`DESC t`)
  137. }
  138. // TestP1Truncate 清空数据但保留结构, 清空后可继续插入。
  139. func TestP1Truncate(t *testing.T) {
  140. db := openTestDB(t)
  141. if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
  142. t.Fatal(err)
  143. }
  144. for i := 1; i <= 5; i++ {
  145. if _, err := db.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, i, i*10); err != nil {
  146. t.Fatal(err)
  147. }
  148. }
  149. if _, err := db.Exec(`TRUNCATE TABLE t`); err != nil {
  150. t.Fatalf("truncate: %v", err)
  151. }
  152. var cnt int
  153. if err := db.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil {
  154. t.Fatal(err)
  155. }
  156. if cnt != 0 {
  157. t.Fatalf("TRUNCATE 后应 0 行, 实际 %d", cnt)
  158. }
  159. // 表结构仍在, 可继续插入
  160. if _, err := db.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 100, 1); err != nil {
  161. t.Fatalf("TRUNCATE 后插入失败: %v", err)
  162. }
  163. }
  164. // TestP1Distinct 投影列去重。
  165. func TestP1Distinct(t *testing.T) {
  166. db := openTestDB(t)
  167. if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, cat VARCHAR(10))`); err != nil {
  168. t.Fatal(err)
  169. }
  170. cats := []string{"a", "b", "a", "c", "b"}
  171. for i, c := range cats {
  172. if _, err := db.Exec(`INSERT INTO t (id, cat) VALUES (?, ?)`, i+1, c); err != nil {
  173. t.Fatal(err)
  174. }
  175. }
  176. rs, err := db.Query(`SELECT DISTINCT cat FROM t`)
  177. if err != nil {
  178. t.Fatalf("distinct: %v", err)
  179. }
  180. defer rs.Close()
  181. var got []string
  182. for rs.Next() {
  183. var c []byte
  184. if err := rs.Scan(&c); err != nil {
  185. t.Fatal(err)
  186. }
  187. got = append(got, string(c))
  188. }
  189. sort.Strings(got)
  190. if len(got) != 3 || got[0] != "a" || got[1] != "b" || got[2] != "c" {
  191. t.Fatalf("DISTINCT 结果 %v, 想要 [a b c]", got)
  192. }
  193. }
  194. // TestP1LimitOffsetComma LIMIT offset,count 语法: 跳过 offset 行取 count 行。
  195. func TestP1LimitOffsetComma(t *testing.T) {
  196. db := openTestDB(t)
  197. if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY)`); err != nil {
  198. t.Fatal(err)
  199. }
  200. for i := 1; i <= 10; i++ {
  201. if _, err := db.Exec(`INSERT INTO t (id) VALUES (?)`, i); err != nil {
  202. t.Fatal(err)
  203. }
  204. }
  205. rs, err := db.Query(`SELECT id FROM t ORDER BY id LIMIT 2,3`)
  206. if err != nil {
  207. t.Fatalf("limit n,m: %v", err)
  208. }
  209. defer rs.Close()
  210. var got []int64
  211. for rs.Next() {
  212. var id int64
  213. if err := rs.Scan(&id); err != nil {
  214. t.Fatal(err)
  215. }
  216. got = append(got, id)
  217. }
  218. want := []int64{3, 4, 5}
  219. if len(got) != 3 || got[0] != want[0] || got[1] != want[1] || got[2] != want[2] {
  220. t.Fatalf("LIMIT 2,3 结果 %v, 想要 %v", got, want)
  221. }
  222. }
  223. // TestP1SQLState 关键错误的 SQLSTATE 映射。
  224. func TestP1SQLState(t *testing.T) {
  225. db := openTestDB(t)
  226. if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
  227. t.Fatal(err)
  228. }
  229. if _, err := db.Exec(`INSERT INTO t (id) VALUES (?)`, 1); err != nil {
  230. t.Fatal(err)
  231. }
  232. // 表不存在 → 42S02
  233. _, err := db.Query(`SELECT * FROM nope`)
  234. if st := sqlState(err); st != "42S02" {
  235. t.Fatalf("表不存在 state=%q, 想要 42S02 (err=%v)", st, err)
  236. }
  237. // 主键重复 → 23000
  238. _, err = db.Exec(`INSERT INTO t (id) VALUES (?)`, 1)
  239. if st := sqlState(err); st != "23000" {
  240. t.Fatalf("主键重复 state=%q, 想要 23000 (err=%v)", st, err)
  241. }
  242. // 语法错误 → 42000
  243. _, err = db.Exec(`SELEC bad`)
  244. if st := sqlState(err); st != "42000" {
  245. t.Fatalf("语法错误 state=%q, 想要 42000 (err=%v)", st, err)
  246. }
  247. // 列不存在 → 42S22
  248. _, err = db.Query(`SELECT nope FROM t`)
  249. if st := sqlState(err); st != "42S22" {
  250. t.Fatalf("列不存在 state=%q, 想要 42S22 (err=%v)", st, err)
  251. }
  252. }
  253. // TestP1ReadOnlyDSN mode=ro: 可读不可写 (写报 25008); cache=private 独立实例持久化。
  254. func TestP1ReadOnlyDSN(t *testing.T) {
  255. path := filepath.Join(t.TempDir(), "ro.db")
  256. // 先写数据再关闭
  257. w, err := sql.Open("vrtx", path)
  258. if err != nil {
  259. t.Fatal(err)
  260. }
  261. if _, err := w.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
  262. t.Fatal(err)
  263. }
  264. if _, err := w.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 1, 10); err != nil {
  265. t.Fatal(err)
  266. }
  267. if err := w.Close(); err != nil {
  268. t.Fatal(err)
  269. }
  270. // 只读打开
  271. ro, err := sql.Open("vrtx", path+"?mode=ro")
  272. if err != nil {
  273. t.Fatal(err)
  274. }
  275. defer ro.Close()
  276. var cnt int
  277. if err := ro.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil {
  278. t.Fatalf("只读 SELECT 失败: %v", err)
  279. }
  280. if cnt != 1 {
  281. t.Fatalf("只读读到 %d 行, 想要 1", cnt)
  282. }
  283. _, err = ro.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 2, 20)
  284. if st := sqlState(err); st != "25008" {
  285. t.Fatalf("只读写入 state=%q, 想要 25008 (err=%v)", st, err)
  286. }
  287. if err := ro.Close(); err != nil {
  288. t.Fatal(err)
  289. }
  290. // cache=private: 独立实例, 数据持久化、可继续写
  291. pv, err := sql.Open("vrtx", path+"?cache=private")
  292. if err != nil {
  293. t.Fatal(err)
  294. }
  295. if _, err := pv.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 2, 20); err != nil {
  296. t.Fatalf("private 实例写入: %v", err)
  297. }
  298. if err := pv.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil {
  299. t.Fatal(err)
  300. }
  301. if cnt != 2 {
  302. t.Fatalf("private 实例应读到 2 行, 实际 %d", cnt)
  303. }
  304. pv.Close()
  305. // 关闭后普通连接能看到 private 写入 (独立实例也正确落盘)
  306. plain, err := sql.Open("vrtx", path)
  307. if err != nil {
  308. t.Fatal(err)
  309. }
  310. defer plain.Close()
  311. if err := plain.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil {
  312. t.Fatal(err)
  313. }
  314. if cnt != 2 {
  315. t.Fatalf("持久化后应 2 行, 实际 %d (%s)", cnt, fmt.Sprint(cnt))
  316. }
  317. }
  318. // TestP1Vacuum VACUUM 语句: 整库重建后行数据原样、文件可继续读写。
  319. // 顺带验证三种典型调用形态: 裸语句 / 多行空白 / 大小写混合。
  320. func TestP1Vacuum(t *testing.T) {
  321. db := openTestDB(t)
  322. if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
  323. t.Fatal(err)
  324. }
  325. for i := 1; i <= 10; i++ {
  326. if _, err := db.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, i, i*10); err != nil {
  327. t.Fatal(err)
  328. }
  329. }
  330. // 删掉一半再 VACUUM: 有垃圾可收, 更接近真实场景
  331. if _, err := db.Exec(`DELETE FROM t WHERE id > ?`, 5); err != nil {
  332. t.Fatal(err)
  333. }
  334. res, err := db.Exec(`VACUUM`)
  335. if err != nil {
  336. t.Fatalf("vacuum: %v", err)
  337. }
  338. if n, err := res.RowsAffected(); err != nil || n != 5 {
  339. t.Fatalf("RowsAffected 应为复制的 5 行, 实际 %d (%v)", n, err)
  340. }
  341. // 行数据原样
  342. var cnt int
  343. if err := db.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil {
  344. t.Fatal(err)
  345. }
  346. if cnt != 5 {
  347. t.Fatalf("VACUUM 后应 5 行, 实际 %d", cnt)
  348. }
  349. var v int
  350. if err := db.QueryRow(`SELECT v FROM t WHERE id = ?`, 3).Scan(&v); err != nil {
  351. t.Fatal(err)
  352. }
  353. if v != 30 {
  354. t.Fatalf("行数据应为 30, 实际 %d", v)
  355. }
  356. // 重建后可继续读写
  357. if _, err := db.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 100, 1); err != nil {
  358. t.Fatalf("VACUUM 后插入失败: %v", err)
  359. }
  360. // 大小写/空白形态
  361. if _, err := db.Exec(" vacuum "); err != nil {
  362. t.Fatalf("小写带空白 VACUUM: %v", err)
  363. }
  364. if err := db.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil {
  365. t.Fatal(err)
  366. }
  367. if cnt != 6 {
  368. t.Fatalf("第二次 VACUUM 后应 6 行, 实际 %d", cnt)
  369. }
  370. }
  371. // TestP1VacuumInTxn 活跃事务中 VACUUM 必须被拒 (引擎层 ErrTxnActive 透传)。
  372. func TestP1VacuumInTxn(t *testing.T) {
  373. db := openTestDB(t)
  374. if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
  375. t.Fatal(err)
  376. }
  377. tx, err := db.Begin()
  378. if err != nil {
  379. t.Fatal(err)
  380. }
  381. if _, err := tx.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 1, 1); err != nil {
  382. t.Fatal(err)
  383. }
  384. if _, err := tx.Exec(`VACUUM`); err == nil {
  385. t.Fatal("事务中 VACUUM 应被拒绝")
  386. }
  387. if err := tx.Rollback(); err != nil {
  388. t.Fatal(err)
  389. }
  390. // 回滚后 VACUUM 恢复可用
  391. if _, err := db.Exec(`VACUUM`); err != nil {
  392. t.Fatalf("回滚后 VACUUM: %v", err)
  393. }
  394. }