foreign-key-builder.vala 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. using Invercargill.DataStructures;
  2. using InvercargillSql.Orm;
  3. namespace InvercargillSql.Migrations {
  4. /**
  5. * Builder for configuring foreign key constraints on a column.
  6. *
  7. * This builder extends MigrationColumnBuilder to allow method chaining between
  8. * FK configuration and column configuration. FK-specific methods return this
  9. * ForeignKeyBuilder, while column methods delegate to the parent and return
  10. * MigrationColumnBuilder (ending the FK configuration chain).
  11. *
  12. * Example usage:
  13. * {{{
  14. * t.column<int64?>("user_id")
  15. * .not_null()
  16. * .references("users", "id")
  17. * .name("fk_orders_users")
  18. * .on_delete_cascade()
  19. * .indexed(); // Returns to column builder
  20. * }}}
  21. */
  22. public class ForeignKeyBuilder : MigrationColumnBuilder {
  23. private MigrationColumnBuilder _inner;
  24. private string _referenced_table;
  25. private string _referenced_column;
  26. private string? _constraint_name;
  27. private ReferentialAction _on_delete = ReferentialAction.NO_ACTION;
  28. private ReferentialAction _on_update = ReferentialAction.NO_ACTION;
  29. /**
  30. * Creates a new ForeignKeyBuilder wrapping an existing MigrationColumnBuilder.
  31. *
  32. * @param inner the parent MigrationColumnBuilder to wrap and delegate to
  33. * @param ref_table the referenced table name
  34. * @param ref_column the referenced column name in the foreign table
  35. */
  36. internal ForeignKeyBuilder(MigrationColumnBuilder inner, string ref_table, string ref_column) {
  37. base.for_subclass();
  38. _inner = inner;
  39. _referenced_table = ref_table;
  40. _referenced_column = ref_column;
  41. }
  42. /**
  43. * Sets an explicit name for the foreign key constraint.
  44. *
  45. * If not specified, the constraint name will be auto-generated as
  46. * `fk_{table}_{column}` when the constraint is built.
  47. *
  48. * @param constraint_name the explicit constraint name
  49. * @return this builder for method chaining
  50. */
  51. public ForeignKeyBuilder name(string constraint_name) {
  52. _constraint_name = constraint_name;
  53. return this;
  54. }
  55. // ========== ON DELETE actions ==========
  56. /**
  57. * Sets ON DELETE CASCADE for this foreign key.
  58. *
  59. * When a referenced row is deleted, all referencing rows will be deleted automatically.
  60. * @return this builder for method chaining
  61. */
  62. public ForeignKeyBuilder on_delete_cascade() {
  63. _on_delete = ReferentialAction.CASCADE;
  64. return this;
  65. }
  66. /**
  67. * Sets ON DELETE SET NULL for this foreign key.
  68. *
  69. * When a referenced row is deleted, the referencing column will be set to NULL.
  70. * @return this builder for method chaining
  71. */
  72. public ForeignKeyBuilder on_delete_set_null() {
  73. _on_delete = ReferentialAction.SET_NULL;
  74. return this;
  75. }
  76. /**
  77. * Sets ON DELETE SET DEFAULT for this foreign key.
  78. *
  79. * When a referenced row is deleted, the referencing column will be set to its default value.
  80. * @return this builder for method chaining
  81. */
  82. public ForeignKeyBuilder on_delete_set_default() {
  83. _on_delete = ReferentialAction.SET_DEFAULT;
  84. return this;
  85. }
  86. /**
  87. * Sets ON DELETE NO ACTION for this foreign key.
  88. *
  89. * This is the SQL standard default. The database will prevent the delete if
  90. * referencing rows exist, but the check may be deferred until transaction end.
  91. * @return this builder for method chaining
  92. */
  93. public ForeignKeyBuilder on_delete_no_action() {
  94. _on_delete = ReferentialAction.NO_ACTION;
  95. return this;
  96. }
  97. /**
  98. * Sets ON DELETE RESTRICT for this foreign key.
  99. *
  100. * Similar to NO_ACTION, but the check is immediate and cannot be deferred.
  101. * @return this builder for method chaining
  102. */
  103. public ForeignKeyBuilder on_delete_restrict() {
  104. _on_delete = ReferentialAction.RESTRICT;
  105. return this;
  106. }
  107. // ========== ON UPDATE actions ==========
  108. /**
  109. * Sets ON UPDATE CASCADE for this foreign key.
  110. *
  111. * When a referenced row's key is updated, the referencing column will be updated automatically.
  112. * @return this builder for method chaining
  113. */
  114. public ForeignKeyBuilder on_update_cascade() {
  115. _on_update = ReferentialAction.CASCADE;
  116. return this;
  117. }
  118. /**
  119. * Sets ON UPDATE SET NULL for this foreign key.
  120. *
  121. * When a referenced row's key is updated, the referencing column will be set to NULL.
  122. * @return this builder for method chaining
  123. */
  124. public ForeignKeyBuilder on_update_set_null() {
  125. _on_update = ReferentialAction.SET_NULL;
  126. return this;
  127. }
  128. /**
  129. * Sets ON UPDATE SET DEFAULT for this foreign key.
  130. *
  131. * When a referenced row's key is updated, the referencing column will be set to its default value.
  132. * @return this builder for method chaining
  133. */
  134. public ForeignKeyBuilder on_update_set_default() {
  135. _on_update = ReferentialAction.SET_DEFAULT;
  136. return this;
  137. }
  138. /**
  139. * Sets ON UPDATE NO ACTION for this foreign key.
  140. *
  141. * This is the SQL standard default. The database will prevent the update if
  142. * referencing rows exist, but the check may be deferred until transaction end.
  143. * @return this builder for method chaining
  144. */
  145. public ForeignKeyBuilder on_update_no_action() {
  146. _on_update = ReferentialAction.NO_ACTION;
  147. return this;
  148. }
  149. /**
  150. * Sets ON UPDATE RESTRICT for this foreign key.
  151. *
  152. * Similar to NO_ACTION, but the check is immediate and cannot be deferred.
  153. * @return this builder for method chaining
  154. */
  155. public ForeignKeyBuilder on_update_restrict() {
  156. _on_update = ReferentialAction.RESTRICT;
  157. return this;
  158. }
  159. // ========== Build methods ==========
  160. /**
  161. * Gets the referenced table name.
  162. * @return the name of the referenced table
  163. */
  164. internal string referenced_table {
  165. get { return _referenced_table; }
  166. }
  167. /**
  168. * Gets the referenced column name.
  169. * @return the name of the referenced column
  170. */
  171. internal string referenced_column {
  172. get { return _referenced_column; }
  173. }
  174. /**
  175. * Gets the constraint name (explicit or null for auto-generation).
  176. * @return the explicit constraint name, or null
  177. */
  178. internal string? constraint_name {
  179. get { return _constraint_name; }
  180. }
  181. /**
  182. * Gets the ON DELETE referential action.
  183. * @return the action to take on delete
  184. */
  185. internal ReferentialAction on_delete_action {
  186. get { return _on_delete; }
  187. }
  188. /**
  189. * Gets the ON UPDATE referential action.
  190. * @return the action to take on update
  191. */
  192. internal ReferentialAction on_update_action {
  193. get { return _on_update; }
  194. }
  195. /**
  196. * Builds a TableConstraint from this foreign key configuration.
  197. *
  198. * @param column_name the name of the column that has this FK
  199. * @param table_name the table name, used for auto-generating constraint names
  200. * @return a populated TableConstraint for this FK
  201. */
  202. public TableConstraint build_constraint(string column_name, string table_name) {
  203. var constraint = new TableConstraint();
  204. constraint.constraint_type = "FOREIGN KEY";
  205. constraint.columns.add(column_name);
  206. constraint.reference_table = _referenced_table;
  207. constraint.reference_columns.add(_referenced_column);
  208. constraint.on_delete_action = _on_delete;
  209. constraint.on_update_action = _on_update;
  210. // Auto-generate constraint name if not specified
  211. constraint.name = _constraint_name ?? @"fk_$(table_name)_$(column_name)";
  212. return constraint;
  213. }
  214. // ========== Delegate to inner builder ==========
  215. /**
  216. * Returns to the parent MigrationBuilder.
  217. *
  218. * This method navigates through the inner builder to find the MigrationBuilder.
  219. * @return the parent MigrationBuilder
  220. */
  221. internal override MigrationBuilder return_to_migration() {
  222. return _inner.return_to_migration();
  223. }
  224. /**
  225. * Gets the inner MigrationColumnBuilder being wrapped.
  226. * @return the inner builder
  227. */
  228. internal MigrationColumnBuilder inner_builder {
  229. get { return _inner; }
  230. }
  231. }
  232. }