Skip to content

fix(clickhouse): parse MODIFY COLUMN as Alter / ModifyColumn - #8020

Open
dgvj-work wants to merge 2 commits into
tobymao:mainfrom
dgvj-work:fix/clickhouse-modify-column-parse
Open

fix(clickhouse): parse MODIFY COLUMN as Alter / ModifyColumn#8020
dgvj-work wants to merge 2 commits into
tobymao:mainfrom
dgvj-work:fix/clickhouse-modify-column-parse

Conversation

@dgvj-work

@dgvj-work dgvj-work commented Aug 1, 2026

Copy link
Copy Markdown

ClickHouse ALTER TABLE ... MODIFY COLUMN was falling back to Command because the ClickHouse MODIFY alter parser only handled MODIFY SQL SECURITY ....

This parses the column form into ModifyColumn (same AST MySQL already uses), enables ClickHouse generation for it, and leaves REMOVE / column settings / ADD ENUM VALUES as Command for now.

Fixes #8019

Disclosure: used LLM assistance while investigating; I reviewed and tested the change.

Digvijay Waghela added 2 commits August 1, 2026 03:26
ClickHouse MODIFY COLUMN was falling through to Command because the
MODIFY alter parser only handled SQL SECURITY properties. Parse the
native column form into ModifyColumn and enable generation for it.

Fixes tobymao#8019
Only emit IF EXISTS for MODIFY COLUMN, and cover the REMOVE fallback.
@geooo109
geooo109 requested a review from treysp August 3, 2026 13:30
if properties := self._parse_properties():
return self.expression(exp.AlterModifySqlSecurity(expressions=properties.expressions))
return None

@treysp treysp Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR!

This kind of a confusing situation, given the differences in behavior across dialects and current implementation.

Existing behavior

The existing implementation is exclusively intended to support ALTER TABLE MODIFY SQL SECURITY.

It's parsed by _parse_alter_table_modify, which was ok before but is a return type issue now that we're expanding the coverage.

Return exp type

In Clickhouse, ALTER COLUMN and MODIFY COLUMN are semantically equivalent, so we should return an AlterColumn exp here. Snowflake has the same semantics - you can see it mapping keyword MODIFY to the ALTER parser here.

(Only in MySQL do ALTER COLUMN and MODIFY COLUMN have different semantics, which is why the ModifyColumn exp class exists.)

Different approach

Let's switch approaches to override the base _parse_alter_table_alter.

Note that this means sqlglot will canonicalize MODIFY COLUMN to ALTER COLUMN during generation.


ALTER_PARSERS = {
**parser.Parser.ALTER_PARSERS,
"MODIFY": lambda self: self._parse_alter_table_modify(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"MODIFY": lambda self: self._parse_alter_table_alter(),

@@ -911,7 +911,29 @@
def _parse_alter_table_modify(self) -> exp.Expr | None:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def _parse_alter_table_modify(self) -> exp.Expr | None:
def _parse_alter_table_alter(self) -> exp.Expr | None:

@@ -911,7 +911,29 @@
def _parse_alter_table_modify(self) -> exp.Expr | None:
if properties := self._parse_properties():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gate this to MODIFY SQL SECURITY since ALTER can now get here

        if self._prev.text.upper() == "MODIFY" and self._match(
            TokenType.SQL_SECURITY, advance=False
        ):
            if properties := self._parse_properties():
                return self.expression(
                    exp.AlterModifySqlSecurity(expressions=properties.expressions)
                )

Comment on lines +915 to +936
# https://clickhouse.com/docs/sql-reference/statements/alter/column#modify-column
if not self._match(TokenType.COLUMN):
return None

exists = self._parse_exists()
column = self._parse_field(any_token=True)
if column is None:
return None

# REMOVE / MODIFY SETTING / RESET SETTING / ADD ENUM VALUES stay as Command
if (
self._match_texts(("REMOVE", "RESET"), advance=False)
or self._match_text_seq("MODIFY", "SETTING", advance=False)
or self._match_text_seq("ADD", "ENUM", advance=False)
):
return None

column_def = self._parse_column_def(column)
if not isinstance(column_def, exp.ColumnDef):
return None

return self.expression(exp.ModifyColumn(this=column_def, exists=exists or None))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove and rely on base parser - the return has it fall back to Command if there are still other tokens left to consume (like SETTINGS).

        alter = super()._parse_alter_table_alter()
        return None if self._curr else alter


class ModifyColumn(Expression):
arg_types = {"this": True, "rename_from": False}
arg_types = {"this": True, "rename_from": False, "exists": False}

@treysp treysp Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Move arg to AlterColumn class above
  2. Add IF EXISTS parsing to base _parse_alter_table_alter
  3. Add SUPPORTS_ALTER_COLUMN_IF_EXISTS=False to base generator
  4. Add SUPPORTS_ALTER_COLUMN_IF_EXISTS=True to Clickhouse generator
  5. Add generation to base generator, gated by SUPPORTS_ALTER_COLUMN_IF_EXISTS
  6. Annotate other dialects' altercolumn_sql overrides with @unsupported_args("exists")

STRUCT_DELIMITER = ("(", ")")
NVL2_SUPPORTED = False
ALTER_SET_TYPE = "TYPE"
SUPPORTS_MODIFY_COLUMN = True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

Comment thread sqlglot/generator.py
if not self.SUPPORTS_MODIFY_COLUMN:
self.unsupported("MODIFY COLUMN is not supported in this dialect")
return f"MODIFY COLUMN {this}"
exists = " IF EXISTS" if expression.args.get("exists") else ""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to altercolumn_sql

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ClickHouse MODIFY COLUMN falls back to Command instead of Alter

2 participants