using Invercargill.DataStructures; using InvercargillSql.Orm; namespace InvercargillSql.Migrations { /** * Builder for configuring foreign key constraints on a column. * * This builder extends MigrationColumnBuilder to allow method chaining between * FK configuration and column configuration. FK-specific methods return this * ForeignKeyBuilder, while column methods delegate to the parent and return * MigrationColumnBuilder (ending the FK configuration chain). * * Example usage: * {{{ * t.column("user_id") * .not_null() * .references("users", "id") * .name("fk_orders_users") * .on_delete_cascade() * .indexed(); // Returns to column builder * }}} */ public class ForeignKeyBuilder : MigrationColumnBuilder { private MigrationColumnBuilder _inner; private string _referenced_table; private string _referenced_column; private string? _constraint_name; private ReferentialAction _on_delete = ReferentialAction.NO_ACTION; private ReferentialAction _on_update = ReferentialAction.NO_ACTION; /** * Creates a new ForeignKeyBuilder wrapping an existing MigrationColumnBuilder. * * @param inner the parent MigrationColumnBuilder to wrap and delegate to * @param ref_table the referenced table name * @param ref_column the referenced column name in the foreign table */ internal ForeignKeyBuilder(MigrationColumnBuilder inner, string ref_table, string ref_column) { base.for_subclass(); _inner = inner; _referenced_table = ref_table; _referenced_column = ref_column; } /** * Sets an explicit name for the foreign key constraint. * * If not specified, the constraint name will be auto-generated as * `fk_{table}_{column}` when the constraint is built. * * @param constraint_name the explicit constraint name * @return this builder for method chaining */ public ForeignKeyBuilder name(string constraint_name) { _constraint_name = constraint_name; return this; } // ========== ON DELETE actions ========== /** * Sets ON DELETE CASCADE for this foreign key. * * When a referenced row is deleted, all referencing rows will be deleted automatically. * @return this builder for method chaining */ public ForeignKeyBuilder on_delete_cascade() { _on_delete = ReferentialAction.CASCADE; return this; } /** * Sets ON DELETE SET NULL for this foreign key. * * When a referenced row is deleted, the referencing column will be set to NULL. * @return this builder for method chaining */ public ForeignKeyBuilder on_delete_set_null() { _on_delete = ReferentialAction.SET_NULL; return this; } /** * Sets ON DELETE SET DEFAULT for this foreign key. * * When a referenced row is deleted, the referencing column will be set to its default value. * @return this builder for method chaining */ public ForeignKeyBuilder on_delete_set_default() { _on_delete = ReferentialAction.SET_DEFAULT; return this; } /** * Sets ON DELETE NO ACTION for this foreign key. * * This is the SQL standard default. The database will prevent the delete if * referencing rows exist, but the check may be deferred until transaction end. * @return this builder for method chaining */ public ForeignKeyBuilder on_delete_no_action() { _on_delete = ReferentialAction.NO_ACTION; return this; } /** * Sets ON DELETE RESTRICT for this foreign key. * * Similar to NO_ACTION, but the check is immediate and cannot be deferred. * @return this builder for method chaining */ public ForeignKeyBuilder on_delete_restrict() { _on_delete = ReferentialAction.RESTRICT; return this; } // ========== ON UPDATE actions ========== /** * Sets ON UPDATE CASCADE for this foreign key. * * When a referenced row's key is updated, the referencing column will be updated automatically. * @return this builder for method chaining */ public ForeignKeyBuilder on_update_cascade() { _on_update = ReferentialAction.CASCADE; return this; } /** * Sets ON UPDATE SET NULL for this foreign key. * * When a referenced row's key is updated, the referencing column will be set to NULL. * @return this builder for method chaining */ public ForeignKeyBuilder on_update_set_null() { _on_update = ReferentialAction.SET_NULL; return this; } /** * Sets ON UPDATE SET DEFAULT for this foreign key. * * When a referenced row's key is updated, the referencing column will be set to its default value. * @return this builder for method chaining */ public ForeignKeyBuilder on_update_set_default() { _on_update = ReferentialAction.SET_DEFAULT; return this; } /** * Sets ON UPDATE NO ACTION for this foreign key. * * This is the SQL standard default. The database will prevent the update if * referencing rows exist, but the check may be deferred until transaction end. * @return this builder for method chaining */ public ForeignKeyBuilder on_update_no_action() { _on_update = ReferentialAction.NO_ACTION; return this; } /** * Sets ON UPDATE RESTRICT for this foreign key. * * Similar to NO_ACTION, but the check is immediate and cannot be deferred. * @return this builder for method chaining */ public ForeignKeyBuilder on_update_restrict() { _on_update = ReferentialAction.RESTRICT; return this; } // ========== Build methods ========== /** * Gets the referenced table name. * @return the name of the referenced table */ internal string referenced_table { get { return _referenced_table; } } /** * Gets the referenced column name. * @return the name of the referenced column */ internal string referenced_column { get { return _referenced_column; } } /** * Gets the constraint name (explicit or null for auto-generation). * @return the explicit constraint name, or null */ internal string? constraint_name { get { return _constraint_name; } } /** * Gets the ON DELETE referential action. * @return the action to take on delete */ internal ReferentialAction on_delete_action { get { return _on_delete; } } /** * Gets the ON UPDATE referential action. * @return the action to take on update */ internal ReferentialAction on_update_action { get { return _on_update; } } /** * Builds a TableConstraint from this foreign key configuration. * * @param column_name the name of the column that has this FK * @param table_name the table name, used for auto-generating constraint names * @return a populated TableConstraint for this FK */ public TableConstraint build_constraint(string column_name, string table_name) { var constraint = new TableConstraint(); constraint.constraint_type = "FOREIGN KEY"; constraint.columns.add(column_name); constraint.reference_table = _referenced_table; constraint.reference_columns.add(_referenced_column); constraint.on_delete_action = _on_delete; constraint.on_update_action = _on_update; // Auto-generate constraint name if not specified constraint.name = _constraint_name ?? @"fk_$(table_name)_$(column_name)"; return constraint; } // ========== Delegate to inner builder ========== /** * Returns to the parent MigrationBuilder. * * This method navigates through the inner builder to find the MigrationBuilder. * @return the parent MigrationBuilder */ internal override MigrationBuilder return_to_migration() { return _inner.return_to_migration(); } /** * Gets the inner MigrationColumnBuilder being wrapped. * @return the inner builder */ internal MigrationColumnBuilder inner_builder { get { return _inner; } } } }