using Invercargill.DataStructures; using InvercargillSql.Orm; namespace InvercargillSql.Migrations { /** * Builder for ALTER TABLE operations that provides a fluent API for modifying existing tables. * * This builder is used within MigrationBuilder.alter_table() to add, drop, or rename columns, * and to create or drop indexes. It follows the builder pattern, returning itself for chaining * alter operations. * * Example usage within a migration: * {{{ * b.alter_table("users", t => { * t.add_column("email", c => c.type_text().not_null()) * t.rename_column("old_name", "new_name") * t.drop_column("deprecated_field") * t.create_index("idx_email").on_column("email") * t.drop_index("idx_old_email") * t.drop_foreign_key("fk_users_profile") * t.drop_foreign_key_on("profile_id") * t.drop_index_on("email") * }); * }}} */ public class AlterTableBuilder : Object { private MigrationBuilder _parent; private string _table_name; private Vector _operations; private Vector _column_builders; /** * Creates a new AlterTableBuilder for the specified table. * * @param parent the parent MigrationBuilder to return to after alterations * @param table_name the name of the table to alter */ internal AlterTableBuilder(MigrationBuilder parent, string table_name) { _parent = parent; _table_name = table_name; _operations = new Vector(); _column_builders = new Vector(); } /** * Adds a new column to the table. * * The generic type T is used to infer the column type. The returned * MigrationColumnBuilder can be used to configure additional column properties. * * Note: The builder is tracked internally to collect FK constraints and index * operations when get_operations() is called. * * @param name the name of the column to add * @return a MigrationColumnBuilder for configuring the column */ public MigrationColumnBuilder add_column(string name) { var col = new ColumnDefinition() { name = name, column_type = ColumnType.from_gtype(typeof(T)) ?? ColumnType.TEXT }; // Add the operation immediately - the column builder will modify it in place _operations.add(new AddColumnOperation() { table_name = _table_name, column = col }); var builder = new MigrationColumnBuilder.for_alter(this, _table_name, col); _column_builders.add(builder); return builder; } /** * Creates an index on this table. * * Use the returned IndexBuilder to specify columns and uniqueness. * * @param name the name of the index * @return an IndexBuilder for configuring the index * * Example: * {{{ * t.create_index("idx_email").on_column("email") * t.create_index("idx_composite").on_columns("email", "name").unique() * }}} */ public IndexBuilder create_index(string name) { return new IndexBuilder.for_alter(this, _table_name, name); } /** * Drops an index from this table. * * @param name the name of the index to drop * @return this builder for method chaining */ public AlterTableBuilder drop_index(string name) { _operations.add(new DropIndexOperation() { index_name = name, table_name = _table_name }); return this; } /** * Drops a foreign key constraint by its explicit name. * * Use this when you know the exact constraint name. * * @param constraint_name the name of the FK constraint to drop * @return this builder for method chaining */ public AlterTableBuilder drop_foreign_key(string constraint_name) { var op = new DropForeignKeyOperation(_table_name); op.constraint_name = constraint_name; _operations.add(op); return this; } /** * Drops a foreign key constraint by column name. * * This is useful for auto-generated constraint names (fk_{table}_{column}). * * @param column_name the column name that has the FK constraint * @return this builder for method chaining */ public AlterTableBuilder drop_foreign_key_on(string column_name) { var op = new DropForeignKeyOperation(_table_name); op.column_name = column_name; _operations.add(op); return this; } /** * Drops an index by column name. * * This is useful for auto-generated index names (idx_{table}_{column}). * * @param column_name the column name that has the index * @return this builder for method chaining */ public AlterTableBuilder drop_index_on(string column_name) { string index_name = @"idx_$(_table_name)_$(column_name)"; _operations.add(new DropIndexOperation() { index_name = index_name, table_name = _table_name }); return this; } /** * Adds an AddColumnOperation to the operations list. * * This is used internally by MigrationColumnBuilder to add the configured * column definition to the alter operations. * * @param col the column definition to add */ internal void add_column_operation(ColumnDefinition col) { _operations.add(new AddColumnOperation() { table_name = _table_name, column = col }); } /** * Adds an index operation to the operations list. * * This is used internally by IndexBuilder. * * @param op the index operation to add */ internal void add_index_operation(CreateIndexOperation op) { _operations.add(op); } /** * Drops a column from the table. * * @param name the name of the column to drop * @return this builder for method chaining */ public AlterTableBuilder drop_column(string name) { _operations.add(new DropColumnOperation() { table_name = _table_name, column_name = name }); return this; } /** * Renames a column in the table. * * @param old_name the current name of the column * @param new_name the new name for the column * @return this builder for method chaining */ public AlterTableBuilder rename_column(string old_name, string new_name) { _operations.add(new RenameColumnOperation() { table_name = _table_name, old_name = old_name, new_name = new_name }); return this; } /** * Returns to the parent MigrationBuilder. * * This method is used by MigrationColumnBuilder to navigate back to the * MigrationBuilder after configuring a column. * * @return the parent MigrationBuilder */ internal MigrationBuilder return_to_migration() { return _parent; } /** * Gets all schema operations collected by this builder. * * This method also collects FK constraints and index operations from column builders. * For ALTER TABLE ADD COLUMN, FK constraints are added as separate operations since * they need to be applied after the column is added. * * @return a Vector of SchemaOperation objects representing the ALTER TABLE operations */ internal Vector get_operations() { // Process column builders to collect FK and index operations // We need to find the corresponding AddColumnOperation for each builder // and add FK/index operations as needed // First, collect all add column operations to match with builders Vector add_col_ops = new Vector(); foreach (var op in _operations) { if (op is AddColumnOperation) { add_col_ops.add((AddColumnOperation) op); } } // Process each column builder for (int i = 0; i < (int) _column_builders.length && i < (int) add_col_ops.length; i++) { var builder = _column_builders.get(i); var add_col_op = add_col_ops.get(i); var col = add_col_op.column; // Collect FK constraint if configured // For ALTER TABLE, we need to add the FK as a table constraint // Note: SQLite doesn't support ADD CONSTRAINT, so the dialect needs // to handle this appropriately (e.g., table recreation) var fk_builder = builder.fk_builder; if (fk_builder != null) { var constraint = fk_builder.build_constraint(col.name, _table_name); // Store the constraint info for the dialect to use // We add a marker to the AddColumnOperation by storing FK info // The dialect will generate inline REFERENCES clause add_col_op.foreign_key_constraint = constraint; } // Collect index operation if configured if (builder.should_create_index) { var index_op = new CreateIndexOperation() { table_name = _table_name, is_unique = col.is_unique }; index_op.columns.add(col.name); // Use custom index name or auto-generate index_op.index_name = builder.index_name ?? @"idx_$(_table_name)_$(col.name)"; _operations.add(index_op); } } return _operations; } } }