From 553fc64df6b556792a71bb4cc4071a3fa15716d5 Mon Sep 17 00:00:00 2001 From: jongleb <10594395+jongleb@users.noreply.github.com> Date: Tue, 15 Sep 2026 18:53:30 +0000 Subject: [PATCH] configure migration ALTER options --- doc/_cli-options.md | 4 +- lib/dialect.ml | 10 ++- lib/sql.ml | 28 +++++++- lib/sql_parser.mly | 38 ++++++---- lib/syntax.ml | 2 +- src/cli.ml | 69 ++++++++++++++++--- src/gen_migrations.ml | 16 ++++- src/main.ml | 4 +- src/schema_diff.ml | 24 ++++--- .../diff-online-ddl.t/alter-options.sql | 4 ++ .../diff-online-ddl.t/explicit-lock.sql | 2 + .../diff-online-ddl.t/initial.sql | 1 + .../diff-online-ddl.t/lock-none.sql | 4 ++ .../diff-online-ddl.t/plain.sql | 4 ++ .../test_migrations/diff-online-ddl.t/run.t | 36 ++++++++++ .../diff-online-ddl.t/target.sql | 1 + 16 files changed, 207 insertions(+), 40 deletions(-) create mode 100644 test/cram/test_migrations/diff-online-ddl.t/alter-options.sql create mode 100644 test/cram/test_migrations/diff-online-ddl.t/explicit-lock.sql create mode 100644 test/cram/test_migrations/diff-online-ddl.t/initial.sql create mode 100644 test/cram/test_migrations/diff-online-ddl.t/lock-none.sql create mode 100644 test/cram/test_migrations/diff-online-ddl.t/plain.sql create mode 100644 test/cram/test_migrations/diff-online-ddl.t/run.t create mode 100644 test/cram/test_migrations/diff-online-ddl.t/target.sql diff --git a/doc/_cli-options.md b/doc/_cli-options.md index 760f5534..a375927c 100644 --- a/doc/_cli-options.md +++ b/doc/_cli-options.md @@ -19,10 +19,12 @@ -now Pin the migration id timestamp (default: current clock); ids are _ -max-migration-id-length Limit generated migration ids to N characters (default: no limit) -ddl-as-migration Write new tables as CREATE TABLE migrations instead of plain schema DDL + -alter-algorithm default|instant|inplace|copy Add ALGORITHM policy to generated ALTER TABLE statements (MySQL and TiDB only) + -alter-lock default|none|shared|exclusive Add LOCK policy to generated ALTER TABLE statements (MySQL only) Dialect and checks: -dialect mysql|postgresql|sqlite|tidb Set SQL dialect. Queries can only use its features - -no-check {all|{,}+} Disable dialect feature checks (possible features: collation|join_on_subquery|create_table_as_select|on_duplicate_key|on_conflict|straight_join|lock_in_share_mode|fulltext_index|unsigned_types|autoincrement|replace_into|row_locking|default_expr|ttl|cached_table|alter_column|user_defined_type|extension) + -no-check {all|{,}+} Disable dialect feature checks (possible features: collation|join_on_subquery|create_table_as_select|on_duplicate_key|on_conflict|straight_join|lock_in_share_mode|fulltext_index|unsigned_types|autoincrement|replace_into|row_locking|default_expr|ttl|cached_table|alter_column|alter_algorithm|alter_lock|user_defined_type|extension) -allow-write-notnull-null Accept writing a nullable value into a NOT NULL column, instead of failing (MySQL, TiDB and SQLite only) Generated header: diff --git a/lib/dialect.ml b/lib/dialect.ml index 86cfc374..3b0cbf73 100644 --- a/lib/dialect.ml +++ b/lib/dialect.ml @@ -32,6 +32,8 @@ type feature = | Ttl [@as "ttl"] | CachedTable [@as "cached_table"] | AlterColumn [@as "alter_column"] + | AlterAlgorithm [@as "alter_algorithm"] + | AlterLock [@as "alter_lock"] | UserDefinedType [@as "user_defined_type"] | Extension [@as "extension"] [@@deriving show { with_path = false }, enumerate, to_string, of_string] @@ -152,6 +154,11 @@ let get_alter_column (change : Sql.Alter_column_pg.t) pos = | Set_type _ | Set_not_null | Drop_not_null -> only AlterColumn [PostgreSQL] pos | Set_default | Drop_default -> only AlterColumn [MySQL; PostgreSQL; TiDB] pos +let get_alter_option ({ Sql.value; pos } : Sql.alter_option Sql.located) = + match value with + | Alter_algorithm _ -> only AlterAlgorithm [MySQL; TiDB] pos + | Alter_lock _ -> only AlterLock [MySQL] pos + let get_user_defined_type pos = only UserDefinedType [PostgreSQL] pos let get_extension pos = only Extension [PostgreSQL] pos @@ -438,7 +445,8 @@ let rec analyze stmt = let acc = get_create_table_as_select pos :: acc in analyze_select_full acc [select] List.rev | Drop _ -> [] - | Alter (_, actions) -> + | Alter { alter_actions = actions; alter_options = options; _ } -> + let acc = List.rev_append (List.map get_alter_option options) acc in analyze_alter_action acc actions List.rev | Rename _ -> [] | CreateIndex { ci_cols; _ } -> List.concat_map check_collated ci_cols diff --git a/lib/sql.ml b/lib/sql.ml index a6c01165..22391af9 100644 --- a/lib/sql.ml +++ b/lib/sql.ml @@ -1079,6 +1079,32 @@ type alter_action = [ | `NoCache of Pos.t | `AlterColumnPG of string * Alter_column_pg.t located ] [@@deriving show {with_path=false}] +type alter_algorithm = + | Algorithm_default [@as "default"] + | Algorithm_instant [@as "instant"] + | Algorithm_inplace [@as "inplace"] + | Algorithm_copy [@as "copy"] + [@@deriving show {with_path=false}, enumerate, to_string, of_string] + +type alter_lock = + | Lock_default [@as "default"] + | Lock_none [@as "none"] + | Lock_shared [@as "shared"] + | Lock_exclusive [@as "exclusive"] + [@@deriving show {with_path=false}, enumerate, to_string, of_string] + +type alter_option = + | Alter_algorithm of alter_algorithm + | Alter_lock of alter_lock + [@@deriving show {with_path=false}] + +type alter = { + alter_table : table_name; + alter_actions : alter_action list; + alter_options : alter_option located list; +} +[@@deriving show] + type create_type_target = | TypeEnum of string list [@@deriving show {with_path=false}] @@ -1086,7 +1112,7 @@ type create_type_target = type stmt = | Create of table_name located * create_target | Drop of table_name - | Alter of table_name * alter_action list + | Alter of alter | Rename of (table_name * table_name) list | CreateIndex of create_index_def | Insert of insert_action diff --git a/lib/sql_parser.mly b/lib/sql_parser.mly index 49e52289..df3302f2 100644 --- a/lib/sql_parser.mly +++ b/lib/sql_parser.mly @@ -144,7 +144,16 @@ statement: CREATE ioption(temporary) TABLE ioption(if_not_exists) name=located(t } | ALTER TABLE name=table_name actions=commas(alter_action_or_ignored) { - Alter (name, List.filter_map (fun x -> x) actions) + let actions, options = + List.fold_right + (fun item (actions, options) -> + match item with + | `Action action -> action :: actions, options + | `Option option -> actions, option :: options + | `Ignored -> actions, options) + actions ([], []) + in + Alter { alter_table = name; alter_actions = actions; alter_options = options } } | RENAME TABLE l=separated_nonempty_list(COMMA, separated_pair(table_name,TO,table_name)) { Rename l } | DROP either(TABLE,VIEW) if_exists? name=table_name @@ -422,11 +431,13 @@ alter_action: ADD COLUMN? col=maybe_parenth(column_def) pos=alter_pos { `Add (co | NOCACHE { `NoCache ($startofs, $endofs) } | either(DEFAULT,pair(CONVERT,TO))? cs=charset c=collate? { `Default_or_convert_to (cs, c) } -(* clauses sqlgg parses but does not act on: kept out of the action list *) -alter_action_or_ignored: a=alter_action { Some a } - | SET IDENT IDENT { None } - | ALGORITHM EQUAL algorithm { None } - | LOCK EQUAL lock { None } +alter_action_or_ignored: a=alter_action { `Action a } + | SET IDENT IDENT { `Ignored } + | option=located(alter_option) { `Option option } + +alter_option: + | ALGORITHM EQUAL algorithm=algorithm { Alter_algorithm algorithm } + | LOCK EQUAL lock=lock { Alter_lock lock } ttl_option: TTL EQUAL col=ident PLUS INTERVAL n=INTEGER unit=INTERVAL_UNIT { `TtlSet (col, n, unit) } @@ -856,14 +867,15 @@ manual_type: | T_DATETIME NULL { Source_type.nullable Datetime } algorithm: - | INPLACE { } - | COPY { } - | INSTANT { } + | DEFAULT { Algorithm_default } + | INPLACE { Algorithm_inplace } + | COPY { Algorithm_copy } + | INSTANT { Algorithm_instant } lock: - | NONE {} - | EXCLUSIVE {} - | DEFAULT {} - | SHARED {} + | NONE { Lock_none } + | EXCLUSIVE { Lock_exclusive } + | DEFAULT { Lock_default } + | SHARED { Lock_shared } %inline located(X): X { make_located ~value:$1 ~pos:($startofs, $endofs) } diff --git a/lib/syntax.ml b/lib/syntax.ml index f7c40677..a78e9381 100644 --- a/lib/syntax.ml +++ b/lib/syntax.ml @@ -1830,7 +1830,7 @@ let rec eval (stmt:Sql.stmt) = Tables.add (name, to_schema schema); [], params, Create name, { annotations with table_defs = (located_name, []) :: annotations.table_defs } - | Alter (name,actions) -> + | Alter { alter_table = name; alter_actions = actions; _ } -> List.iter (function | `Add (col,pos) -> let source_kind = Option.map (fun k -> k.value) col.Alter_action_attr.kind in diff --git a/src/cli.ml b/src/cli.ml index 27fcc0de..546ce8e7 100644 --- a/src/cli.ml +++ b/src/cli.ml @@ -210,6 +210,27 @@ let set_dialect s = | Dialect.MySQL | Dialect.TiDB | Dialect.SQLite -> Some Gen.Unnamed (* ? syntax *) | Dialect.PostgreSQL -> Some Gen.PostgreSQL (* $1, $2, etc. *) +let enum_values to_string values = + String.concat "|" (List.map to_string values) + +let alter_lock_values = + enum_values Sql.alter_lock_to_string Sql.all_of_alter_lock + +let alter_algorithm_values = + enum_values Sql.alter_algorithm_to_string Sql.all_of_alter_algorithm + +let parse_alter_option kind values of_string s = + match of_string s with + | Some option -> option + | None -> fatal "unknown ALTER TABLE %s %S (expected %s)" kind s values + +let parse_alter_lock = + parse_alter_option "lock" alter_lock_values Sql.alter_lock_of_string + +let parse_alter_algorithm = + parse_alter_option "algorithm" alter_algorithm_values + Sql.alter_algorithm_of_string + let set_no_check = function | "all" -> Sqlgg_config.set_no_check_features Dialect.all_of_feature | s -> @@ -246,9 +267,9 @@ let schema_of_sources sources = let load_schema files = schema_of_sources (to_file_sources files) -let diff_schema ~naming ~ddl_as_migration ~from_ ~to_ = +let diff_schema ~naming ~alter_options ~ddl_as_migration ~from_ ~to_ = let migs = - try Schema_diff.generate ~naming ~ddl_as_migration ~from_ ~to_ + try Schema_diff.generate ~naming ~alter_options ~ddl_as_migration ~from_ ~to_ with Gen_migrations.Migration_error msg -> fatal "cannot generate migration (write this step manually):\n%s" msg in @@ -272,6 +293,7 @@ type delta_args = { now : int option; max_id_length : int option; ddl_as_migration : bool; + alter_options : Sql.alter_option list; } type diff_args = { @@ -310,6 +332,8 @@ let parse_args () = let now = ref None in let max_id_length = ref None in let ddl_as_migration = ref false in + let alter_lock = ref None in + let alter_algorithm = ref None in let files : (string, [ `Open of Gen.stmt list | `Positional ]) Hashtbl.t = Hashtbl.create 4 in let canonical = function | "-" -> "-" @@ -356,14 +380,22 @@ let parse_args () = "-max-migration-id-length", Arg.Int (fun n -> max_id_length := Some n), " Limit generated migration ids to N characters (default: no limit)"; "-ddl-as-migration", Arg.Set ddl_as_migration, " Write new tables as CREATE TABLE migrations instead of plain schema DDL"; + "-alter-algorithm", Arg.String (fun s -> alter_algorithm := Some (parse_alter_algorithm s)), + sprintf "%s Add ALGORITHM policy to generated ALTER TABLE statements (MySQL and TiDB only)" + alter_algorithm_values; + "-alter-lock", Arg.String (fun s -> alter_lock := Some (parse_alter_lock s)), + sprintf "%s Add LOCK policy to generated ALTER TABLE statements (MySQL only)" + alter_lock_values; ] }; { title = "Dialect and checks"; opts = [ - "-dialect", Arg.String set_dialect, sprintf "%s Set SQL dialect. Queries can only use its features" (Dialect.all |> List.map Dialect.to_string |> String.concat "|"); + "-dialect", Arg.String set_dialect, + sprintf "%s Set SQL dialect. Queries can only use its features" + (enum_values Dialect.to_string Dialect.all); "-no-check", Arg.String set_no_check, sprintf "{all|{,}+} Disable dialect feature checks (possible features: %s)" - (Dialect.all_of_feature |> List.map Dialect.feature_to_string |> String.concat "|"); + (enum_values Dialect.feature_to_string Dialect.all_of_feature); "-allow-write-notnull-null", Arg.Unit (fun () -> Sqlgg_config.allow_write_notnull_null true), " Accept writing a nullable value into a NOT NULL column, instead of failing (MySQL, TiDB and SQLite only)"; ] }; @@ -404,12 +436,29 @@ let parse_args () = in Arg.parse args work usage_msg; if Array.length Sys.argv = 1 then show_help (); + begin match !alter_lock, !Dialect.selected with + | Some _, (Dialect.PostgreSQL | Dialect.SQLite | Dialect.TiDB) -> + fatal "-alter-lock is only supported for dialect mysql" + | None, _ | Some _, Dialect.MySQL -> () + end; + begin match !alter_algorithm, !Dialect.selected with + | Some _, (Dialect.PostgreSQL | Dialect.SQLite) -> + fatal "-alter-algorithm is only supported for dialects mysql and tidb" + | None, _ | Some _, (Dialect.MySQL | Dialect.TiDB) -> () + end; + let alter_options = + Stdlib.Option.to_list + (Option.map (fun algorithm -> Sql.Alter_algorithm algorithm) !alter_algorithm) + @ Stdlib.Option.to_list + (Option.map (fun lock -> Sql.Alter_lock lock) !alter_lock) + in let delta = { name = !name; target_files = List.rev !target_files; now = !now; max_id_length = !max_id_length; - ddl_as_migration = !ddl_as_migration } + ddl_as_migration = !ddl_as_migration; + alter_options } in (* these modes reset the schema and rebuild it from -base/-target/-initial, silently discarding whatever -open loaded *) @@ -451,7 +500,8 @@ let parse_migrations blocks = abort_on_errors (); migs -let run_migrate ({ delta = { name; target_files; now; max_id_length; ddl_as_migration }; +let run_migrate ({ delta = { name; target_files; now; max_id_length; + ddl_as_migration; alter_options }; gen_lang; initial_files; migrations_file; extends_file } : migrate_args) = let initial = to_file_sources initial_files in let ext = Option.map_default read_blocks [] extends_file in @@ -468,7 +518,7 @@ let run_migrate ({ delta = { name; target_files; now; max_id_length; ddl_as_migr in let base = next_base now before in let naming = Migration_id.naming ~max_length:max_id_length base in - match diff_schema ~naming ~ddl_as_migration ~from_:current ~to_:target with + match diff_schema ~naming ~alter_options ~ddl_as_migration ~from_:current ~to_:target with | [] -> regenerate (); (match before with @@ -508,13 +558,14 @@ let run_materialize_schema ({ base_files } : materialize_args) = end; print_endline ddl -let run_diff ({ delta = { name; target_files; now; max_id_length; ddl_as_migration }; +let run_diff ({ delta = { name; target_files; now; max_id_length; + ddl_as_migration; alter_options }; base_files; output } : diff_args) = let from_ = load_schema base_files in let to_ = load_schema target_files in let base = next_base now [] in let naming = Migration_id.naming ~max_length:max_id_length base in - let migs = diff_schema ~naming ~ddl_as_migration ~from_ ~to_ in + let migs = diff_schema ~naming ~alter_options ~ddl_as_migration ~from_ ~to_ in Tables.restore from_; match output with | None -> () diff --git a/src/gen_migrations.ml b/src/gen_migrations.ml index df8cae61..52322be6 100644 --- a/src/gen_migrations.ml +++ b/src/gen_migrations.ml @@ -219,6 +219,12 @@ type alter_clause = | Default_charset of ddl_charset | Ttl_options of Sql.ttl_option list +let alter_option_to_sql = function + | Sql.Alter_algorithm algorithm -> + "ALGORITHM=" ^ String.uppercase_ascii (Sql.alter_algorithm_to_string algorithm) + | Sql.Alter_lock lock -> + "LOCK=" ^ String.uppercase_ascii (Sql.alter_lock_to_string lock) + let alter_clause_body ~default_sql_lookup = function | Columns actions -> (match List.map (action_to_sql_fragment ~default_sql_lookup) actions with @@ -236,8 +242,14 @@ let alter_clause_body ~default_sql_lookup = function | Ttl_options opts -> Some (action_to_sql_fragment ~default_sql_lookup (`TtlOptions (opts, (0, 0)))) -let alter_table_sql ~default_sql_lookup table clause = - Option.map (sprintf "ALTER TABLE %s %s" (quote_table_name table)) +let alter_table_sql ~default_sql_lookup ?(options = []) table clause = + let suffix = + match options with + | [] -> "" + | options -> ", " ^ String.concat ", " (List.map alter_option_to_sql options) + in + Option.map + (fun body -> sprintf "ALTER TABLE %s %s%s" (quote_table_name table) body suffix) (alter_clause_body ~default_sql_lookup clause) let drop_table_sql name = diff --git a/src/main.ml b/src/main.ml index 0681276e..e35a8c26 100644 --- a/src/main.ml +++ b/src/main.ml @@ -152,7 +152,9 @@ let get_statements ch = Printf.eprintf "Warning: this SQL statement will produce rowset with duplicate column names:\n%s\n" stmt.text; stmts) -let replay_statement stmt = ignore (executable stmt) +let replay_statement stmt = + let (_ : Gen.stmt option) = executable stmt in + () let replay_sql sql = List.iter replay_statement (prepare_statements sql) diff --git a/src/schema_diff.ml b/src/schema_diff.ml index 21c30785..deb5c2f7 100644 --- a/src/schema_diff.ml +++ b/src/schema_diff.ml @@ -204,16 +204,17 @@ let diff_table ~from_ ~to_ = diff_columns ~from_ ~to_ @ diff_pk ~from_ ~to_ @ diff_indexes ~from_ ~to_ @ diff_charset ~from_ ~to_ @ diff_ttl ~from_ ~to_ -let alter_change name target actions = +let alter_change ~alter_options name target actions = let default_sql_lookup col_name = Stdlib.Option.bind (Tables.find_column ~name:col_name target.Tables.columns) (fun (c : Tables.column) -> c.default_sql) in Option.map (fun sql -> Alter_table { table = name; sql; actions }) - (Gen_migrations.alter_table_sql ~default_sql_lookup name (Gen_migrations.Columns actions)) + (Gen_migrations.alter_table_sql ~default_sql_lookup ~options:alter_options + name (Gen_migrations.Columns actions)) -let diff ~ddl_as_migration ~from_ ~to_ ~by_from ~by_to = +let diff ~alter_options ~ddl_as_migration ~from_ ~to_ ~by_from ~by_to = let creates = if not ddl_as_migration then [] else @@ -225,7 +226,7 @@ let diff ~ddl_as_migration ~from_ ~to_ ~by_from ~by_to = let alters = to_ |> List.filter_map (fun (t : Tables.stored_table) -> Stdlib.Option.bind (SMap.find_opt t.name.tn by_from) - (fun old -> alter_change t.name t (diff_table ~from_:old ~to_:t))) in + (fun old -> alter_change ~alter_options t.name t (diff_table ~from_:old ~to_:t))) in drops @ creates @ alters let create_table_of t = @@ -265,7 +266,7 @@ let kind_of_change = function | Drop_table t -> Stmt.Drop t.Tables.name | Alter_table { table; _ } -> Stmt.Alter [table] -let invert ~by_from ~by_to up = +let invert ~alter_options ~by_from ~by_to up = let irreversible reason = Gen_migrations.fail "table %s: this change cannot be auto-reverted (%s); \ @@ -280,24 +281,25 @@ let invert ~by_from ~by_to up = | None, _ | _, None -> irreversible "table is missing from the baseline or target snapshot" | Some f, Some t -> - if f.Tables.tbl_charset = None && t.Tables.tbl_charset <> None then + match f.Tables.tbl_charset, t.Tables.tbl_charset with + | None, Some _ -> irreversible "a DEFAULT CHARSET / COLLATE was added while the baseline has \ no explicit charset to restore" - else - match alter_change name f (diff_table ~from_:t ~to_:f) with + | _ -> + match alter_change ~alter_options name f (diff_table ~from_:t ~to_:f) with | Some down -> down | None -> irreversible "reverse diff renders to nothing" -let generate ~naming ~ddl_as_migration ~from_ ~to_ = +let generate ~naming ~alter_options ~ddl_as_migration ~from_ ~to_ = let from_ = List.map materialize_inline_unique from_ in let to_ = List.map materialize_inline_unique to_ in let by_from = table_by_name from_ in let by_to = table_by_name to_ in - diff ~ddl_as_migration ~from_ ~to_ ~by_from ~by_to |> List.map (fun up -> + diff ~alter_options ~ddl_as_migration ~from_ ~to_ ~by_from ~by_to |> List.map (fun up -> { Gen_migrations.props = [ Props.Name (change_name ~naming up) ]; kind = kind_of_change up; apply = render_apply up; - revert = render_apply (invert ~by_from ~by_to up) }) + revert = render_apply (invert ~alter_options ~by_from ~by_to up) }) let canonical ts = let index_sig (name, (i : Tables.stored_index)) = diff --git a/test/cram/test_migrations/diff-online-ddl.t/alter-options.sql b/test/cram/test_migrations/diff-online-ddl.t/alter-options.sql new file mode 100644 index 00000000..5312729d --- /dev/null +++ b/test/cram/test_migrations/diff-online-ddl.t/alter-options.sql @@ -0,0 +1,4 @@ +-- [sqlgg] generated +-- [sqlgg] id=20260101000000_alter_users_add_index_email_idx +ALTER TABLE `users` ADD INDEX `email_idx` (`email`), ALGORITHM=INPLACE, LOCK=NONE; +ALTER TABLE `users` DROP INDEX `email_idx`, ALGORITHM=INPLACE, LOCK=NONE; diff --git a/test/cram/test_migrations/diff-online-ddl.t/explicit-lock.sql b/test/cram/test_migrations/diff-online-ddl.t/explicit-lock.sql new file mode 100644 index 00000000..3fde7c2b --- /dev/null +++ b/test/cram/test_migrations/diff-online-ddl.t/explicit-lock.sql @@ -0,0 +1,2 @@ +CREATE TABLE users (id INT NOT NULL, email VARCHAR(255)); +ALTER TABLE users ADD INDEX email_idx (email), LOCK=NONE; diff --git a/test/cram/test_migrations/diff-online-ddl.t/initial.sql b/test/cram/test_migrations/diff-online-ddl.t/initial.sql new file mode 100644 index 00000000..00b1fbaf --- /dev/null +++ b/test/cram/test_migrations/diff-online-ddl.t/initial.sql @@ -0,0 +1 @@ +CREATE TABLE users (id INT NOT NULL, email VARCHAR(255)); diff --git a/test/cram/test_migrations/diff-online-ddl.t/lock-none.sql b/test/cram/test_migrations/diff-online-ddl.t/lock-none.sql new file mode 100644 index 00000000..652f5542 --- /dev/null +++ b/test/cram/test_migrations/diff-online-ddl.t/lock-none.sql @@ -0,0 +1,4 @@ +-- [sqlgg] generated +-- [sqlgg] id=20260101000000_alter_users_add_index_email_idx +ALTER TABLE `users` ADD INDEX `email_idx` (`email`), LOCK=NONE; +ALTER TABLE `users` DROP INDEX `email_idx`, LOCK=NONE; diff --git a/test/cram/test_migrations/diff-online-ddl.t/plain.sql b/test/cram/test_migrations/diff-online-ddl.t/plain.sql new file mode 100644 index 00000000..0711c899 --- /dev/null +++ b/test/cram/test_migrations/diff-online-ddl.t/plain.sql @@ -0,0 +1,4 @@ +-- [sqlgg] generated +-- [sqlgg] id=20260101000000_alter_users_add_index_email_idx +ALTER TABLE `users` ADD INDEX `email_idx` (`email`); +ALTER TABLE `users` DROP INDEX `email_idx`; diff --git a/test/cram/test_migrations/diff-online-ddl.t/run.t b/test/cram/test_migrations/diff-online-ddl.t/run.t new file mode 100644 index 00000000..0bdbd3fa --- /dev/null +++ b/test/cram/test_migrations/diff-online-ddl.t/run.t @@ -0,0 +1,36 @@ +Without an alter-lock policy, generated ALTER TABLE statements use the server +default. + + $ D="sqlgg -no-header -dialect mysql -diff -gen sql -now 20260101000000 -base initial.sql -target target.sql" + $ $D | diff plain.sql - + +An explicit policy is applied to both directions of every generated ALTER. + + $ $D -alter-lock none | diff lock-none.sql - + +Algorithm and lock policies can be combined. + + $ $D -alter-algorithm inplace -alter-lock none | diff alter-options.sql - + +The policy is not accepted for dialects without MySQL LOCK semantics. + + $ sqlgg -no-header -dialect tidb -diff -gen sql -alter-lock none -base initial.sql -target target.sql 2>&1 + -alter-lock is only supported for dialect mysql + [1] + +Input ALTER options participate in dialect validation on the XML path. + + $ sqlgg -no-header -dialect tidb -gen xml explicit-lock.sql 2>&1 + Feature AlterLock is not supported for dialect TiDB (supported by: MySQL) at LOCK=NONE + Errors encountered, no code generated + [1] + +Unknown lock values fail during argument parsing. + + $ $D -alter-lock invalid 2>&1 + unknown ALTER TABLE lock "invalid" (expected default|none|shared|exclusive) + [1] + + $ $D -alter-algorithm invalid 2>&1 + unknown ALTER TABLE algorithm "invalid" (expected default|instant|inplace|copy) + [1] diff --git a/test/cram/test_migrations/diff-online-ddl.t/target.sql b/test/cram/test_migrations/diff-online-ddl.t/target.sql new file mode 100644 index 00000000..caf695af --- /dev/null +++ b/test/cram/test_migrations/diff-online-ddl.t/target.sql @@ -0,0 +1 @@ +CREATE TABLE users (id INT NOT NULL, email VARCHAR(255), INDEX email_idx (email));