| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- 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<string>("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<SchemaOperation> _operations;
- private Vector<MigrationColumnBuilder> _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<SchemaOperation>();
- _column_builders = new Vector<MigrationColumnBuilder>();
- }
-
- /**
- * 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<T>(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<SchemaOperation> 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<AddColumnOperation> add_col_ops = new Vector<AddColumnOperation>();
- 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;
- }
- }
- }
|