driver_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. package vrtxsql
  2. // database/sql 驱动测试: 走标准库 API 全链路 (Open/Exec/Query/Tx/Prepare),
  3. // 覆盖 M1 (INSERT/SELECT/WHERE/LIMIT) + M2 (UPDATE/DELETE/事务/IS NULL)
  4. // + M3 (CREATE/DROP TABLE + 聚合)。
  5. import (
  6. "context"
  7. "database/sql"
  8. "errors"
  9. "fmt"
  10. "os"
  11. "path/filepath"
  12. "reflect"
  13. "strings"
  14. "testing"
  15. "time"
  16. "git.ycl.pub/yl32940/TsDB2/DB/core"
  17. )
  18. func openTestDB(t *testing.T) *sql.DB {
  19. t.Helper()
  20. dsn := filepath.Join(t.TempDir(), "driver_test.db")
  21. db, err := sql.Open("vrtx", dsn)
  22. if err != nil {
  23. t.Fatalf("open: %v", err)
  24. }
  25. t.Cleanup(func() { _ = db.Close() })
  26. return db
  27. }
  28. func TestDriverCRUDAndQuery(t *testing.T) {
  29. db := openTestDB(t)
  30. // M3: DDL
  31. if _, err := db.Exec(`CREATE TABLE users (
  32. id BIGINT PRIMARY KEY,
  33. name VARCHAR(32) NOT NULL,
  34. age INT,
  35. email VARCHAR(64) UNIQUE
  36. )`); err != nil {
  37. t.Fatalf("create table: %v", err)
  38. }
  39. // M1: INSERT (LastInsertId)
  40. res, err := db.Exec(`INSERT INTO users (id, name, age, email) VALUES (?, ?, ?, ?)`,
  41. 1, "alice", 30, "a@x.com")
  42. if err != nil {
  43. t.Fatalf("insert: %v", err)
  44. }
  45. if id, _ := res.LastInsertId(); id != 1 {
  46. t.Fatalf("LastInsertId=%d, 想要 1", id)
  47. }
  48. if n, _ := res.RowsAffected(); n != 1 {
  49. t.Fatalf("RowsAffected=%d, 想要 1", n)
  50. }
  51. // 省略列 (email NULL)
  52. if _, err := db.Exec(`INSERT INTO users (id, name, age) VALUES (?, ?, ?)`, 2, "bob", 25); err != nil {
  53. t.Fatalf("insert 2: %v", err)
  54. }
  55. // 唯一索引: 重复 email 必须报错
  56. if _, err := db.Exec(`INSERT INTO users (id, name, email) VALUES (?, ?, ?)`, 3, "carl", "a@x.com"); err == nil {
  57. t.Fatalf("唯一索引应拒绝重复 email")
  58. }
  59. // M1: SELECT 投影 + WHERE + ORDER BY + LIMIT
  60. rows, err := db.Query(`SELECT id, name, age FROM users WHERE age >= ? ORDER BY age DESC LIMIT 10`, 18)
  61. if err != nil {
  62. t.Fatalf("query: %v", err)
  63. }
  64. defer rows.Close()
  65. type u struct {
  66. id int64
  67. name string
  68. age int64
  69. hasAge bool
  70. }
  71. var got []u
  72. for rows.Next() {
  73. var r u
  74. var name []byte
  75. var age sql.NullInt64
  76. if err := rows.Scan(&r.id, &name, &age); err != nil {
  77. t.Fatalf("scan: %v", err)
  78. }
  79. r.name = string(name)
  80. if age.Valid {
  81. r.age = age.Int64
  82. r.hasAge = true
  83. }
  84. got = append(got, r)
  85. }
  86. if len(got) != 2 || got[0].name != "alice" || got[1].name != "bob" {
  87. t.Fatalf("查询结果 %+v 不对", got)
  88. }
  89. if !got[0].hasAge || got[0].age != 30 {
  90. t.Fatalf("age 投影不对: %+v", got[0])
  91. }
  92. // IN / OR / NOT
  93. var cnt int
  94. if err := db.QueryRow(`SELECT COUNT(*) FROM users WHERE id IN (?, ?)`, 1, 2).Scan(&cnt); err != nil || cnt != 2 {
  95. t.Fatalf("IN 查询: cnt=%d err=%v", cnt, err)
  96. }
  97. if err := db.QueryRow(`SELECT COUNT(*) FROM users WHERE NOT (id = ?)`, 1).Scan(&cnt); err != nil || cnt != 1 {
  98. t.Fatalf("NOT 查询: cnt=%d err=%v", cnt, err)
  99. }
  100. // M2: IS NULL (email 为 NULL 的 bob)
  101. if err := db.QueryRow(`SELECT COUNT(*) FROM users WHERE email IS NULL`).Scan(&cnt); err != nil || cnt != 1 {
  102. t.Fatalf("IS NULL: cnt=%d err=%v", cnt, err)
  103. }
  104. if err := db.QueryRow(`SELECT COUNT(*) FROM users WHERE email IS NOT NULL`).Scan(&cnt); err != nil || cnt != 1 {
  105. t.Fatalf("IS NOT NULL: cnt=%d err=%v", cnt, err)
  106. }
  107. // M2: UPDATE (RowsAffected + 唯一冲突传播)
  108. res, err = db.Exec(`UPDATE users SET age = ? WHERE name = ?`, 31, "alice")
  109. if err != nil {
  110. t.Fatalf("update: %v", err)
  111. }
  112. if n, _ := res.RowsAffected(); n != 1 {
  113. t.Fatalf("update affected=%d", n)
  114. }
  115. var age int64
  116. if err := db.QueryRow(`SELECT age FROM users WHERE id = ?`, 1).Scan(&age); err != nil || age != 31 {
  117. t.Fatalf("update 未生效: age=%d err=%v", age, err)
  118. }
  119. // M2: DELETE
  120. res, err = db.Exec(`DELETE FROM users WHERE id = ?`, 2)
  121. if err != nil {
  122. t.Fatalf("delete: %v", err)
  123. }
  124. if n, _ := res.RowsAffected(); n != 1 {
  125. t.Fatalf("delete affected=%d", n)
  126. }
  127. // M3: 聚合 SUM/MIN/MAX/AVG
  128. var sum float64
  129. if err := db.QueryRow(`SELECT SUM(age) FROM users`).Scan(&sum); err != nil || sum != 31 {
  130. t.Fatalf("SUM: %v %v", sum, err)
  131. }
  132. var avg float64
  133. if err := db.QueryRow(`SELECT AVG(age) FROM users`).Scan(&avg); err != nil || avg != 31 {
  134. t.Fatalf("AVG: %v %v", avg, err)
  135. }
  136. // Prepare 路径 (database/sql stmt)
  137. stmt, err := db.Prepare(`SELECT name FROM users WHERE id = ?`)
  138. if err != nil {
  139. t.Fatalf("prepare: %v", err)
  140. }
  141. defer stmt.Close()
  142. var nm string
  143. if err := stmt.QueryRow(1).Scan(&nm); err != nil || nm != "alice" {
  144. t.Fatalf("prepared query: %q %v", nm, err)
  145. }
  146. }
  147. func TestDriverTransaction(t *testing.T) {
  148. db := openTestDB(t)
  149. if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
  150. t.Fatalf("create: %v", err)
  151. }
  152. tx, err := db.Begin()
  153. if err != nil {
  154. t.Fatalf("begin: %v", err)
  155. }
  156. if _, err := tx.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 1, 100); err != nil {
  157. t.Fatalf("tx insert: %v", err)
  158. }
  159. if err := tx.Rollback(); err != nil {
  160. t.Fatalf("rollback: %v", err)
  161. }
  162. var cnt int
  163. if err := db.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil || cnt != 0 {
  164. t.Fatalf("rollback 后应有 0 行, 实际 %d (%v)", cnt, err)
  165. }
  166. tx2, err := db.Begin()
  167. if err != nil {
  168. t.Fatalf("begin2: %v", err)
  169. }
  170. if _, err := tx2.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 2, 200); err != nil {
  171. t.Fatalf("tx2 insert: %v", err)
  172. }
  173. if err := tx2.Commit(); err != nil {
  174. t.Fatalf("commit: %v", err)
  175. }
  176. if err := db.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil || cnt != 1 {
  177. t.Fatalf("commit 后应有 1 行, 实际 %d (%v)", cnt, err)
  178. }
  179. }
  180. func TestDriverUnsupportedAndErrors(t *testing.T) {
  181. db := openTestDB(t)
  182. if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
  183. t.Fatalf("create: %v", err)
  184. }
  185. // 不支持语法: 明确报错而非静默
  186. if _, err := db.Query(`SELECT * FROM t GROUP BY v`); err == nil {
  187. t.Fatalf("GROUP BY 应报错")
  188. }
  189. if _, err := db.Exec(`DROP TABLE t`); err != nil {
  190. t.Fatalf("drop: %v", err)
  191. }
  192. // IF EXISTS
  193. if _, err := db.Exec(`DROP TABLE IF EXISTS t`); err != nil {
  194. t.Fatalf("drop if exists: %v", err)
  195. }
  196. // SELECT 走 Exec / INSERT 走 Query 的错路提示
  197. if _, err := db.Exec(`SELECT * FROM t`); err == nil {
  198. t.Fatalf("SELECT 用 Exec 应提示错路")
  199. }
  200. }
  201. // TestDriverSharedEngine 同一路径的两个 *sql.DB 共享同一引擎实例
  202. // (注册表语义): 经 db1 建表, db2 立即可见。
  203. func TestDriverSharedEngine(t *testing.T) {
  204. dsn := filepath.Join(t.TempDir(), "shared.db")
  205. db1, err := sql.Open("vrtx", dsn)
  206. if err != nil {
  207. t.Fatal(err)
  208. }
  209. defer db1.Close()
  210. db2, err := sql.Open("vrtx", dsn)
  211. if err != nil {
  212. t.Fatal(err)
  213. }
  214. defer db2.Close()
  215. if _, err := db1.Exec(`CREATE TABLE s (id BIGINT PRIMARY KEY)`); err != nil {
  216. t.Fatalf("db1 create: %v", err)
  217. }
  218. if _, err := db2.Exec(`INSERT INTO s (id) VALUES (?)`, 1); err != nil {
  219. t.Fatalf("db2 insert (db1 建的表不可见 = 引擎没共享): %v", err)
  220. }
  221. _ = os.Remove(dsn)
  222. }
  223. // openCore 打开一个裸引擎供包内钻取 (不经 database/sql)。
  224. func openCore(t *testing.T) *core.DB {
  225. t.Helper()
  226. db, err := core.Open(t.TempDir() + "/exec_drill.db")
  227. if err != nil {
  228. t.Fatalf("open: %v", err)
  229. }
  230. t.Cleanup(func() { _ = db.Close() })
  231. return db
  232. }
  233. // TestDriverContextCancelWhileLocked 验证 ctx 在"排队等锁"阶段被尊重:
  234. // db1 用未提交事务持锁, db2 的短超时 Query 必须在约 ctx 超时后返回
  235. // DeadlineExceeded, 而不是无视超时一直阻塞到事务结束。
  236. func TestDriverContextCancelWhileLocked(t *testing.T) {
  237. dsn := filepath.Join(t.TempDir(), "ctx.db")
  238. db1, err := sql.Open("vrtx", dsn)
  239. if err != nil {
  240. t.Fatal(err)
  241. }
  242. defer db1.Close()
  243. db2, err := sql.Open("vrtx", dsn)
  244. if err != nil {
  245. t.Fatal(err)
  246. }
  247. defer db2.Close()
  248. if _, err := db1.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
  249. t.Fatal(err)
  250. }
  251. tx, err := db1.Begin()
  252. if err != nil {
  253. t.Fatal(err)
  254. }
  255. if _, err := tx.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 1, 1); err != nil {
  256. t.Fatal(err)
  257. }
  258. defer tx.Rollback()
  259. ctx, cancel := context.WithTimeout(context.Background(), 80*time.Millisecond)
  260. defer cancel()
  261. start := time.Now()
  262. _, err = db2.QueryContext(ctx, `SELECT * FROM t`)
  263. elapsed := time.Since(start)
  264. if err == nil {
  265. t.Fatal("期望 ctx 超时报错, 实际查询成功")
  266. }
  267. if !errors.Is(err, context.DeadlineExceeded) {
  268. t.Fatalf("期望 context.DeadlineExceeded, 实际 %v", err)
  269. }
  270. if elapsed > 800*time.Millisecond {
  271. t.Fatalf("ctx 未被尊重: 等了 %v 才返回", elapsed)
  272. }
  273. }
  274. // TestDriverStreamingAndColumnTypes 验证普通 SELECT 流式返回 (行数/顺序/内容)
  275. // 及 RowsColumnType* 元数据 (DatabaseTypeName/ScanType/Length)。
  276. func TestDriverStreamingAndColumnTypes(t *testing.T) {
  277. db := openTestDB(t)
  278. if _, err := db.Exec(`CREATE TABLE items (
  279. id BIGINT PRIMARY KEY,
  280. name VARCHAR(40) NOT NULL,
  281. price DOUBLE,
  282. tag VARCHAR(20)
  283. )`); err != nil {
  284. t.Fatal(err)
  285. }
  286. const n = 10000
  287. for b := 0; b < n; b += 500 {
  288. var sb strings.Builder
  289. sb.WriteString(`INSERT INTO items (id, name, price, tag) VALUES `)
  290. args := make([]interface{}, 0, 500*4)
  291. rows := n - b
  292. if rows > 500 {
  293. rows = 500
  294. }
  295. for i := 0; i < rows; i++ {
  296. if i > 0 {
  297. sb.WriteString(",")
  298. }
  299. sb.WriteString("(?,?,?,?)")
  300. id := b + i + 1
  301. args = append(args, id, fmt.Sprintf("item%d", id), float64(id)*1.5, nil)
  302. }
  303. if _, err := db.Exec(sb.String(), args...); err != nil {
  304. t.Fatalf("batch @%d: %v", b, err)
  305. }
  306. }
  307. rs, err := db.Query(`SELECT id, name, price FROM items ORDER BY id ASC`)
  308. if err != nil {
  309. t.Fatal(err)
  310. }
  311. defer rs.Close()
  312. cts, err := rs.ColumnTypes()
  313. if err != nil {
  314. t.Fatal(err)
  315. }
  316. if len(cts) != 3 {
  317. t.Fatalf("列类型数=%d, 想要 3", len(cts))
  318. }
  319. if dbn := cts[0].DatabaseTypeName(); dbn != "BIGINT" {
  320. t.Fatalf("id DatabaseTypeName=%q, 想要 BIGINT", dbn)
  321. }
  322. if st := cts[1].ScanType(); st.Kind() != reflect.String {
  323. t.Fatalf("name ScanType=%v, 想要 string", st)
  324. }
  325. if dbn := cts[2].DatabaseTypeName(); dbn != "DOUBLE" {
  326. t.Fatalf("price DatabaseTypeName=%q, 想要 DOUBLE", dbn)
  327. }
  328. if l, ok := cts[1].Length(); !ok || l < 40 {
  329. t.Fatalf("name Length=%d ok=%v (想要 >=40)", l, ok)
  330. }
  331. count := 0
  332. for rs.Next() {
  333. var id int64
  334. var name []byte
  335. var price float64
  336. if err := rs.Scan(&id, &name, &price); err != nil {
  337. t.Fatal(err)
  338. }
  339. count++
  340. if id != int64(count) {
  341. t.Fatalf("顺序错位: 第 %d 行 id=%d", count, id)
  342. }
  343. if string(name) != fmt.Sprintf("item%d", id) {
  344. t.Fatalf("name 错位: id=%d name=%s", id, name)
  345. }
  346. if price != float64(id)*1.5 {
  347. t.Fatalf("price 错位: id=%d price=%v", id, price)
  348. }
  349. }
  350. if err := rs.Err(); err != nil {
  351. t.Fatal(err)
  352. }
  353. if count != n {
  354. t.Fatalf("流式行数=%d, 想要 %d", count, n)
  355. }
  356. }
  357. // TestDriverPreparedQueryContext 验证 prepared 语句走 driver.StmtQueryContext
  358. // (公开 QueryContext, 响应 ctx), 结果正确。
  359. func TestDriverPreparedQueryContext(t *testing.T) {
  360. db := openTestDB(t)
  361. if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
  362. t.Fatal(err)
  363. }
  364. if _, err := db.Exec(`INSERT INTO t (id, v) VALUES (?,?),(?,?)`, 1, 10, 2, 20); err != nil {
  365. t.Fatal(err)
  366. }
  367. stmt, err := db.Prepare(`SELECT v FROM t WHERE id = ?`)
  368. if err != nil {
  369. t.Fatal(err)
  370. }
  371. defer stmt.Close()
  372. ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
  373. defer cancel()
  374. rs, err := stmt.QueryContext(ctx, 2)
  375. if err != nil {
  376. t.Fatalf("prepared QueryContext: %v", err)
  377. }
  378. defer rs.Close()
  379. if !rs.Next() {
  380. t.Fatal("prepared 查询无行")
  381. }
  382. var v int64
  383. if err := rs.Scan(&v); err != nil {
  384. t.Fatal(err)
  385. }
  386. if v != 20 {
  387. t.Fatalf("v=%d, 想要 20", v)
  388. }
  389. }
  390. // TestDriverDateTimeSymmetry 时间列读写对称: 写 time.Time, SELECT 读回也是
  391. // time.Time; 覆盖 time.Time 参数、时间字符串字面量、范围、MIN/MAX 与列类型元数据。
  392. func TestDriverDateTimeSymmetry(t *testing.T) {
  393. db := openTestDB(t)
  394. if _, err := db.Exec(`CREATE TABLE events (
  395. id BIGINT PRIMARY KEY,
  396. name VARCHAR(32),
  397. created_at DATETIME
  398. )`); err != nil {
  399. t.Fatalf("create: %v", err)
  400. }
  401. // 毫秒对齐的固定时间 (本地时区)
  402. t1 := time.Date(2026, 9, 29, 12, 0, 0, 0, time.Local)
  403. t2 := time.Date(2026, 9, 29, 18, 30, 45, 0, time.Local)
  404. t3 := time.Date(2026, 10, 1, 0, 0, 0, 0, time.Local)
  405. for i, tv := range []time.Time{t1, t2, t3} {
  406. if _, err := db.Exec(`INSERT INTO events (id, name, created_at) VALUES (?, ?, ?)`,
  407. i+1, fmt.Sprintf("e%d", i+1), tv); err != nil {
  408. t.Fatalf("insert%d: %v", i+1, err)
  409. }
  410. }
  411. // 读回对称 + 列类型元数据
  412. rs, err := db.Query(`SELECT id, created_at FROM events ORDER BY id ASC`)
  413. if err != nil {
  414. t.Fatalf("query: %v", err)
  415. }
  416. defer rs.Close()
  417. cts, err := rs.ColumnTypes()
  418. if err != nil {
  419. t.Fatalf("column types: %v", err)
  420. }
  421. if dbn := cts[1].DatabaseTypeName(); dbn != "DATETIME" {
  422. t.Fatalf("created_at DatabaseTypeName=%q, 想要 DATETIME", dbn)
  423. }
  424. if st := cts[1].ScanType(); st != reflect.TypeOf(time.Time{}) {
  425. t.Fatalf("created_at ScanType=%v, 想要 time.Time", st)
  426. }
  427. want := []time.Time{t1, t2, t3}
  428. i := 0
  429. for rs.Next() {
  430. var id int64
  431. var created time.Time
  432. if err := rs.Scan(&id, &created); err != nil {
  433. t.Fatalf("scan: %v", err)
  434. }
  435. if !created.Equal(want[i]) {
  436. t.Fatalf("行 %d 时间不对称: 写 %v 读 %v", i, want[i], created)
  437. }
  438. i++
  439. }
  440. if err := rs.Err(); err != nil {
  441. t.Fatal(err)
  442. }
  443. if i != 3 {
  444. t.Fatalf("读到 %d 行, 想要 3", i)
  445. }
  446. // WHERE 用 time.Time 参数
  447. var name []byte
  448. if err := db.QueryRow(`SELECT name FROM events WHERE created_at = ?`, t2).Scan(&name); err != nil {
  449. t.Fatalf("WHERE time.Time 参数: %v", err)
  450. }
  451. if string(name) != "e2" {
  452. t.Fatalf("命中 %q, 想要 e2", name)
  453. }
  454. // WHERE 用时间字符串字面量 (MySQL 格式, 本地时区)
  455. var id int64
  456. if err := db.QueryRow(`SELECT id FROM events WHERE created_at = '2026-09-29 12:00:00'`).Scan(&id); err != nil {
  457. t.Fatalf("WHERE 时间字面量: %v", err)
  458. }
  459. if id != 1 {
  460. t.Fatalf("命中 id=%d, 想要 1", id)
  461. }
  462. // 时间范围
  463. var cnt int
  464. if err := db.QueryRow(`SELECT COUNT(*) FROM events WHERE created_at > ?`, t1).Scan(&cnt); err != nil {
  465. t.Fatalf("时间范围: %v", err)
  466. }
  467. if cnt != 2 {
  468. t.Fatalf("范围计数=%d, 想要 2", cnt)
  469. }
  470. // MIN/MAX 返回 time.Time
  471. var mn, mx time.Time
  472. if err := db.QueryRow(`SELECT MIN(created_at) FROM events`).Scan(&mn); err != nil {
  473. t.Fatalf("MIN: %v", err)
  474. }
  475. if !mn.Equal(t1) {
  476. t.Fatalf("MIN=%v, 想要 %v", mn, t1)
  477. }
  478. if err := db.QueryRow(`SELECT MAX(created_at) FROM events`).Scan(&mx); err != nil {
  479. t.Fatalf("MAX: %v", err)
  480. }
  481. if !mx.Equal(t3) {
  482. t.Fatalf("MAX=%v, 想要 %v", mx, t3)
  483. }
  484. }
  485. // TestDriverTableOptions GORM/xorm 风格的表选项 (ENGINE/CHARSET/...) 解析并忽略。
  486. func TestDriverTableOptions(t *testing.T) {
  487. db := openTestDB(t)
  488. _, err := db.Exec(`CREATE TABLE t (
  489. id BIGINT PRIMARY KEY,
  490. name VARCHAR(32)
  491. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表' AUTO_INCREMENT=1 ROW_FORMAT=DYNAMIC`)
  492. if err != nil {
  493. t.Fatalf("带表选项的 CREATE TABLE: %v", err)
  494. }
  495. if _, err := db.Exec(`INSERT INTO t (id, name) VALUES (?, ?)`, 1, "a"); err != nil {
  496. t.Fatalf("insert: %v", err)
  497. }
  498. // 白名单之外的选项名必须报错
  499. if _, err := db.Exec(`CREATE TABLE t2 (id BIGINT PRIMARY KEY) FROBNICATOR=1`); err == nil {
  500. t.Fatalf("未知表选项应报错")
  501. }
  502. }