case_insensitive_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. package vrtxsql
  2. // 列名大小写不敏感回归测试:
  3. //
  4. // 驱动的 findField 用 strings.EqualFold 做大小写不敏感的存在性校验, 但构造
  5. // 引擎条件/投影/排序时必须改用引擎规范名 string(fi.FieldName) —— 引擎侧
  6. // (CmpCondition.Match / NullCondition / findFieldIndex / 排序键解析) 一律按
  7. // 精确字节匹配列名。用户写 "Title" 而引擎列是 "title" 时, 若把 SQL 原文列名
  8. // 直接传进引擎 API, WHERE 会静默恒假 (UPDATE/DELETE 作用 0 行), ORDER BY /
  9. // 聚合会取不到列。本文件覆盖所有出口: WHERE (=/IN/LIKE/IS NULL)、UPDATE SET、
  10. // DELETE、ORDER BY、SELECT 投影、聚合, 以及 JOIN 的 ON/WHERE/ORDER BY。
  11. import (
  12. "database/sql"
  13. "strings"
  14. "testing"
  15. )
  16. // caseFixture 建小写列名 (name/age/email) 的 people 表并灌 4 行数据。
  17. func caseFixture(t *testing.T, db *sql.DB) {
  18. t.Helper()
  19. exec := func(q string, args ...interface{}) {
  20. t.Helper()
  21. if _, err := db.Exec(q, args...); err != nil {
  22. t.Fatalf("%s: %v", q, err)
  23. }
  24. }
  25. exec(`CREATE TABLE people (
  26. id BIGINT PRIMARY KEY,
  27. name VARCHAR(32) NOT NULL,
  28. age INT,
  29. email VARCHAR(64)
  30. )`)
  31. exec(`INSERT INTO people (id, name, age, email) VALUES (?, ?, ?, ?), (?, ?, ?, ?), (?, ?, ?, ?), (?, ?, ?, ?)`,
  32. 1, "alice", 30, "a@x.com",
  33. 2, "bob", 25, nil,
  34. 3, "carl", 40, nil,
  35. 4, "dora", 20, "d@x.com")
  36. }
  37. // TestCaseInsensitiveWhereDML WHERE/UPDATE/DELETE 大小写混写命中。
  38. func TestCaseInsensitiveWhereDML(t *testing.T) {
  39. db := openTestDB(t)
  40. caseFixture(t, db)
  41. // SELECT ... WHERE Name = ? (大写列名, 引擎列小写)
  42. var name string
  43. if err := db.QueryRow(`SELECT name FROM people WHERE Name = ?`, "alice").Scan(&name); err != nil {
  44. t.Fatalf("WHERE Name: %v", err)
  45. }
  46. if name != "alice" {
  47. t.Fatalf("name=%q, 想要 alice", name)
  48. }
  49. // 不等 / 范围 / IN / NOT IN 各来一遍
  50. var cnt int
  51. for q, want := range map[string]int{
  52. `SELECT COUNT(*) FROM people WHERE AGE > 25`: 2,
  53. `SELECT COUNT(*) FROM people WHERE Age <= 25`: 2,
  54. `SELECT COUNT(*) FROM people WHERE NAME IN ('alice', 'bob')`: 2,
  55. `SELECT COUNT(*) FROM people WHERE Name NOT IN ('alice', 'bob')`: 2,
  56. } {
  57. if err := db.QueryRow(q).Scan(&cnt); err != nil || cnt != want {
  58. t.Fatalf("%s: cnt=%d err=%v, 想要 %d", q, cnt, err, want)
  59. }
  60. }
  61. // LIKE 大小写混写
  62. if err := db.QueryRow(`SELECT COUNT(*) FROM people WHERE Name LIKE 'a%'`).Scan(&cnt); err != nil || cnt != 1 {
  63. t.Fatalf("LIKE: cnt=%d err=%v, 想要 1", cnt, err)
  64. }
  65. // UPDATE t SET Name=? WHERE NAME=? —— SET 与 WHERE 都混写大小写
  66. res, err := db.Exec(`UPDATE people SET Name = ? WHERE NAME = ?`, "alice2", "alice")
  67. if err != nil {
  68. t.Fatalf("UPDATE: %v", err)
  69. }
  70. if n, _ := res.RowsAffected(); n != 1 {
  71. t.Fatalf("UPDATE RowsAffected=%d, 想要 1", n)
  72. }
  73. if err := db.QueryRow(`SELECT name FROM people WHERE id = 1`).Scan(&name); err != nil {
  74. t.Fatalf("回读 id=1: %v", err)
  75. }
  76. if name != "alice2" {
  77. t.Fatalf("UPDATE 后 name=%q, 想要 alice2", name)
  78. }
  79. // DELETE WHERE AGE >= ? 生效
  80. res, err = db.Exec(`DELETE FROM people WHERE AGE >= ?`, 40)
  81. if err != nil {
  82. t.Fatalf("DELETE: %v", err)
  83. }
  84. if n, _ := res.RowsAffected(); n != 1 {
  85. t.Fatalf("DELETE RowsAffected=%d, 想要 1 (carl, age=40)", n)
  86. }
  87. if err := db.QueryRow(`SELECT COUNT(*) FROM people`).Scan(&cnt); err != nil || cnt != 3 {
  88. t.Fatalf("DELETE 后 cnt=%d err=%v, 想要 3", cnt, err)
  89. }
  90. }
  91. // TestCaseInsensitiveOrderBy ORDER BY 大小写混用正常排序。
  92. func TestCaseInsensitiveOrderBy(t *testing.T) {
  93. db := openTestDB(t)
  94. caseFixture(t, db)
  95. var names []string
  96. rows, err := db.Query(`SELECT name FROM people ORDER BY Name`)
  97. if err != nil {
  98. t.Fatalf("ORDER BY Name: %v", err)
  99. }
  100. for rows.Next() {
  101. var s string
  102. if err := rows.Scan(&s); err != nil {
  103. t.Fatalf("scan: %v", err)
  104. }
  105. names = append(names, s)
  106. }
  107. rows.Close()
  108. if err := rows.Err(); err != nil {
  109. t.Fatal(err)
  110. }
  111. // name 列按字典序: alice bob carl dora
  112. if strings.Join(names, ",") != "alice,bob,carl,dora" {
  113. t.Fatalf("ORDER BY Name = %v", names)
  114. }
  115. // ORDER BY AGE DESC: 40 30 25 20 → carl alice bob dora
  116. names = names[:0]
  117. rows, err = db.Query(`SELECT name FROM people ORDER BY AGE DESC`)
  118. if err != nil {
  119. t.Fatalf("ORDER BY AGE DESC: %v", err)
  120. }
  121. for rows.Next() {
  122. var s string
  123. if err := rows.Scan(&s); err != nil {
  124. t.Fatalf("scan: %v", err)
  125. }
  126. names = append(names, s)
  127. }
  128. rows.Close()
  129. if err := rows.Err(); err != nil {
  130. t.Fatal(err)
  131. }
  132. if strings.Join(names, ",") != "carl,alice,bob,dora" {
  133. t.Fatalf("ORDER BY AGE DESC = %v", names)
  134. }
  135. // 多键混用: ORDER BY AGE DESC, Name
  136. names = names[:0]
  137. rows, err = db.Query(`SELECT name FROM people ORDER BY Age DESC, NAME`)
  138. if err != nil {
  139. t.Fatalf("多键 ORDER BY: %v", err)
  140. }
  141. for rows.Next() {
  142. var s string
  143. if err := rows.Scan(&s); err != nil {
  144. t.Fatalf("scan: %v", err)
  145. }
  146. names = append(names, s)
  147. }
  148. rows.Close()
  149. if err := rows.Err(); err != nil {
  150. t.Fatal(err)
  151. }
  152. if strings.Join(names, ",") != "carl,alice,bob,dora" {
  153. t.Fatalf("多键 ORDER BY = %v", names)
  154. }
  155. }
  156. // TestCaseInsensitiveProjection SELECT 投影混写大小写出数, 表头为引擎规范名。
  157. func TestCaseInsensitiveProjection(t *testing.T) {
  158. db := openTestDB(t)
  159. caseFixture(t, db)
  160. rows, err := db.Query(`SELECT Name, AGE FROM people WHERE Id = ?`, 1)
  161. if err != nil {
  162. t.Fatalf("投影混写: %v", err)
  163. }
  164. defer rows.Close()
  165. cols, err := rows.Columns()
  166. if err != nil {
  167. t.Fatal(err)
  168. }
  169. if strings.Join(cols, ",") != "name,age" {
  170. t.Fatalf("表头=%v, 想要 [name age] (引擎规范名)", cols)
  171. }
  172. if !rows.Next() {
  173. t.Fatal("应有一行")
  174. }
  175. var name string
  176. var age int
  177. if err := rows.Scan(&name, &age); err != nil {
  178. t.Fatalf("scan: %v", err)
  179. }
  180. if name != "alice" || age != 30 {
  181. t.Fatalf("行=(%q,%d), 想要 (alice,30)", name, age)
  182. }
  183. }
  184. // TestCaseInsensitiveIsNull IS NULL 大小写混写按 NULL 位图命中。
  185. func TestCaseInsensitiveIsNull(t *testing.T) {
  186. db := openTestDB(t)
  187. caseFixture(t, db)
  188. var cnt int
  189. // email 列实际小写, 查询写 Email; bob/carl 的 email 为 NULL
  190. if err := db.QueryRow(`SELECT COUNT(*) FROM people WHERE Email IS NULL`).Scan(&cnt); err != nil || cnt != 2 {
  191. t.Fatalf("Email IS NULL: cnt=%d err=%v, 想要 2", cnt, err)
  192. }
  193. if err := db.QueryRow(`SELECT COUNT(*) FROM people WHERE EMAIL IS NOT NULL`).Scan(&cnt); err != nil || cnt != 2 {
  194. t.Fatalf("EMAIL IS NOT NULL: cnt=%d err=%v, 想要 2", cnt, err)
  195. }
  196. // IS NULL 参与 UPDATE/DELETE 谓词 (GORM 软删除同款路径, 必须真正作用到行)
  197. res, err := db.Exec(`UPDATE people SET email = ? WHERE Email IS NULL`, "filled@x.com")
  198. if err != nil {
  199. t.Fatalf("UPDATE IS NULL: %v", err)
  200. }
  201. if n, _ := res.RowsAffected(); n != 2 {
  202. t.Fatalf("UPDATE IS NULL RowsAffected=%d, 想要 2", n)
  203. }
  204. res, err = db.Exec(`DELETE FROM people WHERE EMAIL IS NOT NULL`)
  205. if err != nil {
  206. t.Fatalf("DELETE IS NOT NULL: %v", err)
  207. }
  208. if n, _ := res.RowsAffected(); n != 4 {
  209. t.Fatalf("DELETE IS NOT NULL RowsAffected=%d, 想要 4", n)
  210. }
  211. }
  212. // TestCaseInsensitiveAggregate 聚合目标列大小写混写。
  213. func TestCaseInsensitiveAggregate(t *testing.T) {
  214. db := openTestDB(t)
  215. caseFixture(t, db)
  216. var sum float64
  217. if err := db.QueryRow(`SELECT SUM(AGE) FROM people`).Scan(&sum); err != nil {
  218. t.Fatalf("SUM(AGE): %v", err)
  219. }
  220. if sum != 115 {
  221. t.Fatalf("SUM(AGE)=%v, 想要 115", sum)
  222. }
  223. var mn, mx int
  224. if err := db.QueryRow(`SELECT MIN(Age) FROM people`).Scan(&mn); err != nil {
  225. t.Fatalf("MIN(Age): %v", err)
  226. }
  227. if err := db.QueryRow(`SELECT MAX(aGe) FROM people`).Scan(&mx); err != nil {
  228. t.Fatalf("MAX(aGe): %v", err)
  229. }
  230. if mn != 20 || mx != 40 {
  231. t.Fatalf("MIN=%d MAX=%d, 想要 20/40", mn, mx)
  232. }
  233. var avg float64
  234. if err := db.QueryRow(`SELECT AVG(Age) FROM people WHERE Name LIKE 'a%'`).Scan(&avg); err != nil {
  235. t.Fatalf("AVG: %v", err)
  236. }
  237. if avg != 30 {
  238. t.Fatalf("AVG=%v, 想要 30 (alice)", avg)
  239. }
  240. }
  241. // TestCaseInsensitiveJoin JOIN 的 ON/WHERE/ORDER BY 大小写混用一轮。
  242. func TestCaseInsensitiveJoin(t *testing.T) {
  243. db := openTestDB(t)
  244. joinFixture(t, db)
  245. // ON 两侧限定列混写大小写
  246. got := joinRows(t, db, `SELECT u.name, o.title, o.amount FROM users U JOIN orders O ON U.ID = O.USER_ID ORDER BY O.ID ASC`)
  247. want := []joinHit{{"alice", "book", 99.5}, {"alice", "pen", 20}, {"bob", "cup", 7.5}}
  248. if len(got) != len(want) {
  249. t.Fatalf("JOIN 行数=%d, 想要 %d (%+v)", len(got), len(want), got)
  250. }
  251. for i := range want {
  252. if got[i] != want[i] {
  253. t.Fatalf("行 %d = %+v, 想要 %+v", i, got[i], want[i])
  254. }
  255. }
  256. // WHERE 下推混写大小写 (title 限定名 + 非限定名 age)
  257. got = joinRows(t, db, `SELECT u.name, o.title, o.amount FROM users u JOIN orders o ON u.id = o.user_id WHERE O.Title = 'book' AND AGE > 25`)
  258. want = []joinHit{{"alice", "book", 99.5}}
  259. if len(got) != len(want) || got[0] != want[0] {
  260. t.Fatalf("WHERE 下推 = %+v, 想要 %+v", got, want)
  261. }
  262. // WHERE 残余谓词 (跨表列比较) 混写大小写
  263. got = joinRows(t, db, `SELECT u.name, o.title, o.amount FROM users u JOIN orders o ON u.id = o.user_id WHERE U.Age > O.Amount ORDER BY o.amount ASC`)
  264. want = []joinHit{{"bob", "cup", 7.5}, {"alice", "pen", 20}}
  265. if len(got) != len(want) {
  266. t.Fatalf("残余谓词 = %+v, 想要 %+v", got, want)
  267. }
  268. for i := range want {
  269. if got[i] != want[i] {
  270. t.Fatalf("行 %d = %+v, 想要 %+v", i, got[i], want[i])
  271. }
  272. }
  273. // ORDER BY 非限定名混写大小写 (age 只存在于 users)
  274. got = joinRows(t, db, `SELECT u.name, o.title, o.amount FROM users u JOIN orders o ON u.id = o.user_id ORDER BY Age DESC, o.id ASC`)
  275. want = []joinHit{{"alice", "book", 99.5}, {"alice", "pen", 20}, {"bob", "cup", 7.5}}
  276. if len(got) != len(want) {
  277. t.Fatalf("ORDER BY Age = %+v, 想要 %+v", got, want)
  278. }
  279. for i := range want {
  280. if got[i] != want[i] {
  281. t.Fatalf("行 %d = %+v, 想要 %+v", i, got[i], want[i])
  282. }
  283. }
  284. }
  285. // TestCaseInsensitiveUnknownColumn 错误语义保持: 不存在的列 (任意大小写) 仍报
  286. // 42S22 列不存在, 而不是静默恒假。
  287. func TestCaseInsensitiveUnknownColumn(t *testing.T) {
  288. db := openTestDB(t)
  289. caseFixture(t, db)
  290. checks := []string{
  291. `SELECT COUNT(*) FROM people WHERE NoSuchCol = 1`,
  292. `SELECT COUNT(*) FROM people WHERE Email2 IS NULL`,
  293. `SELECT COUNT(*) FROM people WHERE Name2 LIKE 'a%'`,
  294. `SELECT COUNT(*) FROM people WHERE Age2 IN (1)`,
  295. `SELECT NoSuchCol FROM people`,
  296. `SELECT SUM(NoSuchCol) FROM people`,
  297. `SELECT name FROM people ORDER BY NoSuchCol`,
  298. }
  299. for _, q := range checks {
  300. if _, err := db.Query(q); err == nil {
  301. t.Fatalf("%s: 应报列不存在", q)
  302. } else if st := sqlState(err); st != "42S22" {
  303. t.Fatalf("%s: 错误状态=%q, 想要 42S22 (%v)", q, st, err)
  304. }
  305. }
  306. // UPDATE/DELETE 的未知 WHERE 列也要报错而不是作用 0 行
  307. if _, err := db.Exec(`UPDATE people SET name = 'x' WHERE NoSuchCol = 1`); err == nil {
  308. t.Fatal("UPDATE 未知列应报错")
  309. } else if st := sqlState(err); st != "42S22" {
  310. t.Fatalf("UPDATE 未知列: 错误状态=%q, 想要 42S22", st)
  311. }
  312. if _, err := db.Exec(`DELETE FROM people WHERE NoSuchCol = 1`); err == nil {
  313. t.Fatal("DELETE 未知列应报错")
  314. } else if st := sqlState(err); st != "42S22" {
  315. t.Fatalf("DELETE 未知列: 错误状态=%q, 想要 42S22", st)
  316. }
  317. }