foreign_key_cascade_test.go 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. package executor
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "strings"
  7. "sync"
  8. "testing"
  9. "time"
  10. "github.com/danfragoso/pizzasql-next/pkg/capabilities"
  11. "github.com/danfragoso/pizzasql-next/pkg/storage"
  12. )
  13. // requireNotNullViolation asserts err is a NOT NULL violation (23502).
  14. func requireNotNullViolation(t *testing.T, err error) *storage.NotNullViolationError {
  15. t.Helper()
  16. var violation *storage.NotNullViolationError
  17. if !errors.As(err, &violation) {
  18. t.Fatalf("error = %v, want NotNullViolationError", err)
  19. }
  20. return violation
  21. }
  22. // countRows returns the number of rows in table.
  23. func countRows(t *testing.T, e *Executor, table string) int64 {
  24. t.Helper()
  25. res := execMust(t, e, "SELECT count(*) FROM "+table)
  26. return res.Rows[0][0].(int64)
  27. }
  28. // ON DELETE CASCADE removes the direct children and, transitively, their own
  29. // children. rows_affected counts only the rows the statement matched directly.
  30. func TestForeignKeyDeleteCascadeSingleAndMultiLevel(t *testing.T) {
  31. _, schema, table := newTestDB(t)
  32. e := newExec(schema, table)
  33. execMust(t, e, "CREATE TABLE grandparent (id INTEGER PRIMARY KEY)")
  34. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY, gp_id INTEGER REFERENCES grandparent(id) ON DELETE CASCADE)")
  35. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id) ON DELETE CASCADE)")
  36. execMust(t, e, "INSERT INTO grandparent (id) VALUES (1), (2)")
  37. execMust(t, e, "INSERT INTO parent (id, gp_id) VALUES (10, 1), (20, 2)")
  38. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (100, 10), (101, 10), (200, 20)")
  39. res := execMust(t, e, "DELETE FROM grandparent WHERE id = 1")
  40. if res.RowsAffected != 1 {
  41. t.Fatalf("rows affected = %d, want 1 (cascaded rows are not counted)", res.RowsAffected)
  42. }
  43. if got := countRows(t, e, "grandparent"); got != 1 {
  44. t.Fatalf("grandparent rows = %d, want 1", got)
  45. }
  46. if got := countRows(t, e, "parent"); got != 1 {
  47. t.Fatalf("parent rows = %d, want 1", got)
  48. }
  49. if got := countRows(t, e, "child"); got != 1 {
  50. t.Fatalf("child rows = %d, want 1", got)
  51. }
  52. if res := execMust(t, e, "SELECT id FROM child"); res.Rows[0][0] != int64(200) {
  53. t.Fatalf("remaining child = %v, want 200", res.Rows)
  54. }
  55. // A scan delete cascades every matched parent row in one statement.
  56. execMust(t, e, "DELETE FROM grandparent")
  57. for _, tableName := range []string{"grandparent", "parent", "child"} {
  58. if got := countRows(t, e, tableName); got != 0 {
  59. t.Fatalf("%s rows after cascading scan delete = %d, want 0", tableName, got)
  60. }
  61. }
  62. }
  63. // ON UPDATE CASCADE copies the new parent key into every referencing child,
  64. // including a referenced UNIQUE index column and a composite key.
  65. func TestForeignKeyUpdateCascade(t *testing.T) {
  66. _, schema, table := newTestDB(t)
  67. e := newExec(schema, table)
  68. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY, code TEXT UNIQUE)")
  69. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id) ON UPDATE CASCADE, code TEXT REFERENCES parent(code) ON UPDATE CASCADE)")
  70. execMust(t, e, "INSERT INTO parent (id, code) VALUES (1, 'a')")
  71. execMust(t, e, "INSERT INTO child (id, parent_id, code) VALUES (10, 1, 'a')")
  72. execMust(t, e, "UPDATE parent SET id = 5 WHERE id = 1")
  73. if res := execMust(t, e, "SELECT parent_id, code FROM child WHERE id = 10"); res.Rows[0][0] != int64(5) || res.Rows[0][1] != "a" {
  74. t.Fatalf("child after primary-key cascade = %v, want parent_id 5", res.Rows)
  75. }
  76. execMust(t, e, "UPDATE parent SET code = 'b' WHERE id = 5")
  77. if res := execMust(t, e, "SELECT parent_id, code FROM child WHERE id = 10"); res.Rows[0][0] != int64(5) || res.Rows[0][1] != "b" {
  78. t.Fatalf("child after unique-key cascade = %v, want code b", res.Rows)
  79. }
  80. execMust(t, e, "CREATE TABLE cparent (a TEXT, b INTEGER, PRIMARY KEY (a, b))")
  81. execMust(t, e, "CREATE TABLE cchild (id INTEGER PRIMARY KEY, pa TEXT, pb INTEGER, FOREIGN KEY (pa, pb) REFERENCES cparent(a, b) ON UPDATE CASCADE)")
  82. execMust(t, e, "INSERT INTO cparent (a, b) VALUES ('x', 1)")
  83. execMust(t, e, "INSERT INTO cchild (id, pa, pb) VALUES (10, 'x', 1)")
  84. execMust(t, e, "UPDATE cparent SET a = 'y', b = 2")
  85. if res := execMust(t, e, "SELECT pa, pb FROM cchild WHERE id = 10"); res.Rows[0][0] != "y" || res.Rows[0][1] != int64(2) {
  86. t.Fatalf("composite child after cascade = %v, want ('y', 2)", res.Rows)
  87. }
  88. }
  89. // SET NULL clears the child columns on delete and on a key update, and a NOT
  90. // NULL child column rejects the action with 23502 without changing anything.
  91. func TestForeignKeySetNull(t *testing.T) {
  92. _, schema, table := newTestDB(t)
  93. e := newExec(schema, table)
  94. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  95. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id) ON DELETE SET NULL ON UPDATE SET NULL)")
  96. execMust(t, e, "INSERT INTO parent (id) VALUES (1)")
  97. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  98. execMust(t, e, "UPDATE parent SET id = 2 WHERE id = 1")
  99. if res := execMust(t, e, "SELECT parent_id FROM child WHERE id = 10"); res.Rows[0][0] != nil {
  100. t.Fatalf("parent_id after ON UPDATE SET NULL = %v, want NULL", res.Rows[0][0])
  101. }
  102. execMust(t, e, "UPDATE child SET parent_id = 2 WHERE id = 10")
  103. execMust(t, e, "DELETE FROM parent WHERE id = 2")
  104. if res := execMust(t, e, "SELECT parent_id FROM child WHERE id = 10"); res.Rows[0][0] != nil {
  105. t.Fatalf("parent_id after ON DELETE SET NULL = %v, want NULL", res.Rows[0][0])
  106. }
  107. execMust(t, e, "CREATE TABLE required_parent (id INTEGER PRIMARY KEY)")
  108. execMust(t, e, "CREATE TABLE required_child (id INTEGER PRIMARY KEY, parent_id INTEGER NOT NULL REFERENCES required_parent(id) ON DELETE SET NULL)")
  109. execMust(t, e, "INSERT INTO required_parent (id) VALUES (1)")
  110. execMust(t, e, "INSERT INTO required_child (id, parent_id) VALUES (10, 1)")
  111. _, err := execSQL(e, "DELETE FROM required_parent WHERE id = 1")
  112. requireNotNullViolation(t, err)
  113. if got := countRows(t, e, "required_parent"); got != 1 {
  114. t.Fatalf("required_parent rows after rejected cascade = %d, want 1", got)
  115. }
  116. if res := execMust(t, e, "SELECT parent_id FROM required_child WHERE id = 10"); res.Rows[0][0] != int64(1) {
  117. t.Fatalf("required_child.parent_id after rejected cascade = %v, want 1", res.Rows[0][0])
  118. }
  119. }
  120. // SET DEFAULT rewrites the child columns to their declared defaults, honoring
  121. // NOT NULL, CHECK, and FOREIGN KEY on the resulting row.
  122. func TestForeignKeySetDefault(t *testing.T) {
  123. _, schema, table := newTestDB(t)
  124. e := newExec(schema, table)
  125. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  126. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER DEFAULT 1 REFERENCES parent(id) ON DELETE SET DEFAULT ON UPDATE SET DEFAULT)")
  127. execMust(t, e, "INSERT INTO parent (id) VALUES (1), (2), (4)")
  128. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 2), (11, 4)")
  129. execMust(t, e, "DELETE FROM parent WHERE id = 2")
  130. if res := execMust(t, e, "SELECT parent_id FROM child WHERE id = 10"); res.Rows[0][0] != int64(1) {
  131. t.Fatalf("parent_id after ON DELETE SET DEFAULT = %v, want 1", res.Rows[0][0])
  132. }
  133. execMust(t, e, "UPDATE parent SET id = 3 WHERE id = 4")
  134. if res := execMust(t, e, "SELECT parent_id FROM child WHERE id = 11"); res.Rows[0][0] != int64(1) {
  135. t.Fatalf("parent_id after ON UPDATE SET DEFAULT = %v, want the stationary default 1", res.Rows[0][0])
  136. }
  137. // A default that violates CHECK rolls the whole delete back with 23514.
  138. execMust(t, e, "CREATE TABLE checked_parent (id INTEGER PRIMARY KEY)")
  139. execMust(t, e, "CREATE TABLE checked_child (id INTEGER PRIMARY KEY, parent_id INTEGER DEFAULT 0 CHECK (parent_id > 0) REFERENCES checked_parent(id) ON DELETE SET DEFAULT)")
  140. execMust(t, e, "INSERT INTO checked_parent (id) VALUES (1)")
  141. execMust(t, e, "INSERT INTO checked_child (id, parent_id) VALUES (10, 1)")
  142. _, err := execSQL(e, "DELETE FROM checked_parent WHERE id = 1")
  143. requireCheckViolation(t, err, "")
  144. if got := countRows(t, e, "checked_parent"); got != 1 {
  145. t.Fatalf("checked_parent rows after rejected cascade = %d, want 1", got)
  146. }
  147. // A NOT NULL column without a default yields NULL, which is rejected.
  148. execMust(t, e, "CREATE TABLE strict_parent (id INTEGER PRIMARY KEY)")
  149. execMust(t, e, "CREATE TABLE strict_child (id INTEGER PRIMARY KEY, parent_id INTEGER NOT NULL REFERENCES strict_parent(id) ON DELETE SET DEFAULT)")
  150. execMust(t, e, "INSERT INTO strict_parent (id) VALUES (1)")
  151. execMust(t, e, "INSERT INTO strict_child (id, parent_id) VALUES (10, 1)")
  152. _, err = execSQL(e, "DELETE FROM strict_parent WHERE id = 1")
  153. requireNotNullViolation(t, err)
  154. // A default with no matching parent is a dangling reference at commit.
  155. execMust(t, e, "CREATE TABLE dangling_parent (id INTEGER PRIMARY KEY)")
  156. execMust(t, e, "CREATE TABLE dangling_child (id INTEGER PRIMARY KEY, parent_id INTEGER DEFAULT 99 REFERENCES dangling_parent(id) ON DELETE SET DEFAULT)")
  157. execMust(t, e, "INSERT INTO dangling_parent (id) VALUES (10)")
  158. execMust(t, e, "INSERT INTO dangling_child (id, parent_id) VALUES (20, 10)")
  159. _, err = execSQL(e, "DELETE FROM dangling_parent WHERE id = 10")
  160. requireForeignKeyViolation(t, err)
  161. if got := countRows(t, e, "dangling_child"); got != 1 {
  162. t.Fatalf("dangling_child rows after rejected cascade = %d, want 1", got)
  163. }
  164. }
  165. // A self-referential tree cascades transitively without visiting the deleted row
  166. // as its own child, and ON UPDATE CASCADE rewrites a row that references itself.
  167. func TestForeignKeySelfReferentialCascade(t *testing.T) {
  168. _, schema, table := newTestDB(t)
  169. e := newExec(schema, table)
  170. execMust(t, e, "CREATE TABLE nodes (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES nodes(id) ON DELETE CASCADE ON UPDATE CASCADE)")
  171. execMust(t, e, "INSERT INTO nodes (id, parent_id) VALUES (1, NULL), (2, 1), (3, 2), (4, NULL), (5, 5)")
  172. execMust(t, e, "DELETE FROM nodes WHERE id = 1")
  173. if res := execMust(t, e, "SELECT id FROM nodes ORDER BY id"); res.RowCount != 2 || res.Rows[0][0] != int64(4) || res.Rows[1][0] != int64(5) {
  174. t.Fatalf("nodes after cascading delete = %v, want 4 and 5", res.Rows)
  175. }
  176. // A row that references itself leaves with its own reference.
  177. execMust(t, e, "DELETE FROM nodes WHERE id = 5")
  178. if got := countRows(t, e, "nodes"); got != 1 {
  179. t.Fatalf("nodes after self-referencing delete = %d, want 1", got)
  180. }
  181. execMust(t, e, "DELETE FROM nodes")
  182. execMust(t, e, "INSERT INTO nodes (id, parent_id) VALUES (1, 1), (2, 1)")
  183. execMust(t, e, "UPDATE nodes SET id = 10 WHERE id = 1")
  184. if res := execMust(t, e, "SELECT id, parent_id FROM nodes ORDER BY id"); res.RowCount != 2 ||
  185. res.Rows[0][0] != int64(2) || res.Rows[0][1] != int64(10) ||
  186. res.Rows[1][0] != int64(10) || res.Rows[1][1] != int64(10) {
  187. t.Fatalf("nodes after self-referencing key update = %v, want (2,10) and (10,10)", res.Rows)
  188. }
  189. }
  190. // installForeignKey appends a foreign key to table's persisted catalog. A
  191. // two-table cycle cannot be declared through CREATE TABLE because a forward
  192. // reference to a table that does not exist yet is rejected, so the test writes
  193. // the durable catalog directly and reopens the database.
  194. func installForeignKey(t *testing.T, pool *storage.KVPool, table string, fk storage.ForeignKeyConstraint) {
  195. t.Helper()
  196. schema := storage.NewSchemaManager(pool, "testdb")
  197. stored, err := schema.GetSchema(table)
  198. if err != nil {
  199. t.Fatalf("get schema %s: %v", table, err)
  200. }
  201. stored.ForeignKeys = append(stored.ForeignKeys, fk)
  202. data, err := json.Marshal(stored)
  203. if err != nil {
  204. t.Fatalf("marshal schema %s: %v", table, err)
  205. }
  206. key := "testdb:_schema:" + strings.ToLower(table)
  207. if err := pool.WithClient(func(c *storage.KVClient) error { return c.Write(key, string(data)) }); err != nil {
  208. t.Fatalf("write schema %s: %v", table, err)
  209. }
  210. }
  211. // Two tables that reference each other form a cascade cycle. Deleting or
  212. // re-keying a row must terminate instead of bouncing between the tables.
  213. func TestForeignKeyMutualCascadeCycleTerminates(t *testing.T) {
  214. pool, schema, table := newTestDB(t)
  215. e := newExec(schema, table)
  216. execMust(t, e, "CREATE TABLE a (id INTEGER PRIMARY KEY, b_id INTEGER)")
  217. execMust(t, e, "CREATE TABLE b (id INTEGER PRIMARY KEY, a_id INTEGER REFERENCES a(id) ON DELETE CASCADE ON UPDATE CASCADE)")
  218. installForeignKey(t, pool, "a", storage.ForeignKeyConstraint{
  219. Name: "a_b_fk",
  220. Columns: []string{"b_id"},
  221. RefTable: "b",
  222. RefColumns: []string{"id"},
  223. OnDelete: "CASCADE",
  224. OnUpdate: "CASCADE",
  225. })
  226. reloaded := storage.NewSchemaManager(pool, "testdb")
  227. cyc := newExec(reloaded, storage.NewTableManager(pool, reloaded, "testdb"))
  228. insertCycle := func(aID, bID int) {
  229. t.Helper()
  230. execMust(t, cyc, "BEGIN")
  231. execMust(t, cyc, fmt.Sprintf("INSERT INTO b (id, a_id) VALUES (%d, %d)", bID, aID))
  232. execMust(t, cyc, fmt.Sprintf("INSERT INTO a (id, b_id) VALUES (%d, %d)", aID, bID))
  233. execMust(t, cyc, "COMMIT")
  234. }
  235. insertCycle(1, 1)
  236. execMust(t, cyc, "DELETE FROM a WHERE id = 1")
  237. if got := countRows(t, cyc, "a") + countRows(t, cyc, "b"); got != 0 {
  238. t.Fatalf("cycle rows after cascading delete = %d, want 0", got)
  239. }
  240. insertCycle(1, 1)
  241. execMust(t, cyc, "UPDATE a SET id = 2 WHERE id = 1")
  242. res := execMust(t, cyc, "SELECT id, b_id FROM a")
  243. if res.RowCount != 1 || res.Rows[0][0] != int64(2) || res.Rows[0][1] != int64(1) {
  244. t.Fatalf("a after cycle key update = %v, want (2,1)", res.Rows)
  245. }
  246. res = execMust(t, cyc, "SELECT id, a_id FROM b")
  247. if res.RowCount != 1 || res.Rows[0][0] != int64(1) || res.Rows[0][1] != int64(2) {
  248. t.Fatalf("b after cycle key update = %v, want (1,2)", res.Rows)
  249. }
  250. execMust(t, cyc, "UPDATE b SET id = 5 WHERE id = 1")
  251. res = execMust(t, cyc, "SELECT id, b_id FROM a")
  252. if res.RowCount != 1 || res.Rows[0][0] != int64(2) || res.Rows[0][1] != int64(5) {
  253. t.Fatalf("a after second cycle key update = %v, want (2,5)", res.Rows)
  254. }
  255. }
  256. // NO ACTION and RESTRICT keep the immediate 23503 behavior.
  257. func TestForeignKeyNoActionRestrictStillViolate(t *testing.T) {
  258. _, schema, table := newTestDB(t)
  259. e := newExec(schema, table)
  260. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  261. execMust(t, e, "CREATE TABLE restricting (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id) ON DELETE RESTRICT ON UPDATE RESTRICT)")
  262. execMust(t, e, "CREATE TABLE no_action (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  263. execMust(t, e, "INSERT INTO parent (id) VALUES (1)")
  264. execMust(t, e, "INSERT INTO restricting (id, parent_id) VALUES (10, 1)")
  265. execMust(t, e, "INSERT INTO no_action (id, parent_id) VALUES (20, 1)")
  266. _, err := execSQL(e, "DELETE FROM parent WHERE id = 1")
  267. requireForeignKeyViolation(t, err)
  268. _, err = execSQL(e, "UPDATE parent SET id = 2 WHERE id = 1")
  269. requireForeignKeyViolation(t, err)
  270. execMust(t, e, "DELETE FROM restricting")
  271. _, err = execSQL(e, "DELETE FROM parent WHERE id = 1")
  272. requireForeignKeyViolation(t, err)
  273. execMust(t, e, "DELETE FROM no_action")
  274. execMust(t, e, "DELETE FROM parent WHERE id = 1")
  275. }
  276. // A cascaded row that is itself referenced by a NO ACTION child aborts the whole
  277. // statement; no partially cascaded state is durable.
  278. func TestForeignKeyCascadeDownstreamFailureIsAtomic(t *testing.T) {
  279. _, schema, table := newTestDB(t)
  280. e := newExec(schema, table)
  281. execMust(t, e, "CREATE TABLE grandparent (id INTEGER PRIMARY KEY)")
  282. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY, gp_id INTEGER REFERENCES grandparent(id) ON DELETE CASCADE)")
  283. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  284. execMust(t, e, "INSERT INTO grandparent (id) VALUES (1), (2)")
  285. execMust(t, e, "INSERT INTO parent (id, gp_id) VALUES (10, 1), (20, 2)")
  286. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (100, 10)")
  287. _, err := execSQL(e, "DELETE FROM grandparent WHERE id = 1")
  288. requireForeignKeyViolation(t, err)
  289. if got := countRows(t, e, "grandparent"); got != 2 {
  290. t.Fatalf("grandparent rows after rejected cascade = %d, want 2", got)
  291. }
  292. if got := countRows(t, e, "parent"); got != 2 {
  293. t.Fatalf("parent rows after rejected cascade = %d, want 2", got)
  294. }
  295. if got := countRows(t, e, "child"); got != 1 {
  296. t.Fatalf("child rows after rejected cascade = %d, want 1", got)
  297. }
  298. // A multi-row delete is atomic: the row with the blocked chain also keeps
  299. // the unrelated row in place.
  300. _, err = execSQL(e, "DELETE FROM grandparent")
  301. requireForeignKeyViolation(t, err)
  302. if got := countRows(t, e, "grandparent"); got != 2 {
  303. t.Fatalf("grandparent rows after rejected scan delete = %d, want 2", got)
  304. }
  305. if got := countRows(t, e, "parent"); got != 2 {
  306. t.Fatalf("parent rows after rejected scan delete = %d, want 2", got)
  307. }
  308. // Removing the downstream reference lets the same delete cascade.
  309. execMust(t, e, "DELETE FROM child")
  310. execMust(t, e, "DELETE FROM grandparent")
  311. if got := countRows(t, e, "grandparent") + countRows(t, e, "parent"); got != 0 {
  312. t.Fatalf("rows after successful cascade = %d, want 0", got)
  313. }
  314. }
  315. // A cascade inside an explicit transaction interacts with the commit-time final
  316. // state check: a consistent result commits and a dangling one does not.
  317. func TestForeignKeyCascadeInTransaction(t *testing.T) {
  318. _, schema, table := newTestDB(t)
  319. e := newExec(schema, table)
  320. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  321. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id) ON DELETE CASCADE)")
  322. execMust(t, e, "INSERT INTO parent (id) VALUES (1), (2)")
  323. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1), (11, 1), (20, 2)")
  324. execMust(t, e, "BEGIN")
  325. execMust(t, e, "DELETE FROM parent WHERE id = 1")
  326. execMust(t, e, "SELECT count(*) FROM child")
  327. execMust(t, e, "COMMIT")
  328. if got := countRows(t, e, "child"); got != 1 {
  329. t.Fatalf("child rows after transactional cascade = %d, want 1", got)
  330. }
  331. // Re-adding a child under the deleted parent makes the final state dangling,
  332. // so the commit is rejected and the cascade is discarded.
  333. execMust(t, e, "BEGIN")
  334. execMust(t, e, "DELETE FROM parent WHERE id = 2")
  335. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (99, 2)")
  336. _, err := execSQL(e, "COMMIT")
  337. requireForeignKeyViolation(t, err)
  338. if got := countRows(t, e, "parent"); got != 1 {
  339. t.Fatalf("parent rows after rejected commit = %d, want 1", got)
  340. }
  341. if res := execMust(t, e, "SELECT id, parent_id FROM child ORDER BY id"); res.RowCount != 1 || res.Rows[0][0] != int64(20) {
  342. t.Fatalf("child rows after rejected commit = %v, want only row 20", res.Rows)
  343. }
  344. // A point delete inside a transaction cascades too.
  345. execMust(t, e, "BEGIN")
  346. execMust(t, e, "DELETE FROM parent WHERE id = 2")
  347. execMust(t, e, "COMMIT")
  348. if got := countRows(t, e, "parent") + countRows(t, e, "child"); got != 0 {
  349. t.Fatalf("rows after transactional point cascade = %d, want 0", got)
  350. }
  351. }
  352. // Referential actions persist with the schema and are enforced after a reload.
  353. func TestForeignKeyCascadeDurableReload(t *testing.T) {
  354. pool, schema, table := newTestDB(t)
  355. e := newExec(schema, table)
  356. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  357. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER, CONSTRAINT child_parent FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE CASCADE ON UPDATE CASCADE)")
  358. execMust(t, e, "INSERT INTO parent (id) VALUES (1)")
  359. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  360. reloaded := storage.NewSchemaManager(pool, "testdb")
  361. stored, err := reloaded.GetSchema("child")
  362. if err != nil {
  363. t.Fatalf("reload schema: %v", err)
  364. }
  365. if len(stored.ForeignKeys) != 1 || stored.ForeignKeys[0].OnDelete != "CASCADE" || stored.ForeignKeys[0].OnUpdate != "CASCADE" {
  366. t.Fatalf("reloaded foreign keys = %+v, want ON DELETE/UPDATE CASCADE", stored.ForeignKeys)
  367. }
  368. reopened := newExec(reloaded, storage.NewTableManager(pool, reloaded, "testdb"))
  369. execMust(t, reopened, "UPDATE parent SET id = 2 WHERE id = 1")
  370. if res := execMust(t, reopened, "SELECT parent_id FROM child WHERE id = 10"); res.Rows[0][0] != int64(2) {
  371. t.Fatalf("child after reloaded ON UPDATE CASCADE = %v, want 2", res.Rows[0][0])
  372. }
  373. execMust(t, reopened, "DELETE FROM parent WHERE id = 2")
  374. if got := countRows(t, reopened, "child"); got != 0 {
  375. t.Fatalf("child rows after reloaded ON DELETE CASCADE = %d, want 0", got)
  376. }
  377. }
  378. // The emulated SQLite catalog reproduces every accepted referential action.
  379. func TestForeignKeyCascadeInSQLiteCatalog(t *testing.T) {
  380. _, schema, table := newTestDB(t)
  381. e := newExec(schema, table)
  382. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  383. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER, CONSTRAINT child_parent FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE CASCADE ON UPDATE SET NULL)")
  384. execMust(t, e, "CREATE TABLE defaulted (id INTEGER PRIMARY KEY, parent_id INTEGER DEFAULT 1 REFERENCES parent(id) ON DELETE SET DEFAULT)")
  385. res := execMust(t, e, "SELECT sql FROM sqlite_master WHERE name = 'child'")
  386. sql, _ := res.Rows[0][0].(string)
  387. want := "CONSTRAINT `child_parent` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`) ON DELETE CASCADE ON UPDATE SET NULL"
  388. if !strings.Contains(sql, want) {
  389. t.Fatalf("catalog SQL %q does not contain %q", sql, want)
  390. }
  391. res = execMust(t, e, "SELECT sql FROM sqlite_master WHERE name = 'defaulted'")
  392. sql, _ = res.Rows[0][0].(string)
  393. if !strings.Contains(sql, "ON DELETE SET DEFAULT") {
  394. t.Fatalf("catalog SQL %q does not contain the SET DEFAULT action", sql)
  395. }
  396. }
  397. // The capability document advertises the supported referential actions.
  398. func TestForeignKeyCascadeCapability(t *testing.T) {
  399. notes := capabilities.Current().Constraints.ForeignKey.Notes
  400. for _, want := range []string{"CASCADE", "SET NULL", "SET DEFAULT", "autocommit", "no later than COMMIT", "O(FK component)"} {
  401. if !strings.Contains(notes, want) {
  402. t.Fatalf("foreign_key capability notes = %q, want %q", notes, want)
  403. }
  404. }
  405. }
  406. // A cascading delete that races a child insert must not deadlock on the
  407. // database-wide foreign-key write gate. Either statement may lose (a
  408. // serialization failure or a 23503), but the pair always completes and never
  409. // leaves a dangling child row.
  410. func TestForeignKeyCascadeConcurrentDeleteNoDeadlock(t *testing.T) {
  411. _, schema, table := newTestDB(t)
  412. deleter := newExec(schema, table)
  413. inserter := newExec(schema, table)
  414. execMust(t, inserter, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  415. execMust(t, inserter, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id) ON DELETE CASCADE)")
  416. const rounds = 40
  417. for round := 1; round <= rounds; round++ {
  418. execMust(t, inserter, fmt.Sprintf("INSERT INTO parent (id) VALUES (%d)", round))
  419. start := make(chan struct{})
  420. results := make(chan error, 2)
  421. var wg sync.WaitGroup
  422. wg.Add(2)
  423. go func() {
  424. defer wg.Done()
  425. <-start
  426. _, err := execSQL(deleter, fmt.Sprintf("DELETE FROM parent WHERE id = %d", round))
  427. results <- err
  428. }()
  429. go func() {
  430. defer wg.Done()
  431. <-start
  432. _, err := execSQL(inserter, fmt.Sprintf("INSERT INTO child (id, parent_id) VALUES (%d, %d)", round, round))
  433. results <- err
  434. }()
  435. close(start)
  436. done := make(chan struct{})
  437. go func() {
  438. wg.Wait()
  439. close(done)
  440. }()
  441. select {
  442. case <-done:
  443. case <-time.After(30 * time.Second):
  444. t.Fatalf("round %d: cascading delete and child insert deadlocked", round)
  445. }
  446. close(results)
  447. successes := 0
  448. for err := range results {
  449. if err == nil {
  450. successes++
  451. continue
  452. }
  453. var violation *storage.ForeignKeyViolationError
  454. if !errors.As(err, &violation) && !errors.Is(err, storage.ErrSerialization) {
  455. t.Fatalf("round %d: unexpected error %v", round, err)
  456. }
  457. }
  458. if successes == 0 {
  459. t.Fatalf("round %d: both statements failed", round)
  460. }
  461. dangling := execMust(t, inserter, "SELECT count(*) FROM child c WHERE NOT EXISTS (SELECT 1 FROM parent p WHERE p.id = c.parent_id)")
  462. if dangling.Rows[0][0] != int64(0) {
  463. t.Fatalf("round %d: %v dangling child rows", round, dangling.Rows[0][0])
  464. }
  465. execMust(t, inserter, "DELETE FROM child")
  466. execMust(t, inserter, fmt.Sprintf("DELETE FROM parent WHERE id = %d", round))
  467. }
  468. }
  469. // A cascaded rewrite moves the child's primary key when a foreign-key column is
  470. // part of it.
  471. func TestForeignKeyCascadeChildPrimaryKeyMoves(t *testing.T) {
  472. _, schema, table := newTestDB(t)
  473. e := newExec(schema, table)
  474. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  475. execMust(t, e, "CREATE TABLE child (id INTEGER, parent_id INTEGER REFERENCES parent(id) ON UPDATE CASCADE ON DELETE CASCADE, PRIMARY KEY (parent_id, id))")
  476. execMust(t, e, "INSERT INTO parent (id) VALUES (1)")
  477. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1), (11, 1)")
  478. execMust(t, e, "UPDATE parent SET id = 2 WHERE id = 1")
  479. res := execMust(t, e, "SELECT id, parent_id FROM child ORDER BY id")
  480. if res.RowCount != 2 || res.Rows[0][1] != int64(2) || res.Rows[1][1] != int64(2) {
  481. t.Fatalf("child rows after composite key move = %v, want both parent_id 2", res.Rows)
  482. }
  483. execMust(t, e, "DELETE FROM parent WHERE id = 2")
  484. if got := countRows(t, e, "child"); got != 0 {
  485. t.Fatalf("child rows after cascade = %d, want 0", got)
  486. }
  487. }
  488. // A cascade that makes a UNIQUE index collide rolls back the whole delete.
  489. func TestForeignKeyCascadeUniqueViolationRollsBack(t *testing.T) {
  490. _, schema, table := newTestDB(t)
  491. e := newExec(schema, table)
  492. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  493. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER UNIQUE DEFAULT 1 REFERENCES parent(id) ON DELETE SET DEFAULT)")
  494. execMust(t, e, "INSERT INTO parent (id) VALUES (1), (2)")
  495. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1), (11, 2)")
  496. _, err := execSQL(e, "DELETE FROM parent WHERE id = 2")
  497. if err == nil {
  498. t.Fatal("cascade with a UNIQUE collision was accepted")
  499. }
  500. var violation *storage.UniqueViolationError
  501. if !errors.As(err, &violation) {
  502. t.Fatalf("error = %v, want UniqueViolationError", err)
  503. }
  504. if got := countRows(t, e, "parent"); got != 2 {
  505. t.Fatalf("parent rows after rejected cascade = %d, want 2", got)
  506. }
  507. if res := execMust(t, e, "SELECT id, parent_id FROM child ORDER BY id"); res.RowCount != 2 || res.Rows[1][1] != int64(2) {
  508. t.Fatalf("child rows after rejected cascade = %v, want the original values", res.Rows)
  509. }
  510. }
  511. // Inside an explicit transaction the commit-time foreign-key closure validation
  512. // rejects a cascade that leaves a downstream NO ACTION row dangling, and the
  513. // whole transaction is discarded.
  514. func TestForeignKeyCascadeTransactionDownstreamFailure(t *testing.T) {
  515. _, schema, table := newTestDB(t)
  516. e := newExec(schema, table)
  517. execMust(t, e, "CREATE TABLE grandparent (id INTEGER PRIMARY KEY)")
  518. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY, gp_id INTEGER REFERENCES grandparent(id) ON DELETE CASCADE)")
  519. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  520. execMust(t, e, "INSERT INTO grandparent (id) VALUES (1)")
  521. execMust(t, e, "INSERT INTO parent (id, gp_id) VALUES (10, 1)")
  522. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (100, 10)")
  523. execMust(t, e, "BEGIN")
  524. execMust(t, e, "DELETE FROM grandparent WHERE id = 1")
  525. _, err := execSQL(e, "COMMIT")
  526. requireForeignKeyViolation(t, err)
  527. if got := countRows(t, e, "grandparent"); got != 1 {
  528. t.Fatalf("grandparent rows after rejected commit = %d, want 1", got)
  529. }
  530. if got := countRows(t, e, "parent"); got != 1 {
  531. t.Fatalf("parent rows after rejected commit = %d, want 1", got)
  532. }
  533. if got := countRows(t, e, "child"); got != 1 {
  534. t.Fatalf("child rows after rejected commit = %d, want 1", got)
  535. }
  536. // The same transaction commits once the downstream row is part of the
  537. // cascade itself.
  538. execMust(t, e, "BEGIN")
  539. execMust(t, e, "DELETE FROM child WHERE id = 100")
  540. execMust(t, e, "DELETE FROM grandparent WHERE id = 1")
  541. execMust(t, e, "COMMIT")
  542. if got := countRows(t, e, "grandparent") + countRows(t, e, "parent") + countRows(t, e, "child"); got != 0 {
  543. t.Fatalf("rows after committed cascade = %d, want 0", got)
  544. }
  545. }
  546. // A referenced UNIQUE column may be nullable: moving it to NULL cascades the
  547. // NULL into the child, which leaves the reference in its MATCH SIMPLE form.
  548. func TestForeignKeyUpdateCascadeToNull(t *testing.T) {
  549. _, schema, table := newTestDB(t)
  550. e := newExec(schema, table)
  551. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY, code TEXT UNIQUE)")
  552. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, code TEXT REFERENCES parent(code) ON UPDATE CASCADE)")
  553. execMust(t, e, "INSERT INTO parent (id, code) VALUES (1, 'x')")
  554. execMust(t, e, "INSERT INTO child (id, code) VALUES (10, 'x')")
  555. execMust(t, e, "UPDATE parent SET code = NULL WHERE id = 1")
  556. if res := execMust(t, e, "SELECT code FROM child WHERE id = 10"); res.Rows[0][0] != nil {
  557. t.Fatalf("child.code after cascade to NULL = %v, want NULL", res.Rows[0][0])
  558. }
  559. }
  560. // UPDATE ... FROM that moves a parent key must apply ON UPDATE actions like a
  561. // plain UPDATE. The executor used to implement the move as DELETE + INSERT,
  562. // which fired ON DELETE CASCADE and silently dropped the children.
  563. func TestForeignKeyUpdateFromPrimaryKeyMoveCascades(t *testing.T) {
  564. for _, tc := range []struct {
  565. name string
  566. constraint string
  567. }{
  568. {name: "on delete and update cascade", constraint: "ON DELETE CASCADE ON UPDATE CASCADE"},
  569. {name: "on update cascade only", constraint: "ON UPDATE CASCADE"},
  570. } {
  571. t.Run(tc.name, func(t *testing.T) {
  572. _, schema, table := newTestDB(t)
  573. e := newExec(schema, table)
  574. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  575. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id) "+tc.constraint+")")
  576. execMust(t, e, "CREATE TABLE src (id INTEGER PRIMARY KEY, new_id INTEGER)")
  577. execMust(t, e, "INSERT INTO parent (id) VALUES (1)")
  578. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  579. execMust(t, e, "INSERT INTO src (id, new_id) VALUES (1, 2)")
  580. res := execMust(t, e, "UPDATE parent SET id = src.new_id FROM src WHERE src.id = parent.id")
  581. if res.RowsAffected != 1 {
  582. t.Fatalf("rows affected = %d, want 1", res.RowsAffected)
  583. }
  584. if got := countRows(t, e, "child"); got != 1 {
  585. t.Fatalf("child rows after UPDATE ... FROM key move = %d, want 1 (children were deleted)", got)
  586. }
  587. res = execMust(t, e, "SELECT id, parent_id FROM child")
  588. if res.Rows[0][0] != int64(10) || res.Rows[0][1] != int64(2) {
  589. t.Fatalf("child after UPDATE ... FROM key move = %v, want (10, 2)", res.Rows)
  590. }
  591. if res := execMust(t, e, "SELECT id FROM parent"); res.RowCount != 1 || res.Rows[0][0] != int64(2) {
  592. t.Fatalf("parent after UPDATE ... FROM key move = %v, want id 2", res.Rows)
  593. }
  594. })
  595. }
  596. }
  597. // A cascade failure inside an explicit transaction is detected at the statement
  598. // and rolls back to the statement savepoint, so the transaction can continue.
  599. // A plain NO ACTION/RESTRICT violation is instead deferred to COMMIT, and the
  600. // statement itself succeeds.
  601. func TestForeignKeyCascadeTransactionErrorTiming(t *testing.T) {
  602. _, schema, table := newTestDB(t)
  603. e := newExec(schema, table)
  604. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  605. execMust(t, e, "CREATE TABLE required (id INTEGER PRIMARY KEY, parent_id INTEGER NOT NULL REFERENCES parent(id) ON DELETE SET NULL)")
  606. execMust(t, e, "CREATE TABLE restricted (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  607. execMust(t, e, "INSERT INTO parent (id) VALUES (1), (2)")
  608. execMust(t, e, "INSERT INTO required (id, parent_id) VALUES (10, 1)")
  609. // SET NULL on a NOT NULL child fails at the statement with 23502.
  610. execMust(t, e, "BEGIN")
  611. _, err := execSQL(e, "DELETE FROM parent WHERE id = 1")
  612. requireNotNullViolation(t, err)
  613. // The failed cascade rolled back to the statement savepoint: the parent and
  614. // child are still visible to the transaction and the transaction is usable.
  615. if res := execMust(t, e, "SELECT count(*) FROM parent"); res.Rows[0][0] != int64(2) {
  616. t.Fatalf("parent rows after statement rollback = %v, want 2", res.Rows)
  617. }
  618. if res := execMust(t, e, "SELECT parent_id FROM required WHERE id = 10"); res.Rows[0][0] != int64(1) {
  619. t.Fatalf("required.parent_id after statement rollback = %v, want 1", res.Rows[0][0])
  620. }
  621. execMust(t, e, "ROLLBACK")
  622. // A NO ACTION/RESTRICT violation is deferred to COMMIT: the DELETE succeeds
  623. // as a statement and the transaction fails on commit.
  624. execMust(t, e, "INSERT INTO restricted (id, parent_id) VALUES (20, 2)")
  625. execMust(t, e, "BEGIN")
  626. execMust(t, e, "DELETE FROM parent WHERE id = 2")
  627. _, err = execSQL(e, "COMMIT")
  628. requireForeignKeyViolation(t, err)
  629. if res := execMust(t, e, "SELECT count(*) FROM parent"); res.Rows[0][0] != int64(2) {
  630. t.Fatalf("parent rows after rejected commit = %v, want 2", res.Rows)
  631. }
  632. }