Skip to content

fix(generator): don't crash on non-identifier struct keys [DEEPSEEK] - #8768

Closed
simpleqt wants to merge 1 commit into
tobymao:mainfrom
simpleqt:fix/struct-non-identifier-keys
Closed

simpleqt wants to merge 1 commit into
tobymao:mainfrom
simpleqt:fix/struct-non-identifier-keys

Conversation

@simpleqt

Copy link
Copy Markdown
Contributor

Generator.struct_sql hands every struct key to exp.alias_, which only accepts a string or an
Identifier, so generating a struct whose keys are expressions raises:

>>> sqlglot.transpile("SELECT OBJECT_CONSTRUCT(1, 2) FROM x", read="snowflake", write="bigquery")
ValueError: Name needs to be a string or an Identifier, got: <class 'sqlglot.expressions.Literal'>

Snowflake allows arbitrary expressions as OBJECT_CONSTRUCT keys, and sqlglot itself produces that
input: MAP(ARRAY(1), ARRAY(2)) in snowflake round-trips to OBJECT_CONSTRUCT(1, 2), which then
cannot be transpiled to bigquery, postgres, spark or the default dialect. It reproduces on 30.24.0
too, so this is long-standing rather than a new regression. The DuckDB generator already handles such
keys, which is where the shape of the fix comes from.

The fix keeps the existing string-literal and identifier branches and uses
exp.to_identifier(self.sql(key)) as the field name for any other key: OBJECT_CONSTRUCT(1, 2)
becomes STRUCT(2 AS `1`) for bigquery/spark and STRUCT(2 AS "1") for postgres/default, with
duckdb and snowflake output unchanged.

Tests: tests/dialects/test_snowflake.py gains two validate_all cases (a literal key and a column
key across bigquery/duckdb/postgres/spark/snowflake/default) next to the existing
OBJECT_CONSTRUCT(a, b, c, d) test — 8 failures in that file before the fix, 107 passed / 1641
subtests after, and the full suite is 1416 passed / 22121 subtests. ruff check, ruff format --check and mypy sqlglot/generator.py are clean.

Disclosure: prepared with an AI coding agent (DeepSeek); the repro, the red/green runs and the final
diff were reviewed before opening this.

Generator.struct_sql passed every PropertyEQ key to exp.alias_, which only
accepts a string or an Identifier, so generating a struct whose keys are
expressions raised ValueError. Snowflake allows arbitrary OBJECT_CONSTRUCT key
expressions and sqlglot itself emits OBJECT_CONSTRUCT(1, 2) for
MAP(ARRAY(1), ARRAY(2)), which then failed to transpile to bigquery, postgres,
spark and the default dialect. Other keys now become the field name through
exp.to_identifier(self.sql(key)), as the DuckDB generator already does.

[DEEPSEEK]
Copilot AI balanced review requested due to automatic review settings October 23, 2026 17:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@treysp

treysp commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

PR motivating claim "Snowflake allows arbitrary expressions as OBJECT_CONSTRUCT keys" is not correct

Syntax
OBJECT_CONSTRUCT( [<key>, <value> [, <key>, <value> , ...]] )

OBJECT_CONSTRUCT(*)
Arguments
key
The key in a key-value pair. Each key is a VARCHAR value.

https://docs.snowflake.com/en/sql-reference/functions/object_construct

@treysp treysp closed this Oct 23, 2026
@simpleqt

Copy link
Copy Markdown
Contributor Author

You are right, and thanks for the correction - Snowflake keys are VARCHAR values, so OBJECT_CONSTRUCT(1, 2) is not valid Snowflake input and the PR's framing was wrong. Closing it is fair.

The one part that still reproduces from valid input is on sqlglot's own side: SELECT MAP(ARRAY(1), ARRAY(2)) FROM x read and written as snowflake produces SELECT OBJECT_CONSTRUCT(1, 2) FROM x, so the snowflake generator emits a form that the other generators cannot then turn into SQL (ValueError: Name needs to be a string or an Identifier), and duckdb's {1: 2} maps to the same shape. I am not asking you to reopen anything - just noting where the crash comes from in case that is worth an issue on your side.

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.

3 participants