From 1952387a0e23ad54e2ddc66b4e71598aad517ffc Mon Sep 17 00:00:00 2001 From: Simen Strand Date: Thu, 23 Jul 2026 12:13:16 +0200 Subject: [PATCH 1/3] Fix Redshift parenthesized single-column SORTKEY Signed-off-by: Simen Strand --- sqlmesh/core/engine_adapter/redshift.py | 7 ++++++- tests/core/engine_adapter/test_redshift.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/sqlmesh/core/engine_adapter/redshift.py b/sqlmesh/core/engine_adapter/redshift.py index 39453f0cd2..22dc2f9dbc 100644 --- a/sqlmesh/core/engine_adapter/redshift.py +++ b/sqlmesh/core/engine_adapter/redshift.py @@ -295,7 +295,12 @@ def _to_identifier_if_string(expression: exp.Expr) -> exp.Expr: sortkey = table_properties.get("SORTKEY") if sortkey: - sortkey_expressions = sortkey.expressions if sortkey.expressions else [sortkey] + if isinstance(sortkey, (exp.Tuple, exp.Array)): + sortkey_expressions = sortkey.expressions + elif isinstance(sortkey, exp.Paren): + sortkey_expressions = [sortkey.unnest()] + else: + sortkey_expressions = [sortkey] properties.append( exp.SortKeyProperty( this=[ diff --git a/tests/core/engine_adapter/test_redshift.py b/tests/core/engine_adapter/test_redshift.py index ddd2c7c2c8..490fd6102a 100644 --- a/tests/core/engine_adapter/test_redshift.py +++ b/tests/core/engine_adapter/test_redshift.py @@ -167,7 +167,7 @@ def test_create_table_physical_properties_from_model_definition( physical_properties ( diststyle = key, distkey = "id_file", - sortkey = "batch_time" + sortkey = ("batch_time") ) ); SELECT id_file::INT, batch_time::TIMESTAMP; From 663a0a2e34f4783297a1456694d66f76d01f0e83 Mon Sep 17 00:00:00 2001 From: Simen Strand Date: Tue, 29 Sep 2026 12:46:28 +0200 Subject: [PATCH 2/3] Test all Redshift sortkey forms in model physical_properties Parametrize the model-definition test over bare, quoted, parenthesized single-column, tuple, and array sortkey values, restoring coverage for the bare single-column form. Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: Simen Strand --- tests/core/engine_adapter/test_redshift.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/core/engine_adapter/test_redshift.py b/tests/core/engine_adapter/test_redshift.py index 490fd6102a..9a88900779 100644 --- a/tests/core/engine_adapter/test_redshift.py +++ b/tests/core/engine_adapter/test_redshift.py @@ -152,22 +152,33 @@ def test_create_table_physical_properties_with_string_columns( ] +@pytest.mark.parametrize( + "sortkey, expected_sortkey", + [ + ('"batch_time"', 'SORTKEY("batch_time")'), + ("batch_time", 'SORTKEY("batch_time")'), + ('("batch_time")', 'SORTKEY("batch_time")'), + ("(batch_time)", 'SORTKEY("batch_time")'), + ('("batch_time", "id_file")', 'SORTKEY("batch_time", "id_file")'), + ('["batch_time", "id_file"]', 'SORTKEY("batch_time", "id_file")'), + ], +) def test_create_table_physical_properties_from_model_definition( - make_mocked_engine_adapter: t.Callable, + make_mocked_engine_adapter: t.Callable, sortkey: str, expected_sortkey: str ): adapter = make_mocked_engine_adapter(RedshiftEngineAdapter) model: SqlModel = t.cast( SqlModel, load_sql_based_model( d.parse( - """ + f""" MODEL ( name test_schema.test_table, kind full, physical_properties ( diststyle = key, distkey = "id_file", - sortkey = ("batch_time") + sortkey = {sortkey} ) ); SELECT id_file::INT, batch_time::TIMESTAMP; @@ -183,7 +194,7 @@ def test_create_table_physical_properties_from_model_definition( ) assert to_sql_calls(adapter) == [ - 'CREATE TABLE IF NOT EXISTS "test_schema"."test_table" ("id_file" INTEGER, "batch_time" TIMESTAMP) DISTSTYLE KEY DISTKEY("id_file") SORTKEY("batch_time")', + f'CREATE TABLE IF NOT EXISTS "test_schema"."test_table" ("id_file" INTEGER, "batch_time" TIMESTAMP) DISTSTYLE KEY DISTKEY("id_file") {expected_sortkey}', ] From a8233c212f6624b77b639d4bc55a7bb13b9dab20 Mon Sep 17 00:00:00 2001 From: Simen Strand Date: Fri, 2 Oct 2026 11:06:42 +0200 Subject: [PATCH 3/3] Unwrap parenthesized single-column Redshift distkey A parenthesized distkey such as `distkey = (col)` rendered as `DISTKEY((col))`, which doesn't match Redshift's documented `DISTKEY ( column_name )` syntax. Unwrap the parentheses the same way the sortkey handling does, and test the bare, quoted, string and parenthesized forms in model physical_properties. Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: Simen Strand --- sqlmesh/core/engine_adapter/redshift.py | 4 ++- tests/core/engine_adapter/test_redshift.py | 38 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/sqlmesh/core/engine_adapter/redshift.py b/sqlmesh/core/engine_adapter/redshift.py index 22dc2f9dbc..8f2f098d08 100644 --- a/sqlmesh/core/engine_adapter/redshift.py +++ b/sqlmesh/core/engine_adapter/redshift.py @@ -291,7 +291,9 @@ def _to_identifier_if_string(expression: exp.Expr) -> exp.Expr: distkey = table_properties.get("DISTKEY") if distkey: - properties.append(exp.DistKeyProperty(this=_to_identifier_if_string(distkey))) + properties.append( + exp.DistKeyProperty(this=_to_identifier_if_string(distkey.unnest())) + ) sortkey = table_properties.get("SORTKEY") if sortkey: diff --git a/tests/core/engine_adapter/test_redshift.py b/tests/core/engine_adapter/test_redshift.py index 9a88900779..7211244e91 100644 --- a/tests/core/engine_adapter/test_redshift.py +++ b/tests/core/engine_adapter/test_redshift.py @@ -198,6 +198,44 @@ def test_create_table_physical_properties_from_model_definition( ] +@pytest.mark.parametrize( + "distkey", + ['"id_file"', "id_file", "'id_file'", '("id_file")', "(id_file)"], +) +def test_create_table_physical_properties_distkey_from_model_definition( + make_mocked_engine_adapter: t.Callable, distkey: str +): + adapter = make_mocked_engine_adapter(RedshiftEngineAdapter) + model: SqlModel = t.cast( + SqlModel, + load_sql_based_model( + d.parse( + f""" +MODEL ( + name test_schema.test_table, + kind full, + physical_properties ( + diststyle = key, + distkey = {distkey} + ) +); +SELECT id_file::INT; + """ + ) + ), + ) + + adapter.create_table( + model.name, + target_columns_to_types=model.columns_to_types_or_raise, + table_properties=model.physical_properties, + ) + + assert to_sql_calls(adapter) == [ + 'CREATE TABLE IF NOT EXISTS "test_schema"."test_table" ("id_file" INTEGER) DISTSTYLE KEY DISTKEY("id_file")', + ] + + def test_varchar_size_workaround(make_mocked_engine_adapter: t.Callable, mocker: MockerFixture): adapter = make_mocked_engine_adapter(RedshiftEngineAdapter)