2
0

foreign_key_test.go 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. package executor
  2. import (
  3. "errors"
  4. "strings"
  5. "testing"
  6. "github.com/danfragoso/pizzasql-next/pkg/pgerror"
  7. "github.com/danfragoso/pizzasql-next/pkg/storage"
  8. )
  9. // requireForeignKeyViolation asserts err is a foreign-key violation and returns
  10. // its label so a test can check which constraint fired.
  11. func requireForeignKeyViolation(t *testing.T, err error) *storage.ForeignKeyViolationError {
  12. t.Helper()
  13. var violation *storage.ForeignKeyViolationError
  14. if !errors.As(err, &violation) {
  15. t.Fatalf("error = %v, want ForeignKeyViolationError", err)
  16. }
  17. if !strings.HasPrefix(err.Error(), "FOREIGN KEY constraint failed") {
  18. t.Fatalf("message = %q, want the FOREIGN KEY failure prefix", err.Error())
  19. }
  20. return violation
  21. }
  22. func requireUnsupported(t *testing.T, err error, want string) {
  23. t.Helper()
  24. if err == nil {
  25. t.Fatalf("expected an error containing %q, got nil", want)
  26. }
  27. if !errors.Is(err, pgerror.ErrFeatureNotSupported) {
  28. t.Fatalf("error = %v, want ErrFeatureNotSupported (0A000)", err)
  29. }
  30. if !strings.Contains(err.Error(), want) {
  31. t.Fatalf("error = %q, want it to contain %q", err, want)
  32. }
  33. }
  34. // Inline REFERENCES, table-level FOREIGN KEY, and the named forms all persist
  35. // and enforce the same constraint.
  36. func TestForeignKeyDeclarationForms(t *testing.T) {
  37. tests := []struct {
  38. name string
  39. ddl string
  40. columns string
  41. refColumns string
  42. }{
  43. {
  44. name: "inline",
  45. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))",
  46. columns: "parent_id",
  47. refColumns: "id",
  48. },
  49. {
  50. name: "inline without column list uses the parent primary key",
  51. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent)",
  52. columns: "parent_id",
  53. refColumns: "id",
  54. },
  55. {
  56. name: "table level",
  57. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER, FOREIGN KEY (parent_id) REFERENCES parent(id))",
  58. columns: "parent_id",
  59. refColumns: "id",
  60. },
  61. {
  62. name: "named table level",
  63. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER, CONSTRAINT child_parent FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE RESTRICT ON UPDATE NO ACTION)",
  64. columns: "parent_id",
  65. refColumns: "id",
  66. },
  67. }
  68. for _, tt := range tests {
  69. t.Run(tt.name, func(t *testing.T) {
  70. _, schema, table := newTestDB(t)
  71. e := newExec(schema, table)
  72. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  73. execMust(t, e, tt.ddl)
  74. stored, err := schema.GetSchema("child")
  75. if err != nil {
  76. t.Fatalf("get schema: %v", err)
  77. }
  78. if len(stored.ForeignKeys) != 1 {
  79. t.Fatalf("foreign keys = %+v, want exactly one", stored.ForeignKeys)
  80. }
  81. fk := stored.ForeignKeys[0]
  82. if strings.Join(fk.Columns, ",") != tt.columns {
  83. t.Fatalf("columns = %v, want %s", fk.Columns, tt.columns)
  84. }
  85. if fk.RefTable != "parent" {
  86. t.Fatalf("ref table = %q, want parent", fk.RefTable)
  87. }
  88. if strings.Join(fk.RefColumns, ",") != tt.refColumns {
  89. t.Fatalf("ref columns = %v, want %s", fk.RefColumns, tt.refColumns)
  90. }
  91. execMust(t, e, "INSERT INTO parent (id) VALUES (1)")
  92. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  93. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (11, NULL)")
  94. _, err = execSQL(e, "INSERT INTO child (id, parent_id) VALUES (12, 2)")
  95. requireForeignKeyViolation(t, err)
  96. })
  97. }
  98. }
  99. // A composite foreign key matches the parent key component by component.
  100. func TestForeignKeyComposite(t *testing.T) {
  101. _, schema, table := newTestDB(t)
  102. e := newExec(schema, table)
  103. execMust(t, e, "CREATE TABLE parent (a TEXT, b INTEGER, PRIMARY KEY (a, b))")
  104. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, pa TEXT, pb INTEGER, FOREIGN KEY (pa, pb) REFERENCES parent(a, b))")
  105. stored, err := schema.GetSchema("child")
  106. if err != nil {
  107. t.Fatalf("get schema: %v", err)
  108. }
  109. if len(stored.ForeignKeys) != 1 || strings.Join(stored.ForeignKeys[0].RefColumns, ",") != "a,b" {
  110. t.Fatalf("foreign keys = %+v, want (pa,pb) -> parent(a,b)", stored.ForeignKeys)
  111. }
  112. execMust(t, e, "INSERT INTO parent (a, b) VALUES ('x', 1)")
  113. execMust(t, e, "INSERT INTO child (id, pa, pb) VALUES (1, 'x', 1)")
  114. _, err = execSQL(e, "INSERT INTO child (id, pa, pb) VALUES (2, 'x', 2)")
  115. requireForeignKeyViolation(t, err)
  116. _, err = execSQL(e, "INSERT INTO child (id, pa, pb) VALUES (3, 'x', 1.5)")
  117. requireForeignKeyViolation(t, err)
  118. // Any NULL component disables the constraint (MATCH SIMPLE).
  119. execMust(t, e, "INSERT INTO child (id, pa, pb) VALUES (4, NULL, 1)")
  120. execMust(t, e, "INSERT INTO child (id, pa, pb) VALUES (5, 'x', NULL)")
  121. if res := execMust(t, e, "SELECT count(*) FROM child"); res.Rows[0][0] != int64(3) {
  122. t.Fatalf("child rows = %v, want 3", res.Rows)
  123. }
  124. }
  125. // A child INSERT with a missing parent is rejected before anything is written.
  126. func TestForeignKeyInsertViolation(t *testing.T) {
  127. _, schema, table := newTestDB(t)
  128. e := newExec(schema, table)
  129. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  130. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  131. _, err := execSQL(e, "INSERT INTO child (id, parent_id) VALUES (1, 42)")
  132. requireForeignKeyViolation(t, err)
  133. if res := execMust(t, e, "SELECT count(*) FROM child"); res.Rows[0][0] != int64(0) {
  134. t.Fatalf("child rows after rejected insert = %v, want 0", res.Rows)
  135. }
  136. execMust(t, e, "INSERT INTO parent (id) VALUES (42)")
  137. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (1, 42)")
  138. }
  139. // A multi-row INSERT is atomic: one dangling row rejects the whole statement.
  140. func TestForeignKeyMultiRowInsertIsAtomic(t *testing.T) {
  141. _, schema, table := newTestDB(t)
  142. e := newExec(schema, table)
  143. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  144. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  145. execMust(t, e, "INSERT INTO parent (id) VALUES (1)")
  146. _, err := execSQL(e, "INSERT INTO child (id, parent_id) VALUES (10, 1), (11, 2), (12, 1)")
  147. requireForeignKeyViolation(t, err)
  148. if res := execMust(t, e, "SELECT count(*) FROM child"); res.Rows[0][0] != int64(0) {
  149. t.Fatalf("child rows after rejected multi-row insert = %v, want 0", res.Rows)
  150. }
  151. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1), (12, 1)")
  152. if res := execMust(t, e, "SELECT count(*) FROM child"); res.Rows[0][0] != int64(2) {
  153. t.Fatalf("child rows = %v, want 2", res.Rows)
  154. }
  155. }
  156. // Updating the child foreign-key column to a missing parent is rejected, and a
  157. // valid update is accepted.
  158. func TestForeignKeyChildUpdate(t *testing.T) {
  159. _, schema, table := newTestDB(t)
  160. e := newExec(schema, table)
  161. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  162. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  163. execMust(t, e, "INSERT INTO parent (id) VALUES (1), (2)")
  164. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  165. _, err := execSQL(e, "UPDATE child SET parent_id = 99 WHERE id = 10")
  166. requireForeignKeyViolation(t, err)
  167. res := execMust(t, e, "SELECT parent_id FROM child WHERE id = 10")
  168. if res.Rows[0][0] != int64(1) {
  169. t.Fatalf("parent_id after rejected update = %v, want 1", res.Rows[0][0])
  170. }
  171. execMust(t, e, "UPDATE child SET parent_id = 2 WHERE id = 10")
  172. res = execMust(t, e, "SELECT parent_id FROM child WHERE id = 10")
  173. if res.Rows[0][0] != int64(2) {
  174. t.Fatalf("parent_id after valid update = %v, want 2", res.Rows[0][0])
  175. }
  176. // The same check applies to the update half of an upsert.
  177. _, err = execSQL(e, "INSERT INTO child (id, parent_id) VALUES (10, 99) ON CONFLICT (id) DO UPDATE SET parent_id = excluded.parent_id")
  178. requireForeignKeyViolation(t, err)
  179. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1) ON CONFLICT (id) DO UPDATE SET parent_id = excluded.parent_id")
  180. res = execMust(t, e, "SELECT parent_id FROM child WHERE id = 10")
  181. if res.Rows[0][0] != int64(1) {
  182. t.Fatalf("parent_id after upsert = %v, want 1", res.Rows[0][0])
  183. }
  184. }
  185. // Deleting a referenced parent is rejected; deleting the child first makes it
  186. // possible, and a parent with no children can always be deleted.
  187. func TestForeignKeyParentDelete(t *testing.T) {
  188. _, schema, table := newTestDB(t)
  189. e := newExec(schema, table)
  190. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  191. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  192. execMust(t, e, "INSERT INTO parent (id) VALUES (1), (2)")
  193. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  194. _, err := execSQL(e, "DELETE FROM parent WHERE id = 1")
  195. requireForeignKeyViolation(t, err)
  196. if res := execMust(t, e, "SELECT count(*) FROM parent"); res.Rows[0][0] != int64(2) {
  197. t.Fatalf("parent rows after rejected delete = %v, want 2", res.Rows)
  198. }
  199. // A scan-based delete takes the same path through the transaction commit.
  200. _, err = execSQL(e, "DELETE FROM parent")
  201. requireForeignKeyViolation(t, err)
  202. execMust(t, e, "DELETE FROM child WHERE id = 10")
  203. execMust(t, e, "DELETE FROM parent WHERE id = 1")
  204. execMust(t, e, "DELETE FROM parent WHERE id = 2")
  205. }
  206. // Updating a referenced parent key is rejected while a child points at it and
  207. // accepted once nothing references the old value.
  208. func TestForeignKeyParentKeyUpdate(t *testing.T) {
  209. _, schema, table := newTestDB(t)
  210. e := newExec(schema, table)
  211. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY, v TEXT)")
  212. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  213. execMust(t, e, "INSERT INTO parent (id, v) VALUES (1, 'one'), (2, 'two')")
  214. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  215. // A non-referenced column update is always allowed.
  216. execMust(t, e, "UPDATE parent SET v = 'uno' WHERE id = 1")
  217. _, err := execSQL(e, "UPDATE parent SET id = 5 WHERE id = 1")
  218. requireForeignKeyViolation(t, err)
  219. res := execMust(t, e, "SELECT id FROM parent WHERE v = 'uno'")
  220. if res.Rows[0][0] != int64(1) {
  221. t.Fatalf("parent id after rejected update = %v, want 1", res.Rows[0][0])
  222. }
  223. execMust(t, e, "DELETE FROM child WHERE id = 10")
  224. execMust(t, e, "UPDATE parent SET id = 5 WHERE id = 1")
  225. if res := execMust(t, e, "SELECT id FROM parent WHERE v = 'uno'"); res.Rows[0][0] != int64(5) {
  226. t.Fatalf("parent id after valid update = %v, want 5", res.Rows[0][0])
  227. }
  228. }
  229. // A row may reference itself and another row of the same table; a dangling
  230. // self-reference is rejected.
  231. func TestForeignKeySelfReference(t *testing.T) {
  232. _, schema, table := newTestDB(t)
  233. e := newExec(schema, table)
  234. execMust(t, e, "CREATE TABLE nodes (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES nodes(id))")
  235. execMust(t, e, "INSERT INTO nodes (id, parent_id) VALUES (1, 1)")
  236. execMust(t, e, "INSERT INTO nodes (id, parent_id) VALUES (2, 1)")
  237. execMust(t, e, "INSERT INTO nodes (id, parent_id) VALUES (3, NULL)")
  238. _, err := execSQL(e, "INSERT INTO nodes (id, parent_id) VALUES (4, 99)")
  239. requireForeignKeyViolation(t, err)
  240. // Row 1 exists and references itself, but row 2 also references it, so the
  241. // delete is rejected because another row still points at it.
  242. _, err = execSQL(e, "DELETE FROM nodes WHERE id = 1")
  243. requireForeignKeyViolation(t, err)
  244. // A row that only references itself can be deleted: the row and the
  245. // reference leave together.
  246. execMust(t, e, "INSERT INTO nodes (id, parent_id) VALUES (5, 5)")
  247. execMust(t, e, "DELETE FROM nodes WHERE id = 5")
  248. // Once the other child is gone, node 1's self-reference no longer blocks.
  249. execMust(t, e, "DELETE FROM nodes WHERE id = 2")
  250. execMust(t, e, "DELETE FROM nodes WHERE id = 1")
  251. execMust(t, e, "INSERT INTO nodes (id, parent_id) VALUES (1, 1)")
  252. // A composite self-reference resolves against this table's table-level key.
  253. execMust(t, e, `CREATE TABLE tree (
  254. a TEXT NOT NULL,
  255. b TEXT NOT NULL,
  256. pa TEXT,
  257. pb TEXT,
  258. PRIMARY KEY (a, b),
  259. FOREIGN KEY (pa, pb) REFERENCES tree(a, b)
  260. )`)
  261. execMust(t, e, "INSERT INTO tree (a, b, pa, pb) VALUES ('r', '1', 'r', '1')")
  262. execMust(t, e, "INSERT INTO tree (a, b, pa, pb) VALUES ('c', '1', 'r', '1')")
  263. _, err = execSQL(e, "INSERT INTO tree (a, b, pa, pb) VALUES ('x', '1', 'missing', '1')")
  264. requireForeignKeyViolation(t, err)
  265. }
  266. // A transaction is validated at COMMIT against its final state, so a staged
  267. // child insert and a staged parent delete cannot commit into a dangling
  268. // reference in either order.
  269. func TestForeignKeyTransactionCommitEnforcement(t *testing.T) {
  270. _, schema, table := newTestDB(t)
  271. e := newExec(schema, table)
  272. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  273. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  274. execMust(t, e, "INSERT INTO parent (id) VALUES (1)")
  275. t.Run("staged child insert then parent delete", func(t *testing.T) {
  276. execMust(t, e, "BEGIN")
  277. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  278. execMust(t, e, "DELETE FROM parent WHERE id = 1")
  279. _, err := execSQL(e, "COMMIT")
  280. requireForeignKeyViolation(t, err)
  281. if res := execMust(t, e, "SELECT count(*) FROM child"); res.Rows[0][0] != int64(0) {
  282. t.Fatalf("child rows after rejected commit = %v, want 0", res.Rows)
  283. }
  284. if res := execMust(t, e, "SELECT count(*) FROM parent"); res.Rows[0][0] != int64(1) {
  285. t.Fatalf("parent rows after rejected commit = %v, want 1", res.Rows)
  286. }
  287. })
  288. t.Run("parent delete then staged child insert", func(t *testing.T) {
  289. execMust(t, e, "BEGIN")
  290. execMust(t, e, "DELETE FROM parent WHERE id = 1")
  291. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  292. _, err := execSQL(e, "COMMIT")
  293. requireForeignKeyViolation(t, err)
  294. execMust(t, e, "SELECT count(*) FROM parent")
  295. })
  296. t.Run("staged child insert with missing parent", func(t *testing.T) {
  297. execMust(t, e, "BEGIN")
  298. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (11, 77)")
  299. _, err := execSQL(e, "COMMIT")
  300. requireForeignKeyViolation(t, err)
  301. if res := execMust(t, e, "SELECT count(*) FROM child"); res.Rows[0][0] != int64(0) {
  302. t.Fatalf("child rows after rejected commit = %v, want 0", res.Rows)
  303. }
  304. })
  305. }
  306. // Deleting a parent and its children in one transaction succeeds in either
  307. // order because the commit sees the final, consistent state.
  308. func TestForeignKeyTransactionDeleteParentAndChild(t *testing.T) {
  309. for _, childrenFirst := range []bool{false, true} {
  310. name := "parent first"
  311. if childrenFirst {
  312. name = "children first"
  313. }
  314. t.Run(name, func(t *testing.T) {
  315. _, schema, table := newTestDB(t)
  316. e := newExec(schema, table)
  317. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  318. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  319. execMust(t, e, "INSERT INTO parent (id) VALUES (1), (2)")
  320. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1), (11, 1)")
  321. execMust(t, e, "BEGIN")
  322. if childrenFirst {
  323. execMust(t, e, "DELETE FROM child WHERE parent_id = 1")
  324. execMust(t, e, "DELETE FROM parent WHERE id = 1")
  325. } else {
  326. execMust(t, e, "DELETE FROM parent WHERE id = 1")
  327. execMust(t, e, "DELETE FROM child WHERE parent_id = 1")
  328. }
  329. execMust(t, e, "COMMIT")
  330. if res := execMust(t, e, "SELECT count(*) FROM parent"); res.Rows[0][0] != int64(1) {
  331. t.Fatalf("parent rows = %v, want 1", res.Rows)
  332. }
  333. if res := execMust(t, e, "SELECT count(*) FROM child"); res.Rows[0][0] != int64(0) {
  334. t.Fatalf("child rows = %v, want 0", res.Rows)
  335. }
  336. })
  337. }
  338. }
  339. // A parent column is only a valid target when it is the primary key or covered
  340. // by a UNIQUE index, and forward references fail at CREATE time.
  341. func TestForeignKeyDDLRejections(t *testing.T) {
  342. tests := []struct {
  343. name string
  344. setup []string
  345. ddl string
  346. want string
  347. }{
  348. {
  349. name: "parent does not exist",
  350. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))",
  351. want: "does not exist",
  352. },
  353. {
  354. name: "parent columns are not unique",
  355. setup: []string{"CREATE TABLE parent (id INTEGER PRIMARY KEY, code TEXT)"},
  356. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, code TEXT REFERENCES parent(code))",
  357. want: "foreign key mismatch",
  358. },
  359. {
  360. name: "composite parent columns are not a unique set",
  361. setup: []string{"CREATE TABLE parent (a TEXT UNIQUE, b INTEGER)"},
  362. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, a TEXT, b INTEGER, FOREIGN KEY (a, b) REFERENCES parent(a, b))",
  363. want: "foreign key mismatch",
  364. },
  365. {
  366. name: "unknown child column",
  367. setup: []string{"CREATE TABLE parent (id INTEGER PRIMARY KEY)"},
  368. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, FOREIGN KEY (missing) REFERENCES parent(id))",
  369. want: `column "missing"`,
  370. },
  371. {
  372. name: "unknown parent column",
  373. setup: []string{"CREATE TABLE parent (id INTEGER PRIMARY KEY)"},
  374. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(missing))",
  375. want: "does not exist in table parent",
  376. },
  377. {
  378. name: "column count mismatch",
  379. setup: []string{"CREATE TABLE parent (a TEXT, b TEXT, PRIMARY KEY (a, b))"},
  380. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, a TEXT, FOREIGN KEY (a) REFERENCES parent(a, b))",
  381. want: "same number of columns",
  382. },
  383. {
  384. name: "on delete cascade",
  385. setup: []string{"CREATE TABLE parent (id INTEGER PRIMARY KEY)"},
  386. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id) ON DELETE CASCADE)",
  387. want: "cascades are not implemented",
  388. },
  389. {
  390. name: "on update cascade",
  391. setup: []string{"CREATE TABLE parent (id INTEGER PRIMARY KEY)"},
  392. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER, FOREIGN KEY (parent_id) REFERENCES parent(id) ON UPDATE CASCADE)",
  393. want: "cascades are not implemented",
  394. },
  395. {
  396. name: "on delete set null",
  397. setup: []string{"CREATE TABLE parent (id INTEGER PRIMARY KEY)"},
  398. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER, FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE SET NULL)",
  399. want: "cascades are not implemented",
  400. },
  401. {
  402. name: "on delete set default",
  403. setup: []string{"CREATE TABLE parent (id INTEGER PRIMARY KEY)"},
  404. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER, FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE SET DEFAULT)",
  405. want: "cascades are not implemented",
  406. },
  407. {
  408. name: "deferrable",
  409. setup: []string{"CREATE TABLE parent (id INTEGER PRIMARY KEY)"},
  410. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id) DEFERRABLE INITIALLY DEFERRED)",
  411. want: "DEFERRABLE",
  412. },
  413. }
  414. for _, tt := range tests {
  415. t.Run(tt.name, func(t *testing.T) {
  416. _, schema, table := newTestDB(t)
  417. e := newExec(schema, table)
  418. for _, sql := range tt.setup {
  419. execMust(t, e, sql)
  420. }
  421. _, err := execSQL(e, tt.ddl)
  422. requireUnsupported(t, err, tt.want)
  423. if schema.TableExists("child") {
  424. t.Fatal("rejected CREATE TABLE left a schema behind")
  425. }
  426. })
  427. }
  428. // ALTER TABLE ADD COLUMN still cannot introduce a foreign key.
  429. _, schema, table := newTestDB(t)
  430. e := newExec(schema, table)
  431. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  432. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  433. _, err := execSQL(e, "ALTER TABLE t ADD COLUMN parent_id INTEGER REFERENCES parent(id)")
  434. requireUnsupported(t, err, "CREATE TABLE")
  435. }
  436. // The persisted constraint keeps being enforced after the schema is re-read
  437. // from storage.
  438. func TestForeignKeyDurableReload(t *testing.T) {
  439. pool, schema, table := newTestDB(t)
  440. e := newExec(schema, table)
  441. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  442. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER, CONSTRAINT child_parent FOREIGN KEY (parent_id) REFERENCES parent(id))")
  443. execMust(t, e, "INSERT INTO parent (id) VALUES (1)")
  444. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  445. reloaded := storage.NewSchemaManager(pool, "testdb")
  446. reopened := newExec(reloaded, storage.NewTableManager(pool, reloaded, "testdb"))
  447. stored, err := reloaded.GetSchema("child")
  448. if err != nil {
  449. t.Fatalf("reload schema: %v", err)
  450. }
  451. if len(stored.ForeignKeys) != 1 || stored.ForeignKeys[0].Name != "child_parent" {
  452. t.Fatalf("reloaded foreign keys = %+v, want child_parent", stored.ForeignKeys)
  453. }
  454. _, err = execSQL(reopened, "INSERT INTO child (id, parent_id) VALUES (11, 2)")
  455. requireForeignKeyViolation(t, err)
  456. _, err = execSQL(reopened, "DELETE FROM parent WHERE id = 1")
  457. requireForeignKeyViolation(t, err)
  458. execMust(t, reopened, "INSERT INTO child (id, parent_id) VALUES (12, 1)")
  459. }
  460. // The emulated SQLite catalog reports the foreign key in the table SQL.
  461. func TestForeignKeyInSQLiteCatalog(t *testing.T) {
  462. _, schema, table := newTestDB(t)
  463. e := newExec(schema, table)
  464. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  465. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER, CONSTRAINT child_parent FOREIGN KEY (parent_id) REFERENCES parent(id))")
  466. res := execMust(t, e, "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'child'")
  467. if res.RowCount != 1 {
  468. t.Fatalf("sqlite_master rows = %d, want 1", res.RowCount)
  469. }
  470. sql, _ := res.Rows[0][0].(string)
  471. for _, want := range []string{"CONSTRAINT `child_parent` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`)"} {
  472. if !strings.Contains(sql, want) {
  473. t.Fatalf("catalog SQL %q does not contain %q", sql, want)
  474. }
  475. }
  476. res = execMust(t, e, "SELECT sql FROM sqlite_schema WHERE name = 'child'")
  477. if res.RowCount != 1 || !strings.Contains(res.Rows[0][0].(string), "FOREIGN KEY") {
  478. t.Fatalf("sqlite_schema SQL = %v, want the foreign key", res.Rows)
  479. }
  480. // Declared referential actions round-trip through the catalog text.
  481. execMust(t, e, `CREATE TABLE child_actions (
  482. id INTEGER PRIMARY KEY,
  483. parent_id INTEGER,
  484. CONSTRAINT child_parent_actions FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE RESTRICT ON UPDATE NO ACTION
  485. )`)
  486. res = execMust(t, e, "SELECT sql FROM sqlite_master WHERE name = 'child_actions'")
  487. sql, _ = res.Rows[0][0].(string)
  488. want := "CONSTRAINT `child_parent_actions` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`) ON DELETE RESTRICT ON UPDATE NO ACTION"
  489. if !strings.Contains(sql, want) {
  490. t.Fatalf("catalog SQL %q does not contain %q", sql, want)
  491. }
  492. }
  493. // Foreign keys coexist with the composite primary key, CHECK, and default
  494. // support already in the engine.
  495. func TestForeignKeyRegressionCompositeKeyCheckDefaults(t *testing.T) {
  496. _, schema, table := newTestDB(t)
  497. e := newExec(schema, table)
  498. execMust(t, e, `CREATE TABLE parent (
  499. a TEXT,
  500. b INTEGER,
  501. PRIMARY KEY (a, b)
  502. )`)
  503. execMust(t, e, `CREATE TABLE child (
  504. id INTEGER PRIMARY KEY,
  505. pa TEXT NOT NULL,
  506. pb INTEGER NOT NULL,
  507. state TEXT NOT NULL DEFAULT 'new' CHECK (state IN ('new', 'done')),
  508. FOREIGN KEY (pa, pb) REFERENCES parent(a, b)
  509. )`)
  510. execMust(t, e, "INSERT INTO parent (a, b) VALUES ('x', 1)")
  511. execMust(t, e, "INSERT INTO child (id, pa, pb) VALUES (1, 'x', 1)")
  512. if res := execMust(t, e, "SELECT state FROM child WHERE id = 1"); res.Rows[0][0] != "new" {
  513. t.Fatalf("default state = %v, want new", res.Rows[0][0])
  514. }
  515. execMust(t, e, "UPDATE child SET state = 'done' WHERE id = 1")
  516. _, err := execSQL(e, "INSERT INTO child (id, pa, pb) VALUES (2, 'x', 2)")
  517. requireForeignKeyViolation(t, err)
  518. _, err = execSQL(e, "INSERT INTO child (id, pa, pb, state) VALUES (2, 'x', 1, 'bogus')")
  519. requireCheckViolation(t, err, "")
  520. _, err = execSQL(e, "INSERT INTO child (id, pa, pb) VALUES (1, 'x', 1)")
  521. if err == nil {
  522. t.Fatal("duplicate composite parent key or primary key was accepted")
  523. }
  524. }
  525. // A parent column covered by a UNIQUE index (not the primary key) is a valid
  526. // foreign-key target, whether the uniqueness comes from a constraint or an
  527. // explicit CREATE UNIQUE INDEX.
  528. func TestForeignKeyReferencesUniqueIndex(t *testing.T) {
  529. _, schema, table := newTestDB(t)
  530. e := newExec(schema, table)
  531. execMust(t, e, "CREATE TABLE by_constraint (id INTEGER PRIMARY KEY, code TEXT UNIQUE)")
  532. execMust(t, e, "CREATE TABLE by_index (id INTEGER PRIMARY KEY, code TEXT)")
  533. execMust(t, e, "CREATE UNIQUE INDEX idx_by_index_code ON by_index (code)")
  534. execMust(t, e, "CREATE TABLE child_constraint (id INTEGER PRIMARY KEY, code TEXT REFERENCES by_constraint(code))")
  535. execMust(t, e, "CREATE TABLE child_index (id INTEGER PRIMARY KEY, code TEXT REFERENCES by_index(code))")
  536. execMust(t, e, "INSERT INTO by_constraint (id, code) VALUES (1, 'a')")
  537. execMust(t, e, "INSERT INTO by_index (id, code) VALUES (1, 'b')")
  538. execMust(t, e, "INSERT INTO child_constraint (id, code) VALUES (10, 'a')")
  539. execMust(t, e, "INSERT INTO child_index (id, code) VALUES (10, 'b')")
  540. _, err := execSQL(e, "INSERT INTO child_constraint (id, code) VALUES (11, 'missing')")
  541. requireForeignKeyViolation(t, err)
  542. _, err = execSQL(e, "INSERT INTO child_index (id, code) VALUES (11, 'missing')")
  543. requireForeignKeyViolation(t, err)
  544. _, err = execSQL(e, "DELETE FROM by_constraint WHERE code = 'a'")
  545. requireForeignKeyViolation(t, err)
  546. _, err = execSQL(e, "DELETE FROM by_index WHERE code = 'b'")
  547. requireForeignKeyViolation(t, err)
  548. }
  549. // A scan-based parent UPDATE of a referenced column is validated at the
  550. // statement boundary just like a point update.
  551. func TestForeignKeyParentScanUpdate(t *testing.T) {
  552. _, schema, table := newTestDB(t)
  553. e := newExec(schema, table)
  554. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY, v INTEGER)")
  555. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  556. execMust(t, e, "INSERT INTO parent (id, v) VALUES (1, 1), (2, 1)")
  557. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  558. _, err := execSQL(e, "UPDATE parent SET id = id + 10 WHERE v = 1")
  559. requireForeignKeyViolation(t, err)
  560. if res := execMust(t, e, "SELECT count(*) FROM parent WHERE id > 10"); res.Rows[0][0] != int64(0) {
  561. t.Fatalf("rejected scan update moved rows: %v", res.Rows)
  562. }
  563. execMust(t, e, "DELETE FROM child WHERE id = 10")
  564. execMust(t, e, "UPDATE parent SET id = id + 10 WHERE v = 1")
  565. if res := execMust(t, e, "SELECT count(*) FROM parent WHERE id > 10"); res.Rows[0][0] != int64(2) {
  566. t.Fatalf("valid scan update rows = %v, want 2", res.Rows)
  567. }
  568. }
  569. // INSERT ... SELECT goes through the same enforcement as a VALUES insert.
  570. func TestForeignKeyInsertSelect(t *testing.T) {
  571. _, schema, table := newTestDB(t)
  572. e := newExec(schema, table)
  573. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  574. execMust(t, e, "CREATE TABLE staging (id INTEGER PRIMARY KEY, parent_id INTEGER)")
  575. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  576. execMust(t, e, "INSERT INTO parent (id) VALUES (1)")
  577. execMust(t, e, "INSERT INTO staging (id, parent_id) VALUES (10, 1), (11, 2)")
  578. _, err := execSQL(e, "INSERT INTO child (id, parent_id) SELECT id, parent_id FROM staging")
  579. requireForeignKeyViolation(t, err)
  580. if res := execMust(t, e, "SELECT count(*) FROM child"); res.Rows[0][0] != int64(0) {
  581. t.Fatalf("child rows after rejected INSERT ... SELECT = %v, want 0", res.Rows)
  582. }
  583. execMust(t, e, "DELETE FROM staging WHERE id = 11")
  584. execMust(t, e, "INSERT INTO child (id, parent_id) SELECT id, parent_id FROM staging")
  585. if res := execMust(t, e, "SELECT count(*) FROM child"); res.Rows[0][0] != int64(1) {
  586. t.Fatalf("child rows after valid INSERT ... SELECT = %v, want 1", res.Rows)
  587. }
  588. }
  589. // Referential integrity is a final-state property: a parent and its child can be
  590. // inserted in one transaction, and the commit validates both.
  591. func TestForeignKeyTransactionInsertParentAndChild(t *testing.T) {
  592. _, schema, table := newTestDB(t)
  593. e := newExec(schema, table)
  594. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  595. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  596. execMust(t, e, "BEGIN")
  597. execMust(t, e, "INSERT INTO parent (id) VALUES (1)")
  598. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  599. execMust(t, e, "COMMIT")
  600. if res := execMust(t, e, "SELECT count(*) FROM child"); res.Rows[0][0] != int64(1) {
  601. t.Fatalf("child rows = %v, want 1", res.Rows)
  602. }
  603. }
  604. // Rows of one multi-row self-referential INSERT may reference each other: the
  605. // statement is validated against its own final row set.
  606. func TestForeignKeySiblingReferencesInOneInsert(t *testing.T) {
  607. _, schema, table := newTestDB(t)
  608. e := newExec(schema, table)
  609. execMust(t, e, "CREATE TABLE nodes (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES nodes(id))")
  610. execMust(t, e, "INSERT INTO nodes (id, parent_id) VALUES (1, 2), (2, 1)")
  611. if res := execMust(t, e, "SELECT count(*) FROM nodes"); res.Rows[0][0] != int64(2) {
  612. t.Fatalf("nodes = %v, want 2", res.Rows)
  613. }
  614. _, err := execSQL(e, "INSERT INTO nodes (id, parent_id) VALUES (3, 4), (4, 99)")
  615. requireForeignKeyViolation(t, err)
  616. if res := execMust(t, e, "SELECT count(*) FROM nodes"); res.Rows[0][0] != int64(2) {
  617. t.Fatalf("rejected sibling insert left rows behind: %v", res.Rows)
  618. }
  619. }
  620. // INSERT OR REPLACE rebuilds the conflicting row inside one implicit
  621. // transaction, so replacing a referenced parent row with the same key is valid
  622. // while a replacement that moves the key orphans children and fails at commit.
  623. func TestForeignKeyInsertOrReplaceOnReferencedParent(t *testing.T) {
  624. _, schema, table := newTestDB(t)
  625. e := newExec(schema, table)
  626. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY, code TEXT UNIQUE)")
  627. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  628. execMust(t, e, "INSERT INTO parent (id, code) VALUES (1, 'a')")
  629. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  630. // Same key: the child keeps a valid parent, so the replacement commits.
  631. execMust(t, e, "INSERT OR REPLACE INTO parent (id, code) VALUES (1, 'b')")
  632. if res := execMust(t, e, "SELECT count(*) FROM child WHERE parent_id = 1"); res.Rows[0][0] != int64(1) {
  633. t.Fatalf("child rows after in-place replace = %v, want 1", res.Rows)
  634. }
  635. // A unique-code conflict replaces row 1 with row 2, orphaning the child.
  636. _, err := execSQL(e, "INSERT OR REPLACE INTO parent (id, code) VALUES (2, 'b')")
  637. requireForeignKeyViolation(t, err)
  638. if res := execMust(t, e, "SELECT id, code FROM parent"); res.RowCount != 1 || res.Rows[0][0] != int64(1) || res.Rows[0][1] != "b" {
  639. t.Fatalf("parent after rejected replace = %v, want the original row", res.Rows)
  640. }
  641. if res := execMust(t, e, "SELECT count(*) FROM child"); res.Rows[0][0] != int64(1) {
  642. t.Fatalf("child rows after rejected replace = %v, want 1", res.Rows)
  643. }
  644. }
  645. // A UNIQUE parent index is matched as a column set, and the values are matched in
  646. // declaration order: FOREIGN KEY (a, b) REFERENCES parent(y, x) compares a to y
  647. // and b to x.
  648. func TestForeignKeyReorderedUniqueTarget(t *testing.T) {
  649. _, schema, table := newTestDB(t)
  650. e := newExec(schema, table)
  651. execMust(t, e, "CREATE TABLE parent (x INTEGER, y INTEGER, UNIQUE (x, y))")
  652. execMust(t, e, "CREATE TABLE child (a INTEGER, b INTEGER, FOREIGN KEY (a, b) REFERENCES parent (y, x))")
  653. execMust(t, e, "INSERT INTO parent (x, y) VALUES (1, 2)")
  654. execMust(t, e, "INSERT INTO child (a, b) VALUES (2, 1)")
  655. _, err := execSQL(e, "INSERT INTO child (a, b) VALUES (1, 2)")
  656. requireForeignKeyViolation(t, err)
  657. _, err = execSQL(e, "DELETE FROM parent")
  658. requireForeignKeyViolation(t, err)
  659. }
  660. // ALTER TABLE rejects changes that would strand a persisted foreign key. The
  661. // metadata stores table and column names verbatim, so it cannot be rewritten.
  662. func TestForeignKeyAlterTableRejections(t *testing.T) {
  663. _, schema, table := newTestDB(t)
  664. e := newExec(schema, table)
  665. execMust(t, e, "CREATE TABLE u (code TEXT UNIQUE, note TEXT)")
  666. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, code TEXT REFERENCES u(code), memo TEXT)")
  667. rejections := []struct {
  668. name string
  669. sql string
  670. want string
  671. }{
  672. {"drop child column", "ALTER TABLE child DROP COLUMN code", "cannot drop column code"},
  673. {"rename child column", "ALTER TABLE child RENAME COLUMN code TO parent_code", "cannot rename column code"},
  674. {"drop parent column", "ALTER TABLE u DROP COLUMN code", "cannot drop column code"},
  675. {"rename parent column", "ALTER TABLE u RENAME COLUMN code TO code2", "cannot rename column code"},
  676. {"rename referenced table", "ALTER TABLE u RENAME TO u2", "cannot rename table u"},
  677. {"drop referenced table", "DROP TABLE u", "cannot drop table u"},
  678. }
  679. for _, tt := range rejections {
  680. t.Run(tt.name, func(t *testing.T) {
  681. requireUnsupported(t, execErr(t, e, tt.sql), tt.want)
  682. })
  683. }
  684. // Unrelated columns and the child table itself are not constrained.
  685. execMust(t, e, "ALTER TABLE child DROP COLUMN memo")
  686. execMust(t, e, "ALTER TABLE u RENAME COLUMN note TO comment")
  687. execMust(t, e, "ALTER TABLE u ADD COLUMN extra TEXT")
  688. execMust(t, e, "ALTER TABLE child RENAME TO child2")
  689. execMust(t, e, "INSERT INTO u (code, comment) VALUES ('a', 'x')")
  690. execMust(t, e, "INSERT INTO child2 (id, code) VALUES (1, 'a')")
  691. _, err := execSQL(e, "INSERT INTO child2 (id, code) VALUES (2, 'missing')")
  692. requireForeignKeyViolation(t, err)
  693. }
  694. // A self-referential table is both sides of its own constraint, so renaming or
  695. // dropping it is rejected for the same reason.
  696. func TestForeignKeySelfReferenceAlterRejections(t *testing.T) {
  697. _, schema, table := newTestDB(t)
  698. e := newExec(schema, table)
  699. execMust(t, e, "CREATE TABLE nodes (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES nodes(id), memo TEXT)")
  700. requireUnsupported(t, execErr(t, e, "ALTER TABLE nodes DROP COLUMN parent_id"), "cannot drop column parent_id")
  701. requireUnsupported(t, execErr(t, e, "ALTER TABLE nodes RENAME COLUMN parent_id TO parent"), "cannot rename column parent_id")
  702. requireUnsupported(t, execErr(t, e, "ALTER TABLE nodes RENAME TO n2"), "cannot rename table nodes")
  703. requireUnsupported(t, execErr(t, e, "DROP TABLE nodes"), "cannot drop table nodes")
  704. execMust(t, e, "ALTER TABLE nodes DROP COLUMN memo")
  705. execMust(t, e, "INSERT INTO nodes (id, parent_id) VALUES (1, 1)")
  706. _, err := execSQL(e, "INSERT INTO nodes (id, parent_id) VALUES (2, 9)")
  707. requireForeignKeyViolation(t, err)
  708. }
  709. // execErr parses and executes SQL, returning the error (nil on success).
  710. func execErr(t *testing.T, e *Executor, sql string) error {
  711. t.Helper()
  712. _, err := execSQL(e, sql)
  713. return err
  714. }