foreign_key_test.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  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. // A self-referencing row cannot be deleted while it points at itself.
  241. _, err = execSQL(e, "DELETE FROM nodes WHERE id = 1")
  242. requireForeignKeyViolation(t, err)
  243. execMust(t, e, "INSERT INTO nodes (id, parent_id) VALUES (5, 5)")
  244. execMust(t, e, "DELETE FROM nodes WHERE id = 5")
  245. // A composite self-reference resolves against this table's table-level key.
  246. execMust(t, e, `CREATE TABLE tree (
  247. a TEXT NOT NULL,
  248. b TEXT NOT NULL,
  249. pa TEXT,
  250. pb TEXT,
  251. PRIMARY KEY (a, b),
  252. FOREIGN KEY (pa, pb) REFERENCES tree(a, b)
  253. )`)
  254. execMust(t, e, "INSERT INTO tree (a, b, pa, pb) VALUES ('r', '1', 'r', '1')")
  255. execMust(t, e, "INSERT INTO tree (a, b, pa, pb) VALUES ('c', '1', 'r', '1')")
  256. _, err = execSQL(e, "INSERT INTO tree (a, b, pa, pb) VALUES ('x', '1', 'missing', '1')")
  257. requireForeignKeyViolation(t, err)
  258. }
  259. // A transaction is validated at COMMIT against its final state, so a staged
  260. // child insert and a staged parent delete cannot commit into a dangling
  261. // reference in either order.
  262. func TestForeignKeyTransactionCommitEnforcement(t *testing.T) {
  263. _, schema, table := newTestDB(t)
  264. e := newExec(schema, table)
  265. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  266. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  267. execMust(t, e, "INSERT INTO parent (id) VALUES (1)")
  268. t.Run("staged child insert then parent delete", func(t *testing.T) {
  269. execMust(t, e, "BEGIN")
  270. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  271. execMust(t, e, "DELETE FROM parent WHERE id = 1")
  272. _, err := execSQL(e, "COMMIT")
  273. requireForeignKeyViolation(t, err)
  274. if res := execMust(t, e, "SELECT count(*) FROM child"); res.Rows[0][0] != int64(0) {
  275. t.Fatalf("child rows after rejected commit = %v, want 0", res.Rows)
  276. }
  277. if res := execMust(t, e, "SELECT count(*) FROM parent"); res.Rows[0][0] != int64(1) {
  278. t.Fatalf("parent rows after rejected commit = %v, want 1", res.Rows)
  279. }
  280. })
  281. t.Run("parent delete then staged child insert", func(t *testing.T) {
  282. execMust(t, e, "BEGIN")
  283. execMust(t, e, "DELETE FROM parent WHERE id = 1")
  284. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  285. _, err := execSQL(e, "COMMIT")
  286. requireForeignKeyViolation(t, err)
  287. execMust(t, e, "SELECT count(*) FROM parent")
  288. })
  289. t.Run("staged child insert with missing parent", func(t *testing.T) {
  290. execMust(t, e, "BEGIN")
  291. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (11, 77)")
  292. _, err := execSQL(e, "COMMIT")
  293. requireForeignKeyViolation(t, err)
  294. if res := execMust(t, e, "SELECT count(*) FROM child"); res.Rows[0][0] != int64(0) {
  295. t.Fatalf("child rows after rejected commit = %v, want 0", res.Rows)
  296. }
  297. })
  298. }
  299. // Deleting a parent and its children in one transaction succeeds in either
  300. // order because the commit sees the final, consistent state.
  301. func TestForeignKeyTransactionDeleteParentAndChild(t *testing.T) {
  302. for _, childrenFirst := range []bool{false, true} {
  303. name := "parent first"
  304. if childrenFirst {
  305. name = "children first"
  306. }
  307. t.Run(name, func(t *testing.T) {
  308. _, schema, table := newTestDB(t)
  309. e := newExec(schema, table)
  310. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  311. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  312. execMust(t, e, "INSERT INTO parent (id) VALUES (1), (2)")
  313. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1), (11, 1)")
  314. execMust(t, e, "BEGIN")
  315. if childrenFirst {
  316. execMust(t, e, "DELETE FROM child WHERE parent_id = 1")
  317. execMust(t, e, "DELETE FROM parent WHERE id = 1")
  318. } else {
  319. execMust(t, e, "DELETE FROM parent WHERE id = 1")
  320. execMust(t, e, "DELETE FROM child WHERE parent_id = 1")
  321. }
  322. execMust(t, e, "COMMIT")
  323. if res := execMust(t, e, "SELECT count(*) FROM parent"); res.Rows[0][0] != int64(1) {
  324. t.Fatalf("parent rows = %v, want 1", res.Rows)
  325. }
  326. if res := execMust(t, e, "SELECT count(*) FROM child"); res.Rows[0][0] != int64(0) {
  327. t.Fatalf("child rows = %v, want 0", res.Rows)
  328. }
  329. })
  330. }
  331. }
  332. // A parent column is only a valid target when it is the primary key or covered
  333. // by a UNIQUE index, and forward references fail at CREATE time.
  334. func TestForeignKeyDDLRejections(t *testing.T) {
  335. tests := []struct {
  336. name string
  337. setup []string
  338. ddl string
  339. want string
  340. }{
  341. {
  342. name: "parent does not exist",
  343. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))",
  344. want: "does not exist",
  345. },
  346. {
  347. name: "parent columns are not unique",
  348. setup: []string{"CREATE TABLE parent (id INTEGER PRIMARY KEY, code TEXT)"},
  349. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, code TEXT REFERENCES parent(code))",
  350. want: "foreign key mismatch",
  351. },
  352. {
  353. name: "composite parent columns are not a unique set",
  354. setup: []string{"CREATE TABLE parent (a TEXT UNIQUE, b INTEGER)"},
  355. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, a TEXT, b INTEGER, FOREIGN KEY (a, b) REFERENCES parent(a, b))",
  356. want: "foreign key mismatch",
  357. },
  358. {
  359. name: "unknown child column",
  360. setup: []string{"CREATE TABLE parent (id INTEGER PRIMARY KEY)"},
  361. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, FOREIGN KEY (missing) REFERENCES parent(id))",
  362. want: `column "missing"`,
  363. },
  364. {
  365. name: "unknown parent column",
  366. setup: []string{"CREATE TABLE parent (id INTEGER PRIMARY KEY)"},
  367. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(missing))",
  368. want: "does not exist in table parent",
  369. },
  370. {
  371. name: "column count mismatch",
  372. setup: []string{"CREATE TABLE parent (a TEXT, b TEXT, PRIMARY KEY (a, b))"},
  373. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, a TEXT, FOREIGN KEY (a) REFERENCES parent(a, b))",
  374. want: "same number of columns",
  375. },
  376. {
  377. name: "on delete cascade",
  378. setup: []string{"CREATE TABLE parent (id INTEGER PRIMARY KEY)"},
  379. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id) ON DELETE CASCADE)",
  380. want: "cascades are not implemented",
  381. },
  382. {
  383. name: "on update cascade",
  384. setup: []string{"CREATE TABLE parent (id INTEGER PRIMARY KEY)"},
  385. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER, FOREIGN KEY (parent_id) REFERENCES parent(id) ON UPDATE CASCADE)",
  386. want: "cascades are not implemented",
  387. },
  388. {
  389. name: "on delete set null",
  390. setup: []string{"CREATE TABLE parent (id INTEGER PRIMARY KEY)"},
  391. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER, FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE SET NULL)",
  392. want: "cascades are not implemented",
  393. },
  394. {
  395. name: "on delete set default",
  396. setup: []string{"CREATE TABLE parent (id INTEGER PRIMARY KEY)"},
  397. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER, FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE SET DEFAULT)",
  398. want: "cascades are not implemented",
  399. },
  400. {
  401. name: "deferrable",
  402. setup: []string{"CREATE TABLE parent (id INTEGER PRIMARY KEY)"},
  403. ddl: "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id) DEFERRABLE INITIALLY DEFERRED)",
  404. want: "DEFERRABLE",
  405. },
  406. }
  407. for _, tt := range tests {
  408. t.Run(tt.name, func(t *testing.T) {
  409. _, schema, table := newTestDB(t)
  410. e := newExec(schema, table)
  411. for _, sql := range tt.setup {
  412. execMust(t, e, sql)
  413. }
  414. _, err := execSQL(e, tt.ddl)
  415. requireUnsupported(t, err, tt.want)
  416. if schema.TableExists("child") {
  417. t.Fatal("rejected CREATE TABLE left a schema behind")
  418. }
  419. })
  420. }
  421. // ALTER TABLE ADD COLUMN still cannot introduce a foreign key.
  422. _, schema, table := newTestDB(t)
  423. e := newExec(schema, table)
  424. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  425. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY)")
  426. _, err := execSQL(e, "ALTER TABLE t ADD COLUMN parent_id INTEGER REFERENCES parent(id)")
  427. requireUnsupported(t, err, "CREATE TABLE")
  428. }
  429. // The persisted constraint keeps being enforced after the schema is re-read
  430. // from storage.
  431. func TestForeignKeyDurableReload(t *testing.T) {
  432. pool, schema, table := newTestDB(t)
  433. e := newExec(schema, table)
  434. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  435. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER, CONSTRAINT child_parent FOREIGN KEY (parent_id) REFERENCES parent(id))")
  436. execMust(t, e, "INSERT INTO parent (id) VALUES (1)")
  437. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  438. reloaded := storage.NewSchemaManager(pool, "testdb")
  439. reopened := newExec(reloaded, storage.NewTableManager(pool, reloaded, "testdb"))
  440. stored, err := reloaded.GetSchema("child")
  441. if err != nil {
  442. t.Fatalf("reload schema: %v", err)
  443. }
  444. if len(stored.ForeignKeys) != 1 || stored.ForeignKeys[0].Name != "child_parent" {
  445. t.Fatalf("reloaded foreign keys = %+v, want child_parent", stored.ForeignKeys)
  446. }
  447. _, err = execSQL(reopened, "INSERT INTO child (id, parent_id) VALUES (11, 2)")
  448. requireForeignKeyViolation(t, err)
  449. _, err = execSQL(reopened, "DELETE FROM parent WHERE id = 1")
  450. requireForeignKeyViolation(t, err)
  451. execMust(t, reopened, "INSERT INTO child (id, parent_id) VALUES (12, 1)")
  452. }
  453. // The emulated SQLite catalog reports the foreign key in the table SQL.
  454. func TestForeignKeyInSQLiteCatalog(t *testing.T) {
  455. _, schema, table := newTestDB(t)
  456. e := newExec(schema, table)
  457. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  458. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER, CONSTRAINT child_parent FOREIGN KEY (parent_id) REFERENCES parent(id))")
  459. res := execMust(t, e, "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'child'")
  460. if res.RowCount != 1 {
  461. t.Fatalf("sqlite_master rows = %d, want 1", res.RowCount)
  462. }
  463. sql, _ := res.Rows[0][0].(string)
  464. for _, want := range []string{"CONSTRAINT `child_parent` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`)"} {
  465. if !strings.Contains(sql, want) {
  466. t.Fatalf("catalog SQL %q does not contain %q", sql, want)
  467. }
  468. }
  469. res = execMust(t, e, "SELECT sql FROM sqlite_schema WHERE name = 'child'")
  470. if res.RowCount != 1 || !strings.Contains(res.Rows[0][0].(string), "FOREIGN KEY") {
  471. t.Fatalf("sqlite_schema SQL = %v, want the foreign key", res.Rows)
  472. }
  473. }
  474. // Foreign keys coexist with the composite primary key, CHECK, and default
  475. // support already in the engine.
  476. func TestForeignKeyRegressionCompositeKeyCheckDefaults(t *testing.T) {
  477. _, schema, table := newTestDB(t)
  478. e := newExec(schema, table)
  479. execMust(t, e, `CREATE TABLE parent (
  480. a TEXT,
  481. b INTEGER,
  482. PRIMARY KEY (a, b)
  483. )`)
  484. execMust(t, e, `CREATE TABLE child (
  485. id INTEGER PRIMARY KEY,
  486. pa TEXT NOT NULL,
  487. pb INTEGER NOT NULL,
  488. state TEXT NOT NULL DEFAULT 'new' CHECK (state IN ('new', 'done')),
  489. FOREIGN KEY (pa, pb) REFERENCES parent(a, b)
  490. )`)
  491. execMust(t, e, "INSERT INTO parent (a, b) VALUES ('x', 1)")
  492. execMust(t, e, "INSERT INTO child (id, pa, pb) VALUES (1, 'x', 1)")
  493. if res := execMust(t, e, "SELECT state FROM child WHERE id = 1"); res.Rows[0][0] != "new" {
  494. t.Fatalf("default state = %v, want new", res.Rows[0][0])
  495. }
  496. execMust(t, e, "UPDATE child SET state = 'done' WHERE id = 1")
  497. _, err := execSQL(e, "INSERT INTO child (id, pa, pb) VALUES (2, 'x', 2)")
  498. requireForeignKeyViolation(t, err)
  499. _, err = execSQL(e, "INSERT INTO child (id, pa, pb, state) VALUES (2, 'x', 1, 'bogus')")
  500. requireCheckViolation(t, err, "")
  501. _, err = execSQL(e, "INSERT INTO child (id, pa, pb) VALUES (1, 'x', 1)")
  502. if err == nil {
  503. t.Fatal("duplicate composite parent key or primary key was accepted")
  504. }
  505. }
  506. // A parent column covered by a UNIQUE index (not the primary key) is a valid
  507. // foreign-key target, whether the uniqueness comes from a constraint or an
  508. // explicit CREATE UNIQUE INDEX.
  509. func TestForeignKeyReferencesUniqueIndex(t *testing.T) {
  510. _, schema, table := newTestDB(t)
  511. e := newExec(schema, table)
  512. execMust(t, e, "CREATE TABLE by_constraint (id INTEGER PRIMARY KEY, code TEXT UNIQUE)")
  513. execMust(t, e, "CREATE TABLE by_index (id INTEGER PRIMARY KEY, code TEXT)")
  514. execMust(t, e, "CREATE UNIQUE INDEX idx_by_index_code ON by_index (code)")
  515. execMust(t, e, "CREATE TABLE child_constraint (id INTEGER PRIMARY KEY, code TEXT REFERENCES by_constraint(code))")
  516. execMust(t, e, "CREATE TABLE child_index (id INTEGER PRIMARY KEY, code TEXT REFERENCES by_index(code))")
  517. execMust(t, e, "INSERT INTO by_constraint (id, code) VALUES (1, 'a')")
  518. execMust(t, e, "INSERT INTO by_index (id, code) VALUES (1, 'b')")
  519. execMust(t, e, "INSERT INTO child_constraint (id, code) VALUES (10, 'a')")
  520. execMust(t, e, "INSERT INTO child_index (id, code) VALUES (10, 'b')")
  521. _, err := execSQL(e, "INSERT INTO child_constraint (id, code) VALUES (11, 'missing')")
  522. requireForeignKeyViolation(t, err)
  523. _, err = execSQL(e, "INSERT INTO child_index (id, code) VALUES (11, 'missing')")
  524. requireForeignKeyViolation(t, err)
  525. _, err = execSQL(e, "DELETE FROM by_constraint WHERE code = 'a'")
  526. requireForeignKeyViolation(t, err)
  527. _, err = execSQL(e, "DELETE FROM by_index WHERE code = 'b'")
  528. requireForeignKeyViolation(t, err)
  529. }
  530. // A scan-based parent UPDATE of a referenced column is validated at the
  531. // statement boundary just like a point update.
  532. func TestForeignKeyParentScanUpdate(t *testing.T) {
  533. _, schema, table := newTestDB(t)
  534. e := newExec(schema, table)
  535. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY, v INTEGER)")
  536. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  537. execMust(t, e, "INSERT INTO parent (id, v) VALUES (1, 1), (2, 1)")
  538. execMust(t, e, "INSERT INTO child (id, parent_id) VALUES (10, 1)")
  539. _, err := execSQL(e, "UPDATE parent SET id = id + 10 WHERE v = 1")
  540. requireForeignKeyViolation(t, err)
  541. if res := execMust(t, e, "SELECT count(*) FROM parent WHERE id > 10"); res.Rows[0][0] != int64(0) {
  542. t.Fatalf("rejected scan update moved rows: %v", res.Rows)
  543. }
  544. execMust(t, e, "DELETE FROM child WHERE id = 10")
  545. execMust(t, e, "UPDATE parent SET id = id + 10 WHERE v = 1")
  546. if res := execMust(t, e, "SELECT count(*) FROM parent WHERE id > 10"); res.Rows[0][0] != int64(2) {
  547. t.Fatalf("valid scan update rows = %v, want 2", res.Rows)
  548. }
  549. }
  550. // INSERT ... SELECT goes through the same enforcement as a VALUES insert.
  551. func TestForeignKeyInsertSelect(t *testing.T) {
  552. _, schema, table := newTestDB(t)
  553. e := newExec(schema, table)
  554. execMust(t, e, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  555. execMust(t, e, "CREATE TABLE staging (id INTEGER PRIMARY KEY, parent_id INTEGER)")
  556. execMust(t, e, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  557. execMust(t, e, "INSERT INTO parent (id) VALUES (1)")
  558. execMust(t, e, "INSERT INTO staging (id, parent_id) VALUES (10, 1), (11, 2)")
  559. _, err := execSQL(e, "INSERT INTO child (id, parent_id) SELECT id, parent_id FROM staging")
  560. requireForeignKeyViolation(t, err)
  561. if res := execMust(t, e, "SELECT count(*) FROM child"); res.Rows[0][0] != int64(0) {
  562. t.Fatalf("child rows after rejected INSERT ... SELECT = %v, want 0", res.Rows)
  563. }
  564. execMust(t, e, "DELETE FROM staging WHERE id = 11")
  565. execMust(t, e, "INSERT INTO child (id, parent_id) SELECT id, parent_id FROM staging")
  566. if res := execMust(t, e, "SELECT count(*) FROM child"); res.Rows[0][0] != int64(1) {
  567. t.Fatalf("child rows after valid INSERT ... SELECT = %v, want 1", res.Rows)
  568. }
  569. }