foreign_keys.go 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. package executor
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/danfragoso/pizzasql-next/pkg/parser"
  6. "github.com/danfragoso/pizzasql-next/pkg/pgerror"
  7. "github.com/danfragoso/pizzasql-next/pkg/storage"
  8. )
  9. // foreignKeyDeclaration is one parsed FOREIGN KEY (or inline REFERENCES) before
  10. // it is resolved against the parent table.
  11. type foreignKeyDeclaration struct {
  12. name string
  13. columns []string
  14. refTable string
  15. refColumns []string
  16. onDelete parser.ReferentialAction
  17. onUpdate parser.ReferentialAction
  18. deferrable bool
  19. }
  20. // createTableForeignKeys resolves the FOREIGN KEY declarations of a CREATE TABLE
  21. // into durable constraints. The parent table must already exist with its
  22. // unique key in place, except for a self-reference to the table being created;
  23. // a forward reference fails here instead of being stored and silently ignored.
  24. func (e *Executor) createTableForeignKeys(stmt *parser.CreateTableStmt, schema *storage.Schema, uniqueIndexes []*storage.Index) ([]storage.ForeignKeyConstraint, error) {
  25. var declarations []foreignKeyDeclaration
  26. for _, colDef := range stmt.Columns {
  27. for _, constraint := range colDef.Constraints {
  28. if constraint.Type != parser.ConstraintForeignKey {
  29. continue
  30. }
  31. declarations = append(declarations, foreignKeyDeclaration{
  32. name: constraint.Name,
  33. columns: []string{colDef.Name},
  34. refTable: constraint.RefTable,
  35. refColumns: constraint.RefColumns,
  36. onDelete: constraint.OnDelete,
  37. onUpdate: constraint.OnUpdate,
  38. deferrable: constraint.Deferrable,
  39. })
  40. }
  41. }
  42. for _, constraint := range stmt.Constraints {
  43. if constraint.Type != parser.ConstraintForeignKey {
  44. continue
  45. }
  46. declarations = append(declarations, foreignKeyDeclaration{
  47. name: constraint.Name,
  48. columns: constraint.Columns,
  49. refTable: constraint.RefTable,
  50. refColumns: constraint.RefColumns,
  51. onDelete: constraint.OnDelete,
  52. onUpdate: constraint.OnUpdate,
  53. deferrable: constraint.Deferrable,
  54. })
  55. }
  56. foreignKeys := make([]storage.ForeignKeyConstraint, 0, len(declarations))
  57. for _, declaration := range declarations {
  58. foreignKey, err := e.resolveForeignKey(stmt.Table.Name, schema, uniqueIndexes, declaration)
  59. if err != nil {
  60. return nil, err
  61. }
  62. foreignKeys = append(foreignKeys, foreignKey)
  63. }
  64. return foreignKeys, nil
  65. }
  66. // resolveForeignKey validates one declaration and returns its durable form.
  67. func (e *Executor) resolveForeignKey(table string, schema *storage.Schema, uniqueIndexes []*storage.Index, declaration foreignKeyDeclaration) (storage.ForeignKeyConstraint, error) {
  68. label := declaration.label()
  69. if declaration.deferrable {
  70. return storage.ForeignKeyConstraint{}, pgerror.Unsupported(
  71. "foreign key %s on table %s is DEFERRABLE; foreign keys are always enforced immediately and deferred constraints are not implemented",
  72. label, table,
  73. )
  74. }
  75. for _, action := range []struct {
  76. clause string
  77. value parser.ReferentialAction
  78. }{{"ON DELETE", declaration.onDelete}, {"ON UPDATE", declaration.onUpdate}} {
  79. switch action.value {
  80. case parser.RefActionCascade, parser.RefActionSetNull, parser.RefActionSetDefault:
  81. return storage.ForeignKeyConstraint{}, pgerror.Unsupported(
  82. "foreign key %s on table %s uses %s %s; cascades are not implemented, use NO ACTION or RESTRICT",
  83. label, table, action.clause, action.value,
  84. )
  85. }
  86. }
  87. childColumns := make([]string, 0, len(declaration.columns))
  88. seen := make(map[string]bool, len(declaration.columns))
  89. for _, name := range declaration.columns {
  90. column, ok := schema.GetColumn(name)
  91. if !ok {
  92. return storage.ForeignKeyConstraint{}, pgerror.Unsupported(
  93. "foreign key %s on table %s names column %q, which does not exist",
  94. label, table, name,
  95. )
  96. }
  97. if seen[strings.ToLower(column.Name)] {
  98. return storage.ForeignKeyConstraint{}, pgerror.Unsupported(
  99. "foreign key %s on table %s repeats column %q",
  100. label, table, column.Name,
  101. )
  102. }
  103. seen[strings.ToLower(column.Name)] = true
  104. childColumns = append(childColumns, column.Name)
  105. }
  106. if len(childColumns) == 0 {
  107. return storage.ForeignKeyConstraint{}, pgerror.Unsupported(
  108. "foreign key %s on table %s declares no columns", label, table,
  109. )
  110. }
  111. selfReference := strings.EqualFold(declaration.refTable, table)
  112. var parent *storage.Schema
  113. var parentIndexes []*storage.Index
  114. if selfReference {
  115. parent = schema
  116. parentIndexes = uniqueIndexes
  117. } else {
  118. resolved, err := e.schema.GetSchema(declaration.refTable)
  119. if err != nil {
  120. return storage.ForeignKeyConstraint{}, pgerror.Unsupported(
  121. "foreign key %s on table %s references table %q, which does not exist; create the parent table before the child table",
  122. label, table, declaration.refTable,
  123. )
  124. }
  125. parent = resolved
  126. indexes, err := e.schema.ListTableIndexes(declaration.refTable)
  127. if err != nil {
  128. return storage.ForeignKeyConstraint{}, err
  129. }
  130. parentIndexes = indexes
  131. }
  132. refColumns := append([]string(nil), declaration.refColumns...)
  133. if len(refColumns) == 0 {
  134. if parent.UsesSyntheticRowID() {
  135. return storage.ForeignKeyConstraint{}, pgerror.Unsupported(
  136. "foreign key %s on table %s references table %s without a column list, but %s has no PRIMARY KEY; declare a PRIMARY KEY or list parent columns covered by a UNIQUE index",
  137. label, table, parent.Name, parent.Name,
  138. )
  139. }
  140. refColumns = parent.PrimaryKeyList()
  141. }
  142. if len(childColumns) != len(refColumns) {
  143. return storage.ForeignKeyConstraint{}, pgerror.Unsupported(
  144. "foreign key %s on table %s has %d child column(s) but references %d parent column(s); both sides of a foreign key need the same number of columns",
  145. label, table, len(childColumns), len(refColumns),
  146. )
  147. }
  148. canonical := make([]string, 0, len(refColumns))
  149. for _, name := range refColumns {
  150. column, ok := parent.GetColumn(name)
  151. if !ok {
  152. return storage.ForeignKeyConstraint{}, pgerror.Unsupported(
  153. "foreign key %s on table %s references column %q, which does not exist in table %s",
  154. label, table, name, parent.Name,
  155. )
  156. }
  157. canonical = append(canonical, column.Name)
  158. }
  159. if !foreignKeyTargetIsUnique(parent, canonical, parentIndexes) {
  160. return storage.ForeignKeyConstraint{}, pgerror.Unsupported(
  161. "foreign key mismatch: table %s references %s(%s), but the referenced columns are neither the parent's PRIMARY KEY nor covered by a UNIQUE index; declare UNIQUE (%s) on %s first",
  162. table, parent.Name, strings.Join(canonical, ", "), strings.Join(canonical, ", "), parent.Name,
  163. )
  164. }
  165. return storage.ForeignKeyConstraint{
  166. Name: declaration.name,
  167. Columns: childColumns,
  168. RefTable: parent.Name,
  169. RefColumns: canonical,
  170. OnDelete: declaration.onDelete.String(),
  171. OnUpdate: declaration.onUpdate.String(),
  172. }, nil
  173. }
  174. // label names the declaration in a DDL error before it has been resolved.
  175. func (d foreignKeyDeclaration) label() string {
  176. if d.name != "" {
  177. return d.name
  178. }
  179. if len(d.columns) == 0 {
  180. return "FOREIGN KEY"
  181. }
  182. return fmt.Sprintf("FOREIGN KEY (%s)", strings.Join(d.columns, ", "))
  183. }
  184. // foreignKeyTargetIsUnique reports whether columns is exactly the parent's
  185. // primary key or exactly the column set of a UNIQUE index on the parent.
  186. func foreignKeyTargetIsUnique(parent *storage.Schema, columns []string, indexes []*storage.Index) bool {
  187. if !parent.UsesSyntheticRowID() && parent.PrimaryKeyTargetMatches(columns) {
  188. return true
  189. }
  190. for _, index := range indexes {
  191. if !index.Unique || len(index.Columns) != len(columns) {
  192. continue
  193. }
  194. plain := true
  195. names := make([]string, len(index.Columns))
  196. for i, column := range index.Columns {
  197. if column.Expression != "" {
  198. plain = false
  199. break
  200. }
  201. names[i] = column.Name
  202. }
  203. if plain && sameColumnSet(names, columns) {
  204. return true
  205. }
  206. }
  207. return false
  208. }
  209. // sameColumnSet reports whether both lists name the same columns, in any order.
  210. func sameColumnSet(a, b []string) bool {
  211. if len(a) != len(b) {
  212. return false
  213. }
  214. for _, name := range a {
  215. found := false
  216. for _, other := range b {
  217. if strings.EqualFold(name, other) {
  218. found = true
  219. break
  220. }
  221. }
  222. if !found {
  223. return false
  224. }
  225. }
  226. return true
  227. }
  228. // rejectForeignKeyColumnChange refuses to drop or rename a column that a
  229. // persisted foreign key depends on, on the child side or as a referenced parent
  230. // column. Constraint metadata stores column names verbatim and cannot be
  231. // rewritten, so accepting the change would either silently stop enforcing the
  232. // constraint or leave the catalog naming a missing column.
  233. func (e *Executor) rejectForeignKeyColumnChange(table, column, action string) error {
  234. labels, err := e.schema.ForeignKeyColumnDependency(table, column)
  235. if err != nil {
  236. return err
  237. }
  238. if len(labels) == 0 {
  239. return nil
  240. }
  241. return pgerror.Unsupported(
  242. "cannot %s column %s: foreign key %s depends on it; drop the constrained table (or recreate it without that constraint) first",
  243. action, column, strings.Join(labels, ", "),
  244. )
  245. }
  246. // rejectForeignKeyTableRename refuses to rename a table another constraint (or
  247. // its own constraint) names as the parent. The referenced table name is
  248. // persisted verbatim, so a rename would leave parent-side enforcement pointing
  249. // at a table that no longer exists.
  250. func (e *Executor) rejectForeignKeyTableRename(table string) error {
  251. labels, err := e.schema.ForeignKeyTableDependency(table)
  252. if err != nil {
  253. return err
  254. }
  255. if len(labels) == 0 {
  256. return nil
  257. }
  258. return pgerror.Unsupported(
  259. "cannot rename table %s: foreign key %s references it; drop the constrained table (or recreate it without that constraint) first",
  260. table, strings.Join(labels, ", "),
  261. )
  262. }
  263. // rejectForeignKeyDependentTableDrop refuses to drop a table that a persisted
  264. // foreign key names as its parent, because the constraint would be left dangling
  265. // on the child table.
  266. func (e *Executor) rejectForeignKeyDependentTableDrop(table string) error {
  267. labels, err := e.schema.ForeignKeyTableDependency(table)
  268. if err != nil {
  269. return err
  270. }
  271. if len(labels) == 0 {
  272. return nil
  273. }
  274. return pgerror.Unsupported(
  275. "cannot drop table %s: foreign key %s references it; drop or recreate the table that declares that constraint first",
  276. table, strings.Join(labels, ", "),
  277. )
  278. }