join_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. package vrtxsql
  2. // JOIN 支持测试: INNER/LEFT [OUTER] JOIN、链式多表、限定名列、WHERE 下推与
  3. // 残余谓词 (跨表列比较 / 跨表 OR)、* 投影与重名列去重、歧义列/非法 ON 报错。
  4. // 全部走标准 database/sql 全链路, 数据与断言一一对应 join.go 的执行策略。
  5. import (
  6. "database/sql"
  7. "strings"
  8. "testing"
  9. )
  10. // joinFixture 建 users/orders/refunds 三张表并灌入固定数据:
  11. //
  12. // users: 1 alice 30 / 2 bob 25 / 3 carl 40
  13. // orders: 10 ->u1 99.5 book / 11 ->u1 20 pen / 12 ->u2 7.5 cup
  14. // refunds: 100 ->o10 broken / 101 ->o10 late
  15. func joinFixture(t *testing.T, db *sql.DB) {
  16. t.Helper()
  17. exec := func(q string, args ...interface{}) {
  18. t.Helper()
  19. if _, err := db.Exec(q, args...); err != nil {
  20. t.Fatalf("%s: %v", q, err)
  21. }
  22. }
  23. exec(`CREATE TABLE users (id BIGINT PRIMARY KEY, name VARCHAR(32) NOT NULL, age INT)`)
  24. exec(`CREATE TABLE orders (id BIGINT PRIMARY KEY, user_id BIGINT, amount DOUBLE, title VARCHAR(32))`)
  25. exec(`CREATE TABLE refunds (id BIGINT PRIMARY KEY, order_id BIGINT, reason VARCHAR(32))`)
  26. exec(`INSERT INTO users (id, name, age) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)`,
  27. 1, "alice", 30, 2, "bob", 25, 3, "carl", 40)
  28. exec(`INSERT INTO orders (id, user_id, amount, title) VALUES (?, ?, ?, ?), (?, ?, ?, ?), (?, ?, ?, ?)`,
  29. 10, 1, 99.5, "book", 11, 1, 20.0, "pen", 12, 2, 7.5, "cup")
  30. exec(`INSERT INTO refunds (id, order_id, reason) VALUES (?, ?, ?), (?, ?, ?)`,
  31. 100, 10, "broken", 101, 10, "late")
  32. }
  33. type joinHit struct {
  34. name, title string
  35. amount float64
  36. }
  37. // joinRows 执行 JOIN 查询并收集 (name, title, amount) 三元组。
  38. func joinRows(t *testing.T, db *sql.DB, query string, args ...interface{}) []joinHit {
  39. t.Helper()
  40. rows, err := db.Query(query, args...)
  41. if err != nil {
  42. t.Fatalf("query: %v", err)
  43. }
  44. defer rows.Close()
  45. var got []joinHit
  46. for rows.Next() {
  47. var h joinHit
  48. var name, title []byte
  49. if err := rows.Scan(&name, &title, &h.amount); err != nil {
  50. t.Fatalf("scan: %v", err)
  51. }
  52. h.name, h.title = string(name), string(title)
  53. got = append(got, h)
  54. }
  55. if err := rows.Err(); err != nil {
  56. t.Fatal(err)
  57. }
  58. return got
  59. }
  60. // TestJoinInner INNER JOIN 命中: users/orders 按 user_id 关联, 验证行数与列值。
  61. func TestJoinInner(t *testing.T) {
  62. db := openTestDB(t)
  63. joinFixture(t, db)
  64. rows, err := db.Query(`SELECT u.name, o.title, o.amount FROM users u JOIN orders o ON u.id = o.user_id ORDER BY o.id ASC`)
  65. if err != nil {
  66. t.Fatalf("inner join: %v", err)
  67. }
  68. cols, err := rows.Columns()
  69. if err != nil {
  70. t.Fatal(err)
  71. }
  72. if strings.Join(cols, ",") != "name,title,amount" {
  73. t.Fatalf("列名=%v, 想要 [name title amount]", cols)
  74. }
  75. rows.Close()
  76. 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`)
  77. want := []joinHit{{"alice", "book", 99.5}, {"alice", "pen", 20}, {"bob", "cup", 7.5}}
  78. if len(got) != len(want) {
  79. t.Fatalf("行数=%d, 想要 %d (%+v)", len(got), len(want), got)
  80. }
  81. for i := range want {
  82. if got[i] != want[i] {
  83. t.Fatalf("行 %d = %+v, 想要 %+v", i, got[i], want[i])
  84. }
  85. }
  86. // INNER JOIN 显式关键字 + 表名限定 (不写别名)
  87. got = joinRows(t, db, `SELECT users.name, orders.title, orders.amount FROM users INNER JOIN orders ON users.id = orders.user_id ORDER BY orders.id ASC`)
  88. if len(got) != len(want) {
  89. t.Fatalf("INNER JOIN 行数=%d, 想要 %d (%+v)", len(got), len(want), got)
  90. }
  91. }
  92. // TestJoinLeftNullRow LEFT OUTER JOIN 右表无匹配: 右列全 NULL 保留行。
  93. func TestJoinLeftNullRow(t *testing.T) {
  94. db := openTestDB(t)
  95. joinFixture(t, db)
  96. rows, err := db.Query(`SELECT u.name, o.title FROM users u LEFT OUTER JOIN orders o ON u.id = o.user_id ORDER BY u.id ASC, o.id ASC`)
  97. if err != nil {
  98. t.Fatalf("left join: %v", err)
  99. }
  100. defer rows.Close()
  101. type row struct {
  102. name string
  103. title sql.NullString
  104. }
  105. var got []row
  106. for rows.Next() {
  107. var r row
  108. var name []byte
  109. if err := rows.Scan(&name, &r.title); err != nil {
  110. t.Fatalf("scan: %v", err)
  111. }
  112. r.name = string(name)
  113. got = append(got, r)
  114. }
  115. if err := rows.Err(); err != nil {
  116. t.Fatal(err)
  117. }
  118. if len(got) != 4 {
  119. t.Fatalf("行数=%d, 想要 4 (%+v)", len(got), got)
  120. }
  121. if got[3].name != "carl" || got[3].title.Valid {
  122. t.Fatalf("carl 应为右表无匹配 (title NULL), 实际 %+v", got[3])
  123. }
  124. if got[0].title.String != "book" || got[1].title.String != "pen" || got[2].title.String != "cup" {
  125. t.Fatalf("匹配行不对: %+v", got)
  126. }
  127. }
  128. // TestJoinWhereOrderLimit JOIN + WHERE(限定名) + ORDER BY + LIMIT/OFFSET。
  129. func TestJoinWhereOrderLimit(t *testing.T) {
  130. db := openTestDB(t)
  131. joinFixture(t, db)
  132. // u.age 下推 users 表, o.amount 下推 orders 表
  133. got := joinRows(t, db,
  134. `SELECT u.name, o.title, o.amount FROM users u JOIN orders o ON u.id = o.user_id
  135. WHERE u.age >= ? AND o.amount > ? ORDER BY o.amount DESC LIMIT 1`, 26, 50)
  136. want := []joinHit{{"alice", "book", 99.5}}
  137. if len(got) != 1 || got[0] != want[0] {
  138. t.Fatalf("got %+v, 想要 %+v", got, want)
  139. }
  140. // 非限定列名 (唯一归属 age 在 users) + LIMIT offset,count 写法
  141. // (age>=26 只剩 alice: ASC 序 [pen 20, book 99.5], LIMIT 1,1 = 跳过 1 行取 1 行)
  142. got = joinRows(t, db,
  143. `SELECT u.name, o.title, o.amount FROM users u JOIN orders o ON u.id = o.user_id
  144. WHERE age >= ? ORDER BY o.amount ASC LIMIT 1, 1`, 26)
  145. if len(got) != 1 || got[0] != (joinHit{"alice", "book", 99.5}) {
  146. t.Fatalf("LIMIT offset,count got %+v, 想要 [{alice book 99.5}]", got)
  147. }
  148. // OFFSET 关键字写法 (ASC 序 [cup 7.5, pen 20, book 99.5], 跳 1 行取 1 行)
  149. got = joinRows(t, db,
  150. `SELECT u.name, o.title, o.amount FROM users u JOIN orders o ON u.id = o.user_id
  151. ORDER BY o.amount ASC LIMIT 1 OFFSET 1`)
  152. if len(got) != 1 || got[0] != (joinHit{"alice", "pen", 20}) {
  153. t.Fatalf("OFFSET got %+v, 想要 [{alice pen 20}]", got)
  154. }
  155. }
  156. // TestJoinThreeTables 3 表链式 JOIN: users → orders → refunds。
  157. func TestJoinThreeTables(t *testing.T) {
  158. db := openTestDB(t)
  159. joinFixture(t, db)
  160. rows, err := db.Query(
  161. `SELECT u.name, o.title, r.reason FROM users u
  162. JOIN orders o ON u.id = o.user_id
  163. JOIN refunds r ON o.id = r.order_id
  164. ORDER BY r.id ASC`)
  165. if err != nil {
  166. t.Fatalf("3 表 join: %v", err)
  167. }
  168. defer rows.Close()
  169. var got [][2]string
  170. for rows.Next() {
  171. var name, title, reason []byte
  172. if err := rows.Scan(&name, &title, &reason); err != nil {
  173. t.Fatalf("scan: %v", err)
  174. }
  175. got = append(got, [2]string{string(name) + "/" + string(title), string(reason)})
  176. }
  177. if err := rows.Err(); err != nil {
  178. t.Fatal(err)
  179. }
  180. want := [][2]string{{"alice/book", "broken"}, {"alice/book", "late"}}
  181. if len(got) != len(want) {
  182. t.Fatalf("行数=%d, 想要 %d (%+v)", len(got), len(want), got)
  183. }
  184. for i := range want {
  185. if got[i] != want[i] {
  186. t.Fatalf("行 %d = %+v, 想要 %+v", i, got[i], want[i])
  187. }
  188. }
  189. }
  190. // TestJoinStarAndDedup * 投影跨表展开, 重名列 (id) 用 别名_列名 去重。
  191. func TestJoinStarAndDedup(t *testing.T) {
  192. db := openTestDB(t)
  193. joinFixture(t, db)
  194. rs, err := db.Query(`SELECT * FROM users u JOIN orders o ON u.id = o.user_id WHERE u.id = 1 ORDER BY o.id ASC`)
  195. if err != nil {
  196. t.Fatalf("star join: %v", err)
  197. }
  198. defer rs.Close()
  199. cols, err := rs.Columns()
  200. if err != nil {
  201. t.Fatal(err)
  202. }
  203. wantCols := "id,name,age,o_id,user_id,amount,title"
  204. if strings.Join(cols, ",") != wantCols {
  205. t.Fatalf("列=%v, 想要 %v", cols, wantCols)
  206. }
  207. n := 0
  208. for rs.Next() {
  209. vals := make([]interface{}, len(cols))
  210. ptrs := make([]interface{}, len(cols))
  211. for i := range vals {
  212. ptrs[i] = &vals[i]
  213. }
  214. if err := rs.Scan(ptrs...); err != nil {
  215. t.Fatalf("scan: %v", err)
  216. }
  217. if n == 0 {
  218. // 第一行: alice 的 book 订单
  219. if vals[0] != int64(1) || string(vals[1].([]byte)) != "alice" || vals[2] != int64(30) ||
  220. vals[3] != int64(10) || vals[4] != int64(1) || vals[5] != float64(99.5) ||
  221. string(vals[6].([]byte)) != "book" {
  222. t.Fatalf("行值不对: %#v", vals)
  223. }
  224. }
  225. n++
  226. }
  227. if err := rs.Err(); err != nil {
  228. t.Fatal(err)
  229. }
  230. if n != 2 {
  231. t.Fatalf("行数=%d, 想要 2", n)
  232. }
  233. // t.* 与限定列混用 (u.* = id,name,age 三列 + o.amount)
  234. rs2, err := db.Query(`SELECT u.*, o.amount FROM users u JOIN orders o ON u.id = o.user_id WHERE o.id = 12`)
  235. if err != nil {
  236. t.Fatalf("t.* 混用: %v", err)
  237. }
  238. defer rs2.Close()
  239. if !rs2.Next() {
  240. t.Fatal("t.* 混用无行")
  241. }
  242. var id, age int64
  243. var uname []byte
  244. var amount float64
  245. if err := rs2.Scan(&id, &uname, &age, &amount); err != nil {
  246. t.Fatalf("scan: %v", err)
  247. }
  248. if id != 2 || string(uname) != "bob" || age != 25 || amount != 7.5 {
  249. t.Fatalf("t.* 混用行 = %d,%s,%d,%v", id, uname, age, amount)
  250. }
  251. }
  252. // TestJoinResidualPredicate 跨表条件作为残余谓词在组合行上过滤:
  253. // 列与列比较 (o.amount > u.age) 与跨表 OR (u.name = ? OR o.amount > ?)。
  254. func TestJoinResidualPredicate(t *testing.T) {
  255. db := openTestDB(t)
  256. joinFixture(t, db)
  257. // 列比较: alice 99.5>30 命中, 20>30 / 7.5>25 不命中
  258. got := joinRows(t, db,
  259. `SELECT u.name, o.title, o.amount FROM users u JOIN orders o ON u.id = o.user_id
  260. WHERE o.amount > u.age ORDER BY o.id ASC`)
  261. want := []joinHit{{"alice", "book", 99.5}}
  262. if len(got) != len(want) || got[0] != want[0] {
  263. t.Fatalf("列比较 got %+v, 想要 %+v", got, want)
  264. }
  265. // 跨表 OR (结构条件无法下推): alice 两行 + amount>50 的 alice/book
  266. got = joinRows(t, db,
  267. `SELECT u.name, o.title, o.amount FROM users u INNER JOIN orders o ON u.id = o.user_id
  268. WHERE u.name = ? OR o.amount > ? ORDER BY o.id ASC`, "alice", 50)
  269. want = []joinHit{{"alice", "book", 99.5}, {"alice", "pen", 20}}
  270. if len(got) != len(want) {
  271. t.Fatalf("跨表 OR 行数=%d, 想要 %d (%+v)", len(got), len(want), got)
  272. }
  273. for i := range want {
  274. if got[i] != want[i] {
  275. t.Fatalf("行 %d = %+v, 想要 %+v", i, got[i], want[i])
  276. }
  277. }
  278. }
  279. // TestJoinErrors 歧义列 / 非法 ON / 未知限定的明确报错。
  280. func TestJoinErrors(t *testing.T) {
  281. db := openTestDB(t)
  282. joinFixture(t, db)
  283. // SELECT 非限定歧义列 (users/orders 都有 id)
  284. if _, err := db.Query(`SELECT id FROM users u JOIN orders o ON u.id = o.user_id`); err == nil ||
  285. !strings.Contains(err.Error(), "不明确") {
  286. t.Fatalf("SELECT 歧义列应报 不明确, 实际 %v", err)
  287. }
  288. // WHERE 非限定歧义列 (age 只属 users, id 两表都有)
  289. if _, err := db.Query(`SELECT u.name FROM users u JOIN orders o ON u.id = o.user_id WHERE id > 10`); err == nil ||
  290. !strings.Contains(err.Error(), "不明确") {
  291. t.Fatalf("WHERE 歧义列应报 不明确, 实际 %v", err)
  292. }
  293. // ON 非等值
  294. if _, err := db.Query(`SELECT * FROM users u JOIN orders o ON u.id > o.user_id`); err == nil ||
  295. !strings.Contains(err.Error(), "JOIN ON 仅支持等值条件") {
  296. t.Fatalf("ON 非等值应报错, 实际 %v", err)
  297. }
  298. // ON 未知限定
  299. if _, err := db.Query(`SELECT * FROM users u JOIN orders o ON x.id = o.user_id`); err == nil {
  300. t.Fatalf("ON 未知限定应报错")
  301. }
  302. // ON 列不存在
  303. if _, err := db.Query(`SELECT * FROM users u JOIN orders o ON u.nope = o.user_id`); err == nil {
  304. t.Fatalf("ON 未知列应报错")
  305. }
  306. // JOIN 与聚合同用
  307. if _, err := db.Query(`SELECT COUNT(*) FROM users u JOIN orders o ON u.id = o.user_id`); err == nil {
  308. t.Fatalf("JOIN+聚合应报错")
  309. }
  310. }
  311. // TestJoinQualifiedSingleTable 单表查询的限定名列/t.* (兼容路径, 解限定后走标准翻译)。
  312. func TestJoinQualifiedSingleTable(t *testing.T) {
  313. db := openTestDB(t)
  314. joinFixture(t, db)
  315. var name []byte
  316. if err := db.QueryRow(`SELECT u.name FROM users u WHERE u.age > ? ORDER BY u.id LIMIT 1`, 26).Scan(&name); err != nil {
  317. t.Fatalf("限定名 WHERE: %v", err)
  318. }
  319. if string(name) != "alice" {
  320. t.Fatalf("name=%q, 想要 alice", name)
  321. }
  322. if err := db.QueryRow(`SELECT name FROM users AS u WHERE u.age >= ? ORDER BY u.id DESC LIMIT 1`, 26).Scan(&name); err != nil {
  323. t.Fatalf("AS 别名 + 限定名: %v", err)
  324. }
  325. if string(name) != "carl" {
  326. t.Fatalf("name=%q, 想要 carl", name)
  327. }
  328. // t.* 单表展开 + 聚合限定列
  329. var age int64
  330. if err := db.QueryRow(`SELECT COUNT(u.id) FROM users u WHERE u.age > ?`, 26).Scan(&age); err != nil {
  331. t.Fatalf("聚合限定列: %v", err)
  332. }
  333. if age != 2 {
  334. t.Fatalf("count=%d, 想要 2", age)
  335. }
  336. // 限定词不是本表 → 明确报错
  337. if _, err := db.Query(`SELECT o.name FROM users u WHERE u.age > 1`); err == nil {
  338. t.Fatalf("限定词不匹配应报错")
  339. }
  340. }