diff --git a/lib/sql.ml b/lib/sql.ml index 22391af9..fedc9c9c 100644 --- a/lib/sql.ml +++ b/lib/sql.ml @@ -1047,7 +1047,8 @@ type charset_name = Named of string | Binary | Ascii | Unicode type ttl_option = [ `TtlSet of string * int * string - | `TtlEnable of string ] [@@deriving show {with_path=false}] + | `TtlEnable of string + | `TtlJobInterval of string ] [@@deriving show {with_path=false}] module Alter_column_pg = struct type t = diff --git a/lib/sql_lexer.mll b/lib/sql_lexer.mll index ba454908..7bad04d8 100644 --- a/lib/sql_lexer.mll +++ b/lib/sql_lexer.mll @@ -193,6 +193,7 @@ let keywords = "recursive", RECURSIVE; "ttl", TTL; "ttl_enable", TTL_ENABLE; + "ttl_job_interval", TTL_JOB_INTERVAL; "remove", REMOVE; "type", TYPE "type"; ] in (* more *) diff --git a/lib/sql_parser.mly b/lib/sql_parser.mly index df3302f2..cb6943d7 100644 --- a/lib/sql_parser.mly +++ b/lib/sql_parser.mly @@ -46,7 +46,7 @@ FIRST_VALUE LAST_VALUE NTH_VALUE PARTITION ROWS RANGE UNBOUNDED PRECEDING FOLLOWING CURRENT ROW CAST GENERATED ALWAYS VIRTUAL STORED STATEMENT DOUBLECOLON QSTN TWO_QSTN INSTANT INPLACE COPY ALGORITHM RECURSIVE SHARED EXCLUSIVE NONE - TTL TTL_ENABLE REMOVE CACHE NOCACHE + TTL TTL_ENABLE TTL_JOB_INTERVAL REMOVE CACHE NOCACHE %token FUNCTION PROCEDURE LANGUAGE RETURNS OUT INOUT BEGIN COMMENT %token EXTENSION SCHEMA VERSION %token SECOND_MICROSECOND MINUTE_MICROSECOND MINUTE_SECOND @@ -442,6 +442,7 @@ alter_option: ttl_option: TTL EQUAL col=ident PLUS INTERVAL n=INTEGER unit=INTERVAL_UNIT { `TtlSet (col, n, unit) } | TTL_ENABLE EQUAL v=TEXT { `TtlEnable v } + | TTL_JOB_INTERVAL EQUAL v=TEXT { `TtlJobInterval v } index_or_key: INDEX | KEY { } index_type: | index_or_key { Sql.Plain_idx } diff --git a/lib/syntax.ml b/lib/syntax.ml index a78e9381..65d40bcb 100644 --- a/lib/syntax.ml +++ b/lib/syntax.ml @@ -1862,20 +1862,26 @@ let rec eval (stmt:Sql.stmt) = Tables.index_rename name ~old_name ~new_name | `AddConstraint _ | `DropConstraint _ | `Cache _ | `NoCache _ -> () | `TtlOptions (opts, _) -> - let expr, enabled = - List.fold_left (fun (expr, enabled) -> function - | `TtlSet (col, n, unit) -> Some (col, n, String.uppercase_ascii unit), enabled - | `TtlEnable v -> expr, Some (String.uppercase_ascii v <> "OFF")) - (None, None) opts + let expr, enabled, job_interval = + List.fold_left (fun (expr, enabled, job_interval) -> function + | `TtlSet (col, n, unit) -> Some (col, n, String.uppercase_ascii unit), enabled, job_interval + | `TtlEnable v -> expr, Some (String.uppercase_ascii v <> "OFF"), job_interval + | `TtlJobInterval v -> expr, enabled, Some v) + (None, None, None) opts in let prev = Tables.get_ttl name in let ttl_enabled = Option.default (Option.map_default (fun (t : Tables.table_ttl) -> t.ttl_enabled) true prev) enabled in + let ttl_job_interval = + match job_interval with + | Some _ -> job_interval + | None -> Option.map_default (fun (t : Tables.table_ttl) -> t.ttl_job_interval) None prev + in Tables.set_ttl name @@ Option.map_default - (fun (ttl_col, ttl_n, ttl_unit) -> Some { Tables.ttl_col; ttl_n; ttl_unit; ttl_enabled }) - (Option.map (fun (t : Tables.table_ttl) -> { t with ttl_enabled }) prev) + (fun (ttl_col, ttl_n, ttl_unit) -> Some { Tables.ttl_col; ttl_n; ttl_unit; ttl_enabled; ttl_job_interval }) + (Option.map (fun (t : Tables.table_ttl) -> { t with ttl_enabled; ttl_job_interval }) prev) expr | `RemoveTtl _ -> Tables.set_ttl name None | `Default_or_convert_to (cs, collation) -> diff --git a/lib/tables.ml b/lib/tables.ml index 764bda47..5f2c2424 100644 --- a/lib/tables.ml +++ b/lib/tables.ml @@ -14,7 +14,7 @@ type table = Sql.table type table_charset = { charset : Sql.charset_name; collation : string option } -type table_ttl = { ttl_col : string; ttl_n : int; ttl_unit : string; ttl_enabled : bool } +type table_ttl = { ttl_col : string; ttl_n : int; ttl_unit : string; ttl_enabled : bool; ttl_job_interval : string option } module SMap = Map.Make(String) diff --git a/src/gen_migrations.ml b/src/gen_migrations.ml index 52322be6..41bf638b 100644 --- a/src/gen_migrations.ml +++ b/src/gen_migrations.ml @@ -156,7 +156,8 @@ let action_to_sql_fragment ~default_sql_lookup (action : Sql.alter_action) = mat |> List.map (function | `TtlSet (col, n, unit) -> sprintf "TTL = %s + INTERVAL %d %s" (quote_id col) n (String.uppercase_ascii unit) - | `TtlEnable v -> sprintf "TTL_ENABLE = '%s'" v) + | `TtlEnable v -> sprintf "TTL_ENABLE = '%s'" v + | `TtlJobInterval v -> sprintf "TTL_JOB_INTERVAL = '%s'" v) |> String.concat " " | `RemoveTtl _ -> "REMOVE TTL" | `Cache _ -> "CACHE" diff --git a/src/schema_diff.ml b/src/schema_diff.ml index deb5c2f7..d562974c 100644 --- a/src/schema_diff.ml +++ b/src/schema_diff.ml @@ -191,9 +191,12 @@ let diff_charset = [`Default_or_convert_to (charset, Option.map Gen_migrations.loc collation)]) []) +let default_tidb_ttl_job_interval = "1h" + let ttl_options_of (t : Tables.table_ttl) = [ `TtlSet (t.ttl_col, t.ttl_n, t.ttl_unit); `TtlEnable (if t.ttl_enabled then "ON" else "OFF") ] + @ Option.map_default (fun v -> [`TtlJobInterval v]) [] t.ttl_job_interval let diff_ttl = diff_property (fun t -> t.Tables.tbl_ttl) @@ -201,6 +204,16 @@ let diff_ttl = [`RemoveTtl (0, 0)]) let diff_table ~from_ ~to_ = + let to_ = + match from_.Tables.tbl_ttl, to_.Tables.tbl_ttl with + | Some { ttl_job_interval = Some _; _ }, + Some ({ ttl_job_interval = None; _ } as ttl) -> + let ttl = + { ttl with ttl_job_interval = Some default_tidb_ttl_job_interval } + in + { to_ with tbl_ttl = Some ttl } + | _ -> to_ + in diff_columns ~from_ ~to_ @ diff_pk ~from_ ~to_ @ diff_indexes ~from_ ~to_ @ diff_charset ~from_ ~to_ @ diff_ttl ~from_ ~to_ @@ -313,8 +326,9 @@ let canonical ts = in let ttl_sig = Option.map_default - (fun ({ ttl_col; ttl_n; ttl_unit; ttl_enabled } : Tables.table_ttl) -> - sprintf "%s+%d %s/%s" ttl_col ttl_n ttl_unit (if ttl_enabled then "on" else "off")) + (fun ({ ttl_col; ttl_n; ttl_unit; ttl_enabled; ttl_job_interval } : Tables.table_ttl) -> + sprintf "%s+%d %s/%s/%s" ttl_col ttl_n ttl_unit (if ttl_enabled then "on" else "off") + (Option.default default_tidb_ttl_job_interval ttl_job_interval)) "" in let table_sig (t : Tables.stored_table) = diff --git a/test/cram/test.t b/test/cram/test.t index a660883f..1ddbf2dc 100644 --- a/test/cram/test.t +++ b/test/cram/test.t @@ -3665,6 +3665,22 @@ Quoted `cache` still works as an identifier: > SELECT `cache` FROM bar; > EOF +TTL_JOB_INTERVAL is accepted on TiDB, standalone or with other TTL options: + $ sqlgg -gen caml -no-header -dialect=tidb - <<'EOF' >/dev/null + > CREATE TABLE foo (id INT NOT NULL, created_at TIMESTAMP NOT NULL); + > ALTER TABLE foo TTL = `created_at` + INTERVAL 6 MONTH TTL_ENABLE = 'ON' TTL_JOB_INTERVAL = '24h'; + > ALTER TABLE foo TTL_JOB_INTERVAL = '30m'; + > EOF + +TTL_JOB_INTERVAL is rejected on non-TiDB dialects: + $ sqlgg -gen caml -no-header -dialect=mysql - <<'EOF' 2>&1 + > CREATE TABLE foo (id INT NOT NULL, created_at TIMESTAMP NOT NULL); + > ALTER TABLE foo TTL_JOB_INTERVAL = '30m'; + > EOF + Feature Ttl is not supported for dialect MySQL (supported by: TiDB) at TTL_JOB_INTERVAL = '30m' + Errors encountered, no code generated + [1] + Composite: a choice nested inside another choice — every level is wrapped independently, so precedence is protected at each depth: $ sqlgg -gen caml -params unnamed -no-header - <<'EOF' 2>&1 | grep -F 'WHERE FALSE AND' | head -1 diff --git a/test/cram/test_migrations/diff-ttl.t/run.t b/test/cram/test_migrations/diff-ttl.t/run.t index cbeedd54..82b6d88a 100644 --- a/test/cram/test_migrations/diff-ttl.t/run.t +++ b/test/cram/test_migrations/diff-ttl.t/run.t @@ -38,6 +38,22 @@ Changing the interval: the down restores the previous expression: ALTER TABLE `foo` TTL = `created_at` + INTERVAL 90 DAY TTL_ENABLE = 'ON'; ALTER TABLE `foo` TTL = `created_at` + INTERVAL 6 MONTH TTL_ENABLE = 'ON'; +Changing TTL_JOB_INTERVAL from its implicit 1h default is reversible: + + $ sqlgg -no-header -dialect tidb -diff -now 20260101000000 -gen sql -base initial.sql -base initial.ttl.sql -target initial.sql -target initial.ttl.sql -target target.ttl-job-24h.sql + -- [sqlgg] generated + -- [sqlgg] id=20260101000000_alter_foo_set_ttl + ALTER TABLE `foo` TTL = `created_at` + INTERVAL 6 MONTH TTL_ENABLE = 'ON' TTL_JOB_INTERVAL = '24h'; + ALTER TABLE `foo` TTL = `created_at` + INTERVAL 6 MONTH TTL_ENABLE = 'ON' TTL_JOB_INTERVAL = '1h'; + +Returning from an explicit interval to the implicit default also emits 1h: + + $ sqlgg -no-header -dialect tidb -diff -now 20260101000000 -gen sql -base initial.sql -base initial.ttl.sql -base target.ttl-job-24h.sql -target initial.sql -target initial.ttl.sql + -- [sqlgg] generated + -- [sqlgg] id=20260101000000_alter_foo_set_ttl + ALTER TABLE `foo` TTL = `created_at` + INTERVAL 6 MONTH TTL_ENABLE = 'ON' TTL_JOB_INTERVAL = '1h'; + ALTER TABLE `foo` TTL = `created_at` + INTERVAL 6 MONTH TTL_ENABLE = 'ON' TTL_JOB_INTERVAL = '24h'; + TTL survives schema materialization (and parses back, closing the loop): $ sqlgg -no-header -dialect tidb -gen sql -base initial.sql -base initial.ttl.sql diff --git a/test/cram/test_migrations/diff-ttl.t/target.ttl-job-24h.sql b/test/cram/test_migrations/diff-ttl.t/target.ttl-job-24h.sql new file mode 100644 index 00000000..0af0e361 --- /dev/null +++ b/test/cram/test_migrations/diff-ttl.t/target.ttl-job-24h.sql @@ -0,0 +1 @@ +ALTER TABLE foo TTL_JOB_INTERVAL = '24h';