Skip to content

Commit d75eac1

Browse files
committed
Add support for PostgreSQL-style ALTER COLUMN syntax
PostgreSQL (and standard SQL) uses `ALTER COLUMN` instead of MySQL's `CHANGE COLUMN` / `MODIFY COLUMN` for modifying existing columns. This adds parsing and schema evaluation for the five main sub-commands: - `ALTER COLUMN col TYPE newtype` - `ALTER COLUMN col SET NOT NULL` - `ALTER COLUMN col DROP NOT NULL` - `ALTER COLUMN col SET DEFAULT expr` - `ALTER COLUMN col DROP DEFAULT` These can be combined with other actions in a single `ALTER TABLE` statement, eg.: ``` ALTER TABLE t DROP COLUMN old_col, ALTER COLUMN col TYPE SMALLINT, ALTER COLUMN col SET NOT NULL; ``` Migration generation (inverse actions + SQL fragments) is also supported for all five variants.
1 parent 16813a2 commit d75eac1

9 files changed

Lines changed: 137 additions & 2 deletions

File tree

lib/dialect.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,8 @@ and analyze_alter_action acc actions k = match actions with
394394
| `Default_or_convert_to (_, collation) ->
395395
let acc = check_collation_opt collation @ acc in
396396
analyze_alter_action acc rest k
397-
| `Drop _ | `RenameTable _ | `RenameColumn _ | `RenameIndex _ | `AddIndex _ | `DropIndex _ | `AddPrimaryKey _ | `DropPrimaryKey | `AddConstraint _ | `DropConstraint _ | `None ->
397+
| `Drop _ | `RenameTable _ | `RenameColumn _ | `RenameIndex _ | `AddIndex _ | `DropIndex _ | `AddPrimaryKey _ | `DropPrimaryKey | `AddConstraint _ | `DropConstraint _ | `None
398+
| `AlterColumnType _ | `AlterColumnSetNotNull _ | `AlterColumnDropNotNull _ | `AlterColumnSetDefault _ | `AlterColumnDropDefault _ ->
398399

399400
analyze_alter_action acc rest k
400401

lib/sql.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,11 @@ type alter_action = [
817817
| `AddConstraint of string option
818818
| `DropConstraint of string
819819
| `Default_or_convert_to of (charset_name option * string located option)
820+
| `AlterColumnType of string * Source_type.kind collated located
821+
| `AlterColumnSetNotNull of string
822+
| `AlterColumnDropNotNull of string
823+
| `AlterColumnSetDefault of string * expr located
824+
| `AlterColumnDropDefault of string
820825
| `None ] [@@deriving show {with_path=false}]
821826

822827
type stmt =

lib/sql_lexer.mll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ let keywords =
194194
"algorithm", ALGORITHM;
195195
"copy", COPY;
196196
"recursive", RECURSIVE;
197+
"type", TYPE;
197198
] in (* more *)
198199
k := !k @ List.map (fun s -> s, INTERVAL_UNIT s) [ "microsecond"; "second"; "minute"; "hour"; "day"; "week"; "month"; "quarter"; "year" ];
199200
let all token l = k := !k @ List.map (fun x -> x,token) l in

lib/sql_parser.mly

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
CONCAT_OP LEFT RIGHT FULL INNER OUTER NATURAL CROSS REPLACE IN GROUP HAVING
3737
UNIQUE PRIMARY KEY FOREIGN AUTOINCREMENT ON CONFLICT DO NOTHING TEMPORARY IF EXISTS
3838
PRECISION SIGNED UNSIGNED ZEROFILL VARYING CHARSET NATIONAL ASCII UNICODE COLLATE BINARY CHARACTER
39-
DATETIME_FUNC DATE TIME TIMESTAMP ALTER RENAME ADD COLUMN CASCADE RESTRICT DROP
39+
DATETIME_FUNC DATE TIME TIMESTAMP ALTER RENAME ADD COLUMN CASCADE RESTRICT DROP TYPE
4040
GLOBAL LOCAL REFERENCES CHECK CONSTRAINT IGNORED AFTER INDEX FULLTEXT SPATIAL FIRST
4141
CASE WHEN THEN ELSE END CHANGE MODIFY DELAYED ENUM FOR SHARE MODE LOCK
4242
OF WITH NOWAIT ACTION NO IS INTERVAL SUBSTRING DIV MOD CONVERT LAG LEAD OVER
@@ -366,6 +366,11 @@ alter_action: ADD COLUMN? col=maybe_parenth(column_def) pos=alter_pos { `Add (co
366366
| DROP CHECK name=IDENT { `DropConstraint name }
367367
| CHANGE COLUMN? old_name=IDENT column=column_def pos=alter_pos { `Change (old_name,column,pos) }
368368
| MODIFY COLUMN? column=column_def pos=alter_pos { `Change (column.Alter_action_attr.name,column,pos) }
369+
| ALTER COLUMN? col=IDENT TYPE t=located_sql_type { `AlterColumnType (col, t) }
370+
| ALTER COLUMN? col=IDENT SET NOT NULL { `AlterColumnSetNotNull col }
371+
| ALTER COLUMN? col=IDENT DROP NOT NULL { `AlterColumnDropNotNull col }
372+
| ALTER COLUMN? col=IDENT SET DEFAULT e=default_value { `AlterColumnSetDefault (col, make_located ~value:e ~pos:($startofs, $endofs)) }
373+
| ALTER COLUMN? col=IDENT DROP DEFAULT { `AlterColumnDropDefault col }
369374
| SET IDENT IDENT { `None }
370375
| ALGORITHM EQUAL algorithm { `None }
371376
| LOCK EQUAL lock { `None }

lib/syntax.ml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,6 +1466,16 @@ let rec eval (stmt:Sql.stmt) =
14661466
Tables.drop_primary_key name
14671467
| `AddPrimaryKey cols ->
14681468
Tables.add_primary_key name ~cols
1469+
| `AlterColumnType (col, new_type) ->
1470+
Tables.alter_column_type name ~col_name:col ~new_kind:new_type.value.collated
1471+
| `AlterColumnSetNotNull col ->
1472+
Tables.alter_column_set_not_null name ~col_name:col
1473+
| `AlterColumnDropNotNull col ->
1474+
Tables.alter_column_drop_not_null name ~col_name:col
1475+
| `AlterColumnSetDefault (col, _) ->
1476+
Tables.alter_column_set_default name ~col_name:col
1477+
| `AlterColumnDropDefault col ->
1478+
Tables.alter_column_drop_default name ~col_name:col
14691479
| `RenameIndex _ | `AddIndex _ | `DropIndex _ | `AddConstraint _ | `DropConstraint _ -> () (* indices are not tracked yet *)
14701480
| `Default_or_convert_to (cs, collation) ->
14711481
let old = Tables.get_charset name in

lib/tables.ml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,52 @@ let add_primary_key name ~cols:col_names =
124124
{ c with attr = { c.attr with extra = Sql.Constraints.add pk c.attr.Sql.extra } }
125125
else c))
126126

127+
let alter_column_type name ~col_name ~new_kind =
128+
alter name (fun cols ->
129+
List.map (fun c ->
130+
if c.attr.Sql.name = col_name then
131+
let new_t = Sql.Alter_action_attr.kind_to_type_kind new_kind in
132+
{ attr = { c.attr with domain = { c.attr.domain with Sql.Type.t = new_t } };
133+
source_kind = Some new_kind }
134+
else c
135+
) cols)
136+
137+
let alter_column_set_not_null name ~col_name =
138+
alter name (fun cols ->
139+
List.map (fun c ->
140+
if c.attr.Sql.name = col_name then
141+
{ c with attr = { c.attr with
142+
domain = { c.attr.domain with Sql.Type.nullability = Strict };
143+
extra = Sql.Constraints.add NotNull c.attr.extra } }
144+
else c
145+
) cols)
146+
147+
let alter_column_drop_not_null name ~col_name =
148+
alter name (fun cols ->
149+
List.map (fun c ->
150+
if c.attr.Sql.name = col_name then
151+
{ c with attr = { c.attr with
152+
domain = { c.attr.domain with Sql.Type.nullability = Nullable };
153+
extra = Sql.Constraints.remove NotNull c.attr.extra } }
154+
else c
155+
) cols)
156+
157+
let alter_column_set_default name ~col_name =
158+
alter name (fun cols ->
159+
List.map (fun c ->
160+
if c.attr.Sql.name = col_name then
161+
{ c with attr = { c.attr with extra = Sql.Constraints.add WithDefault c.attr.extra } }
162+
else c
163+
) cols)
164+
165+
let alter_column_drop_default name ~col_name =
166+
alter name (fun cols ->
167+
List.map (fun c ->
168+
if c.attr.Sql.name = col_name then
169+
{ c with attr = { c.attr with extra = Sql.Constraints.remove WithDefault c.attr.extra } }
170+
else c
171+
) cols)
172+
127173
let print ch tables = let out = IO.output_channel ch in List.iter (Sql.print_table out) tables; IO.flush out
128174
let print_all () = print stdout (List.map (fun (n, cols) -> (n, columns_to_schema cols)) !store)
129175
let print1 name = print stdout [get @@ Sql.make_table_name name]

src/gen_migrations.ml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,28 @@ let inverse_action table_name (columns : Tables.column list) (action : Sql.alter
111111
| None -> None, None
112112
in
113113
`Default_or_convert_to (cs, collation)
114+
| `AlterColumnType (col_name, _) ->
115+
let entry = find_column columns col_name in
116+
let old_type = Sql.Alter_action_attr.from_attr entry.attr |> enrich_with_source_kind entry.source_kind in
117+
let old_kind = match old_type.Sql.Alter_action_attr.kind with
118+
| Some k -> k
119+
| None -> Sql.make_located ~pos:(0,0) ~value:(Sql.make_collated ~collated:(Sql.Source_type.Infer entry.attr.domain.Sql.Type.t) ())
120+
in
121+
`AlterColumnType (col_name, old_kind)
122+
| `AlterColumnSetNotNull col_name -> `AlterColumnDropNotNull col_name
123+
| `AlterColumnDropNotNull col_name -> `AlterColumnSetNotNull col_name
124+
| `AlterColumnSetDefault (col_name, _) -> `AlterColumnDropDefault col_name
125+
| `AlterColumnDropDefault col_name ->
126+
if Sql.Constraints.mem WithDefault (find_column columns col_name).attr.extra then
127+
let entry = find_column columns col_name in
128+
let col = Sql.Alter_action_attr.from_attr entry.attr in
129+
let default_expr = List.find_map (fun (c : Sql.Alter_action_attr.constraint_ Sql.located) ->
130+
match c.value with Sql.Alter_action_attr.Default e -> Some e | _ -> None
131+
) col.extra in
132+
match default_expr with
133+
| Some e -> `AlterColumnSetDefault (col_name, e)
134+
| None -> `None
135+
else `None
114136
| `None -> `None
115137

116138
let action_to_sql_fragment (action : Sql.alter_action) =
@@ -156,6 +178,16 @@ let action_to_sql_fragment (action : Sql.alter_action) =
156178
| [] -> "(* unsupported: unknown previous charset *)"
157179
| _ -> String.concat " " parts
158180
end
181+
| `AlterColumnType (col_name, new_type) ->
182+
sprintf "ALTER COLUMN %s TYPE %s" (quote_id col_name) (source_type_kind_to_sql new_type.Sql.value.collated)
183+
| `AlterColumnSetNotNull col_name ->
184+
sprintf "ALTER COLUMN %s SET NOT NULL" (quote_id col_name)
185+
| `AlterColumnDropNotNull col_name ->
186+
sprintf "ALTER COLUMN %s DROP NOT NULL" (quote_id col_name)
187+
| `AlterColumnSetDefault (col_name, _) ->
188+
sprintf "ALTER COLUMN %s SET DEFAULT (* unknown *)" (quote_id col_name)
189+
| `AlterColumnDropDefault col_name ->
190+
sprintf "ALTER COLUMN %s DROP DEFAULT" (quote_id col_name)
159191
| `None -> "(* unsupported: index/constraint operation *)"
160192

161193
let alter_to_sql table_name actions =

test/alter.sql

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,19 @@ CREATE INDEX `foo_unique` ON `foo` (`col1`, `col2`, `col3`);
88
ALTER TABLE `foo`
99
DROP INDEX `foo_unique`,
1010
ADD UNIQUE `foo_unique` (`col1`, `col3`);
11+
12+
CREATE TABLE "bar" (
13+
"id" INTEGER NOT NULL,
14+
"role" SMALLINT NOT NULL,
15+
"role_new" INTEGER NOT NULL,
16+
"omniscience" INTEGER NOT NULL
17+
);
18+
19+
ALTER TABLE "bar"
20+
DROP COLUMN "role",
21+
ALTER COLUMN "role_new" TYPE SMALLINT,
22+
ALTER COLUMN "role_new" SET NOT NULL,
23+
ALTER COLUMN "omniscience" TYPE BOOLEAN,
24+
ALTER COLUMN "omniscience" SET NOT NULL;
25+
26+
ALTER TABLE "bar" RENAME COLUMN "role_new" TO "role";

test/out/alter.xml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,25 @@
1313
<in/>
1414
<out/>
1515
</stmt>
16+
<stmt name="create_bar" sql="CREATE TABLE &quot;bar&quot; (&#x0A; &quot;id&quot; INTEGER NOT NULL,&#x0A; &quot;role&quot; SMALLINT NOT NULL,&#x0A; &quot;role_new&quot; INTEGER NOT NULL,&#x0A; &quot;omniscience&quot; INTEGER NOT NULL&#x0A;)" category="DDL" kind="create" target="bar" cardinality="0">
17+
<in/>
18+
<out/>
19+
</stmt>
20+
<stmt name="alter_bar_4" sql="ALTER TABLE &quot;bar&quot;&#x0A; DROP COLUMN &quot;role&quot;,&#x0A; ALTER COLUMN &quot;role_new&quot; TYPE SMALLINT,&#x0A; ALTER COLUMN &quot;role_new&quot; SET NOT NULL,&#x0A; ALTER COLUMN &quot;omniscience&quot; TYPE BOOLEAN,&#x0A; ALTER COLUMN &quot;omniscience&quot; SET NOT NULL" category="DDL" kind="alter" target="bar" cardinality="0">
21+
<in/>
22+
<out/>
23+
</stmt>
24+
<stmt name="alter_bar_5" sql="ALTER TABLE &quot;bar&quot; RENAME COLUMN &quot;role_new&quot; TO &quot;role&quot;" category="DDL" kind="alter" target="bar" cardinality="0">
25+
<in/>
26+
<out/>
27+
</stmt>
28+
<table name="bar">
29+
<schema>
30+
<value name="id" type="Int"/>
31+
<value name="role" type="Int"/>
32+
<value name="omniscience" type="Bool"/>
33+
</schema>
34+
</table>
1635
<table name="foo">
1736
<schema>
1837
<value name="col1" type="Int"/>

0 commit comments

Comments
 (0)