Skip to content

Conversation

@themisvaltinos
Copy link
Contributor

This pr : #4847 along with the columns added normalisation for MERGE_SOURCE_ALIAS and MERGE_TARGET_ALIAS constants that since are capitalised in the merge statement are mismatched and lead to error when executing the merge for dialects like Postgres so we'd get:

MERGE INTO table AS "__MERGE_TARGET__" USING (
SELECT ... AS "__MERGE_SOURCE__" ON 
"__MERGE_TARGET__"."id" = "__MERGE_SOURCE__"."id" 
WHEN MATCHED THEN UPDATE SET "id" = "__merge_source__"."id"  ... ) # this will be set to lowercase due to normalisation

instead of:

MERGE INTO table AS "__MERGE_TARGET__" USING (
SELECT ... AS "__MERGE_SOURCE__" ON 
"__MERGE_TARGET__"."id" = "__MERGE_SOURCE__"."id" 
WHEN MATCHED THEN UPDATE SET "id" = "__MERGE_SOURCE__"."id"  ... )

This pr leaves the normalisation logic, but reverts to the previous behaviour for the aliases.

@georgesittas
Copy link
Contributor

Does this require a migration?

@themisvaltinos
Copy link
Contributor Author

Does this require a migration?

if I’m not missing anything im not sure it is needed, because the merge into would fail unless the engine is one with uppercase like Snowflake and in that case nothing would change in state regarding the aliases. my thinking is if a when matched was in state and the dialect was case-insensitive but not uppercase (like Postgres) it simply wouldn't work as the alias would be lowercase causing sqlmesh run to fail

if isinstance(expression, exp.Column) and (first_part := expression.parts[0]):
if first_part.this.lower() in ("target", "dbt_internal_dest", "__merge_target__"):
first_part.replace(normalized_merge_target_alias)
first_part.replace(exp.to_identifier(MERGE_TARGET_ALIAS, quoted=True))
Copy link
Collaborator

Choose a reason for hiding this comment

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

It took me some time to grok this but I see why it works. quoted=True is the key part to prevent it being un-done later.

This essentially ensures that everything is uppercase regardless of the engine's normalization strategy, which lines up with what EngineAdapter._merge uses.

EngineAdapter._merge produces an unquoted uppercase value, which gets quoted by default during EngineAdapter.execute, which is why it needs to either be uppercase here or normalized in EngineAdapter._merge

branches:
only:
- main
# filters:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Todo: to reinstate prior to merge

@themisvaltinos themisvaltinos force-pushed the themis/when_match branch 2 times, most recently from 453ecde to fbbaeb0 Compare August 5, 2025 16:44
@themisvaltinos
Copy link
Contributor Author

I've also added an integration test for this, if you want to have another look @erindru

@themisvaltinos themisvaltinos requested a review from erindru August 5, 2025 18:16
Copy link
Collaborator

@erindru erindru left a comment

Choose a reason for hiding this comment

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

I think that test failure will disappear if you rebase from main

pytest.skip(f"{ctx.dialect} on {ctx.gateway} doesnt support merge")

# DuckDB and some other engines use logical_merge which doesn't support when_matched
if ctx.dialect not in ["bigquery", "databricks", "postgres", "snowflake", "spark"]:
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: would a more robust test check if ctx.engine_adapter is an instance of LogicalMergeMixin instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes good point and much cleaner this way revised it


return validate_expression(v, dialect=dialect)
v = validate_expression(v, dialect=dialect)
return t.cast(exp.Whens, v.transform(d.replace_merge_table_aliases, dialect=dialect))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Previously we only ran the replace_merge_table_aliases transform when loading from disk. When reading back from state, we assumed it had already been applied so didn't apply it again.

However, applying it regardless like you do here will transparently "fix" what was in state, right? So that when people upgrade SQLMesh, everything should still match

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes that was my thinking behind it to foolproof it

@themisvaltinos themisvaltinos merged commit adf6a68 into main Aug 6, 2025
27 checks passed
@themisvaltinos themisvaltinos deleted the themis/when_match branch August 6, 2025 10:00
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.

4 participants