recursive_cte_test.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. package executor
  2. import "testing"
  3. func TestRecursiveCTE(t *testing.T) {
  4. _, schema, table := newTestDB(t)
  5. e := newExec(schema, table)
  6. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, parent_id INTEGER)")
  7. execMust(t, e, "INSERT INTO t VALUES (1, NULL)")
  8. execMust(t, e, "INSERT INTO t VALUES (2, 1)")
  9. execMust(t, e, "INSERT INTO t VALUES (3, 2)")
  10. execMust(t, e, "INSERT INTO t VALUES (4, NULL)")
  11. res := execMust(t, e, `WITH RECURSIVE hier AS (
  12. SELECT id, parent_id, 0 AS level, id AS root FROM t WHERE id = 3
  13. UNION ALL
  14. SELECT t.id, t.parent_id, h.level + 1, h.root FROM t INNER JOIN hier h ON t.id = h.parent_id
  15. )
  16. SELECT id, level, root FROM hier ORDER BY id`)
  17. if res.RowCount != 3 {
  18. t.Fatalf("expected 3 rows, got %d: %v", res.RowCount, res.Rows)
  19. }
  20. want := map[int64]int64{3: 0, 2: 1, 1: 2}
  21. for _, row := range res.Rows {
  22. id := row[0].(int64)
  23. if row[1].(int64) != want[id] {
  24. t.Fatalf("id %d level = %v, want %v", id, row[1], want[id])
  25. }
  26. if row[2].(int64) != 3 {
  27. t.Fatalf("id %d root = %v, want 3", id, row[2])
  28. }
  29. }
  30. }
  31. func TestRowNumberWindow(t *testing.T) {
  32. _, schema, table := newTestDB(t)
  33. e := newExec(schema, table)
  34. execMust(t, e, "CREATE TABLE s (id INTEGER PRIMARY KEY, grp INTEGER, val INTEGER)")
  35. execMust(t, e, "INSERT INTO s VALUES (1, 1, 10)")
  36. execMust(t, e, "INSERT INTO s VALUES (2, 1, 5)")
  37. execMust(t, e, "INSERT INTO s VALUES (3, 2, 7)")
  38. execMust(t, e, "INSERT INTO s VALUES (4, 2, 7)")
  39. res := execMust(t, e, "SELECT id, ROW_NUMBER() OVER (PARTITION BY grp ORDER BY val) AS rn FROM s ORDER BY id")
  40. want := map[int64]int64{1: 2, 2: 1, 3: 1, 4: 2}
  41. if res.RowCount != 4 {
  42. t.Fatalf("expected 4 rows, got %d: %v", res.RowCount, res.Rows)
  43. }
  44. for _, row := range res.Rows {
  45. id := row[0].(int64)
  46. if row[1].(int64) != want[id] {
  47. t.Fatalf("id %d rn = %v, want %v (rows %v)", id, row[1], want[id], res.Rows)
  48. }
  49. }
  50. }
  51. // TestRecursiveCTEWithWindow reproduces the combined shape of Vikunja's
  52. // subscription query: a recursive CTE, a dependent CTE, and a windowed derived
  53. // table joined back to a real table.
  54. func TestRecursiveCTEWithWindow(t *testing.T) {
  55. _, schema, table := newTestDB(t)
  56. e := newExec(schema, table)
  57. execMust(t, e, "CREATE TABLE projects (id INTEGER PRIMARY KEY, parent_project_id INTEGER)")
  58. execMust(t, e, "CREATE TABLE subscriptions (id INTEGER PRIMARY KEY, entity_type INTEGER, entity_id INTEGER, user_id INTEGER, muted INTEGER)")
  59. execMust(t, e, "CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT)")
  60. execMust(t, e, "INSERT INTO projects VALUES (1, NULL)")
  61. execMust(t, e, "INSERT INTO projects VALUES (2, 1)")
  62. execMust(t, e, "INSERT INTO projects VALUES (3, 2)")
  63. execMust(t, e, "INSERT INTO subscriptions VALUES (1, 1, 1, 10, 0)")
  64. execMust(t, e, "INSERT INTO subscriptions VALUES (2, 1, 3, 20, 0)")
  65. execMust(t, e, "INSERT INTO users VALUES (10, 'alice')")
  66. execMust(t, e, "INSERT INTO users VALUES (20, 'bob')")
  67. res := execMust(t, e, `WITH RECURSIVE project_hierarchy AS (
  68. SELECT id, parent_project_id, 0 AS level, id AS original_project_id FROM projects WHERE id IN (3)
  69. UNION ALL
  70. SELECT p.id, p.parent_project_id, ph.level + 1, ph.original_project_id
  71. FROM projects p INNER JOIN project_hierarchy ph ON p.id = ph.parent_project_id
  72. ),
  73. subscription_hierarchy AS (
  74. SELECT s.id, s.entity_type, s.entity_id, s.user_id, s.muted,
  75. CASE WHEN s.entity_id = ph.original_project_id THEN 1 ELSE ph.level + 1 END AS priority,
  76. ph.original_project_id
  77. FROM subscriptions s INNER JOIN project_hierarchy ph ON s.entity_id = ph.id
  78. WHERE s.entity_type = 1
  79. )
  80. SELECT p.id AS original_entity_id, sh.id AS subscription_id, sh.user_id
  81. FROM projects p
  82. LEFT JOIN (
  83. SELECT *, ROW_NUMBER() OVER (PARTITION BY original_project_id, user_id ORDER BY priority) AS rn
  84. FROM subscription_hierarchy
  85. ) sh ON p.id = sh.original_project_id AND sh.rn = 1
  86. WHERE p.id IN (3)
  87. ORDER BY p.id, sh.user_id`)
  88. if res.RowCount != 2 {
  89. t.Fatalf("expected 2 rows, got %d: %v", res.RowCount, res.Rows)
  90. }
  91. got := map[int64]bool{}
  92. for _, row := range res.Rows {
  93. got[row[1].(int64)] = true
  94. }
  95. if !got[1] || !got[2] {
  96. t.Fatalf("expected subscriptions 1 and 2, got %v", res.Rows)
  97. }
  98. }