foreign_key_cascade.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. package storage
  2. import (
  3. "fmt"
  4. "strings"
  5. "time"
  6. )
  7. // ReferentialAction is one persisted FOREIGN KEY ON DELETE/ON UPDATE action.
  8. // The catalog stores the SQL spelling so sqlite_master can reproduce the
  9. // declaration; an omitted clause and NO ACTION behave identically.
  10. type ReferentialAction string
  11. const (
  12. ReferentialActionNoAction ReferentialAction = "NO ACTION"
  13. ReferentialActionRestrict ReferentialAction = "RESTRICT"
  14. ReferentialActionCascade ReferentialAction = "CASCADE"
  15. ReferentialActionSetNull ReferentialAction = "SET NULL"
  16. ReferentialActionSetDefault ReferentialAction = "SET DEFAULT"
  17. )
  18. // DeleteAction returns the action applied when the parent row is deleted.
  19. func (fk ForeignKeyConstraint) DeleteAction() ReferentialAction {
  20. return parseReferentialAction(fk.OnDelete)
  21. }
  22. // UpdateAction returns the action applied when a referenced parent key changes.
  23. func (fk ForeignKeyConstraint) UpdateAction() ReferentialAction {
  24. return parseReferentialAction(fk.OnUpdate)
  25. }
  26. // parseReferentialAction maps the persisted spelling to its action. Anything
  27. // unrecognized is NO ACTION, the default the catalog uses when the clause was
  28. // omitted, so a hand-edited catalog cannot silently disable enforcement.
  29. func parseReferentialAction(text string) ReferentialAction {
  30. switch strings.ToUpper(strings.TrimSpace(text)) {
  31. case "CASCADE":
  32. return ReferentialActionCascade
  33. case "SET NULL":
  34. return ReferentialActionSetNull
  35. case "SET DEFAULT":
  36. return ReferentialActionSetDefault
  37. case "RESTRICT":
  38. return ReferentialActionRestrict
  39. default:
  40. return ReferentialActionNoAction
  41. }
  42. }
  43. // rewritesReferencingRows reports whether the action changes or removes the
  44. // child rows instead of rejecting the parent change.
  45. func (a ReferentialAction) rewritesReferencingRows() bool {
  46. switch a {
  47. case ReferentialActionCascade, ReferentialActionSetNull, ReferentialActionSetDefault:
  48. return true
  49. default:
  50. return false
  51. }
  52. }
  53. // HasCascadingReference reports whether table is the referenced parent of a
  54. // constraint whose DELETE action (forDelete) or UPDATE action rewrites the
  55. // referencing rows. Callers use it to choose the transactional write path, so a
  56. // statement whose only actions are NO ACTION/RESTRICT keeps the optimized
  57. // point-write path.
  58. func (m *SchemaManager) HasCascadingReference(table string, forDelete bool) (bool, error) {
  59. refs, err := m.referencingForeignKeys(table)
  60. if err != nil {
  61. return false, err
  62. }
  63. for _, ref := range refs {
  64. action := ref.FK.UpdateAction()
  65. if forDelete {
  66. action = ref.FK.DeleteAction()
  67. }
  68. if action.rewritesReferencingRows() {
  69. return true, nil
  70. }
  71. }
  72. return false, nil
  73. }
  74. // beginCascadingPointWrite decides whether an autocommit point write must be
  75. // staged so its cascades publish with the parent change, and starts that
  76. // implicit transaction when it is needed.
  77. //
  78. // A table outside the foreign-key graph keeps the point-write fast path and
  79. // never touches the gate. For a participating table the database-wide
  80. // foreign-key write gate is taken before the cascade classification and held
  81. // across Begin and the caller's staging. CREATE TABLE ... REFERENCES takes the
  82. // same unconditional gate, so a constraint cannot be published between deciding
  83. // that the write is not cascading and opening the transaction that would have
  84. // applied its actions. The returned release function must be called before
  85. // Commit, which acquires the same non-reentrant gate.
  86. //
  87. // A table gains its first constraint only together with a new child table,
  88. // which is created empty under the gate and whose rows take the gate on insert.
  89. // If the participant probe above goes stale, the non-cascading point path
  90. // re-reads the catalog under the gate and rejects the write with 23503 instead
  91. // of ignoring the new constraint.
  92. //
  93. // The gate is held while Begin and the caller's staging take the session lock.
  94. // That order cannot invert with Commit: a Session serves one connection and one
  95. // statement at a time, so Commit on this Session runs after the staging path
  96. // releases the gate, while other sessions hold their own lock and contend only
  97. // on the shared gate.
  98. func (s *Session) beginCascadingPointWrite(table string, forDelete bool) (bool, func(), error) {
  99. participates, err := s.schema.ForeignKeyParticipants(table)
  100. if err != nil {
  101. return false, nil, err
  102. }
  103. if !participates {
  104. return false, func() {}, nil
  105. }
  106. unlockFK := s.table.LockForeignKeyWriteGate()
  107. cascades, err := s.schema.HasCascadingReference(table, forDelete)
  108. if err != nil {
  109. unlockFK()
  110. return false, nil, err
  111. }
  112. if !cascades {
  113. unlockFK()
  114. return false, func() {}, nil
  115. }
  116. if err := s.Begin(); err != nil {
  117. unlockFK()
  118. return false, nil, err
  119. }
  120. return true, unlockFK, nil
  121. }
  122. // foreignKeyRawValues returns the unformatted values a row holds for the given
  123. // columns, and reports false when a column is missing. A NULL component is
  124. // returned as nil: on a parent-side update the cascading action copies the new
  125. // value, including a NULL that clears the reference.
  126. func foreignKeyRawValues(row Row, columns []string) ([]interface{}, bool) {
  127. values := make([]interface{}, len(columns))
  128. for i, name := range columns {
  129. value, ok := rowColumnValue(row, name)
  130. if !ok {
  131. return nil, false
  132. }
  133. values[i] = value
  134. }
  135. return values, true
  136. }
  137. // foreignKeyActionKey formats a row's values for the cascade event key, keeping
  138. // a NULL component distinguishable so a key that becomes NULL still changes the
  139. // event identity.
  140. func foreignKeyActionKey(row Row, columns []string) ([]string, bool) {
  141. values := make([]string, len(columns))
  142. for i, name := range columns {
  143. value, ok := rowColumnValue(row, name)
  144. if !ok {
  145. return nil, false
  146. }
  147. if value == nil {
  148. values[i] = "\x00NULL"
  149. continue
  150. }
  151. values[i] = formatIndexValue(value)
  152. }
  153. return values, true
  154. }
  155. // cascadeExpansion tracks the referential actions one statement already applied.
  156. // It is the termination guard for self-references and constraint cycles: a row
  157. // delete is applied at most once, and one (row, constraint, old key, new key)
  158. // child update at most once. Because the applied event includes the key values
  159. // involved, a cycle that revisits a row with a different key still terminates:
  160. // the set of values a cascade can copy is bounded by the rows it starts from.
  161. type cascadeExpansion struct {
  162. applied map[string]bool
  163. // now is the statement instant used to evaluate a SET DEFAULT expression
  164. // default, so every cascaded row of one statement sees the same value.
  165. now time.Time
  166. }
  167. func newCascadeExpansion(schema *SchemaManager) *cascadeExpansion {
  168. return &cascadeExpansion{applied: make(map[string]bool), now: schema.defaultNow()}
  169. }
  170. func (e *cascadeExpansion) markDelete(table, key string) bool {
  171. id := "D\x00" + strings.ToLower(table) + "\x00" + key
  172. if e.applied[id] {
  173. return false
  174. }
  175. e.applied[id] = true
  176. return true
  177. }
  178. func (e *cascadeExpansion) markUpdate(table, key string, fk ForeignKeyConstraint, oldKey, newKey []string) bool {
  179. id := "U\x00" + strings.ToLower(table) + "\x00" + key + "\x00" + strings.ToLower(fk.RefTable) + "\x00" + joinForeignKeyValues(fk.Columns) + "\x00" + joinForeignKeyValues(oldKey) + "\x00" + joinForeignKeyValues(newKey)
  180. if e.applied[id] {
  181. return false
  182. }
  183. e.applied[id] = true
  184. return true
  185. }
  186. // referencingRows returns the effective child rows (durable rows with the
  187. // transaction overlay applied) that reference key through ref. The caller holds
  188. // the session lock, and selectLocked registers the read so a concurrent write on
  189. // the child side invalidates the commit instead of interleaving.
  190. func (s *Session) referencingRows(ref referencingForeignKey, key []string) ([]Row, error) {
  191. return s.selectLocked(ref.Table, func(row Row) bool {
  192. return foreignKeyMatches(row, ref.FK.Columns, key)
  193. })
  194. }
  195. // stageCascadeDelete stages a row delete plus the ON DELETE actions of every
  196. // constraint that names its table as the parent. The row is staged before the
  197. // child scan, so a self-referential row is not visited as its own child.
  198. func (s *Session) stageCascadeDelete(table, key string, row Row, exp *cascadeExpansion) error {
  199. if !exp.markDelete(table, key) {
  200. return nil
  201. }
  202. if entry, ok := s.overlay[strings.ToLower(table)][key]; ok && entry.absent {
  203. return nil
  204. }
  205. s.stageDelete(table, key, row)
  206. return s.cascadeAfterDelete(table, row, exp)
  207. }
  208. // cascadeAfterDelete applies the ON DELETE actions triggered by removing row
  209. // from table.
  210. func (s *Session) cascadeAfterDelete(table string, row Row, exp *cascadeExpansion) error {
  211. refs, err := s.referencingForeignKeysLocked(table)
  212. if err != nil {
  213. return err
  214. }
  215. for _, ref := range refs {
  216. action := ref.FK.DeleteAction()
  217. if !action.rewritesReferencingRows() {
  218. continue
  219. }
  220. key, ok := foreignKeyValues(row, ref.FK.RefColumns)
  221. if !ok {
  222. continue
  223. }
  224. children, err := s.referencingRows(ref, key)
  225. if err != nil {
  226. return err
  227. }
  228. for _, child := range children {
  229. if err := s.applyDeleteAction(ref, child, action, exp); err != nil {
  230. return err
  231. }
  232. }
  233. }
  234. return nil
  235. }
  236. // applyDeleteAction performs one row's ON DELETE action. CASCADE removes the
  237. // child and recurses through its own constraints; SET NULL/SET DEFAULT rewrite
  238. // the child's foreign-key columns and then propagate as an update, because those
  239. // columns can themselves be a key another table references.
  240. func (s *Session) applyDeleteAction(ref referencingForeignKey, child Row, action ReferentialAction, exp *cascadeExpansion) error {
  241. schema, err := s.getSchemaLocked(ref.Table)
  242. if err != nil {
  243. return err
  244. }
  245. pk, err := schema.PrimaryKeyForRow(child)
  246. if err != nil {
  247. return err
  248. }
  249. key := s.table.dataKey(ref.Table, pk)
  250. if action == ReferentialActionCascade {
  251. return s.stageCascadeDelete(ref.Table, key, child, exp)
  252. }
  253. updated, err := s.applySetAction(schema, ref.FK, child, action, exp)
  254. if err != nil {
  255. return err
  256. }
  257. return s.stageCascadeUpdate(ref.Table, child, updated, exp)
  258. }
  259. // applySetAction returns the child row with its foreign-key columns replaced by
  260. // NULL or by the column defaults. A column without a default receives NULL, the
  261. // SQL default, which then fails NOT NULL if the column is required.
  262. func (s *Session) applySetAction(schema *Schema, fk ForeignKeyConstraint, child Row, action ReferentialAction, exp *cascadeExpansion) (Row, error) {
  263. updated := cloneRow(child)
  264. for _, name := range fk.Columns {
  265. if action == ReferentialActionSetNull {
  266. updated[name] = nil
  267. continue
  268. }
  269. column, ok := schema.GetColumn(name)
  270. if !ok {
  271. return nil, fmt.Errorf("foreign key %s: column %s is missing from table %s", fk.Label(), name, schema.Name)
  272. }
  273. value, hasDefault, err := s.schema.columnDefaultValue(*column, exp.now)
  274. if err != nil {
  275. return nil, err
  276. }
  277. if !hasDefault {
  278. value = nil
  279. }
  280. updated[name] = value
  281. }
  282. return updated, nil
  283. }
  284. // cascadeAfterUpdate applies the ON UPDATE actions triggered by changing a
  285. // parent row whose referenced key moved from oldRow to newRow.
  286. func (s *Session) cascadeAfterUpdate(table string, oldRow, newRow Row, exp *cascadeExpansion) error {
  287. refs, err := s.referencingForeignKeysLocked(table)
  288. if err != nil {
  289. return err
  290. }
  291. for _, ref := range refs {
  292. action := ref.FK.UpdateAction()
  293. if !action.rewritesReferencingRows() {
  294. continue
  295. }
  296. oldKey, ok := foreignKeyValues(oldRow, ref.FK.RefColumns)
  297. if !ok {
  298. continue
  299. }
  300. newKey, newOK := foreignKeyActionKey(newRow, ref.FK.RefColumns)
  301. if newOK && foreignKeyValuesEqual(oldKey, newKey) {
  302. continue
  303. }
  304. parentValues, ok := foreignKeyRawValues(newRow, ref.FK.RefColumns)
  305. if !ok {
  306. continue
  307. }
  308. children, err := s.referencingRows(ref, oldKey)
  309. if err != nil {
  310. return err
  311. }
  312. childSchema, err := s.getSchemaLocked(ref.Table)
  313. if err != nil {
  314. return err
  315. }
  316. for _, child := range children {
  317. childPK, err := childSchema.PrimaryKeyForRow(child)
  318. if err != nil {
  319. return err
  320. }
  321. childKey := s.table.dataKey(ref.Table, childPK)
  322. if !exp.markUpdate(ref.Table, childKey, ref.FK, oldKey, newKey) {
  323. continue
  324. }
  325. var updated Row
  326. switch action {
  327. case ReferentialActionCascade:
  328. updated = cloneRow(child)
  329. for i, column := range ref.FK.Columns {
  330. updated[column] = parentValues[i]
  331. }
  332. case ReferentialActionSetNull, ReferentialActionSetDefault:
  333. updated, err = s.applySetAction(childSchema, ref.FK, child, action, exp)
  334. if err != nil {
  335. return err
  336. }
  337. }
  338. if err := s.stageCascadeUpdate(ref.Table, child, updated, exp); err != nil {
  339. return err
  340. }
  341. }
  342. }
  343. return nil
  344. }
  345. // stageCascadeUpdate stages one cascaded row rewrite. The row keeps its key
  346. // unless a cascaded foreign-key column is part of it, in which case the staged
  347. // image moves and a collision with another row fails like any primary-key move.
  348. // Required columns and CHECK predicates are validated immediately so a SET NULL
  349. // on a NOT NULL column fails at the statement with 23502 instead of surfacing at
  350. // commit; FK and UNIQUE validity are validated against the transaction's final
  351. // state at commit.
  352. func (s *Session) stageCascadeUpdate(table string, oldRow, newRow Row, exp *cascadeExpansion) error {
  353. schema, err := s.getSchemaLocked(table)
  354. if err != nil {
  355. return err
  356. }
  357. if err := s.schema.validateRowConstraints(schema, newRow); err != nil {
  358. return err
  359. }
  360. oldPK, err := schema.PrimaryKeyForRow(oldRow)
  361. if err != nil {
  362. return err
  363. }
  364. newPK, err := schema.PrimaryKeyForRow(newRow)
  365. if err != nil {
  366. return err
  367. }
  368. oldKey := s.table.dataKey(table, oldPK)
  369. newKey := s.table.dataKey(table, newPK)
  370. if oldKey != newKey {
  371. if _, gerr := s.getByPKLocked(table, newPK); gerr == nil {
  372. return &PrimaryKeyViolationError{Table: table, Key: newPK}
  373. } else if gerr != ErrKeyNotFound {
  374. return gerr
  375. }
  376. s.stageDelete(table, oldKey, oldRow)
  377. }
  378. s.stagePut(table, newKey, newRow)
  379. return s.cascadeAfterUpdate(table, oldRow, newRow, exp)
  380. }
  381. // rollbackToLocked discards mutations after a savepoint position. The caller
  382. // holds the session lock.
  383. func (s *Session) rollbackToLocked(pos int) {
  384. for i := len(s.log) - 1; i >= pos && i >= 0; i-- {
  385. m := s.log[i]
  386. if m.prev == nil {
  387. delete(s.overlay[m.table], m.key)
  388. if len(s.overlay[m.table]) == 0 {
  389. delete(s.overlay, m.table)
  390. }
  391. } else {
  392. if s.overlay[m.table] == nil {
  393. s.overlay[m.table] = make(map[string]*overlayEntry)
  394. }
  395. s.overlay[m.table][m.key] = m.prev
  396. }
  397. }
  398. if pos < len(s.log) {
  399. s.log = s.log[:pos]
  400. }
  401. }