Skip to content

Commit b1e3086

Browse files
committed
Feat: Introduce runtime check for identifier limits per engine
1 parent 06a51dc commit b1e3086

4 files changed

Lines changed: 39 additions & 8 deletions

File tree

sqlmesh/core/engine_adapter/base.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ class EngineAdapter:
106106
SUPPORTS_REPLACE_TABLE = True
107107
DEFAULT_CATALOG_TYPE = DIALECT
108108
QUOTE_IDENTIFIERS_IN_VIEWS = True
109+
MAX_IDENTIFIER_LENGTH: t.Optional[int] = None
109110

110111
def __init__(
111112
self,
@@ -2138,14 +2139,12 @@ def execute(
21382139
)
21392140
with self.transaction():
21402141
for e in ensure_list(expressions):
2141-
sql = t.cast(
2142-
str,
2143-
(
2144-
self._to_sql(e, quote=quote_identifiers, **to_sql_kwargs)
2145-
if isinstance(e, exp.Expression)
2146-
else e
2147-
),
2148-
)
2142+
if isinstance(e, exp.Expression):
2143+
self._check_identifier_length(e, check_only_ddl=True)
2144+
sql = self._to_sql(e, quote=quote_identifiers, **to_sql_kwargs)
2145+
else:
2146+
sql = t.cast(str, e)
2147+
21492148
self._log_sql(
21502149
sql,
21512150
expression=e if isinstance(e, exp.Expression) else None,
@@ -2516,6 +2515,22 @@ def ping(self) -> None:
25162515
def _select_columns(cls, columns: t.Iterable[str]) -> exp.Select:
25172516
return exp.select(*(exp.column(c, quoted=True) for c in columns))
25182517

2518+
def _check_identifier_length(
2519+
self, expression: exp.Expression, check_only_ddl: bool = True
2520+
) -> None:
2521+
if self.MAX_IDENTIFIER_LENGTH is None or (
2522+
check_only_ddl and not isinstance(expression, exp.DDL)
2523+
):
2524+
return
2525+
2526+
for identifier in expression.find_all(exp.Identifier):
2527+
name = identifier.name
2528+
name_length = len(name)
2529+
if name_length > self.MAX_IDENTIFIER_LENGTH:
2530+
raise SQLMeshError(
2531+
f"Identifier name {name} (length {name_length}) exceeds {self.dialect.capitalize()}'s max identifier limit of {self.MAX_IDENTIFIER_LENGTH} characters"
2532+
)
2533+
25192534

25202535
class EngineAdapterWithIndexSupport(EngineAdapter):
25212536
SUPPORTS_INDEXES = True

sqlmesh/core/engine_adapter/mysql.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class MySQLEngineAdapter(
3939
MAX_TABLE_COMMENT_LENGTH = 2048
4040
MAX_COLUMN_COMMENT_LENGTH = 1024
4141
SUPPORTS_REPLACE_TABLE = False
42+
MAX_IDENTIFIER_LENGTH = 64
4243
SCHEMA_DIFFER = SchemaDiffer(
4344
parameterized_type_defaults={
4445
exp.DataType.build("BIT", dialect=DIALECT).this: [(1,)],

sqlmesh/core/engine_adapter/postgres.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class PostgresEngineAdapter(
3434
HAS_VIEW_BINDING = True
3535
CURRENT_CATALOG_EXPRESSION = exp.column("current_catalog")
3636
SUPPORTS_REPLACE_TABLE = False
37+
MAX_IDENTIFIER_LENGTH = 63
3738
SCHEMA_DIFFER = SchemaDiffer(
3839
parameterized_type_defaults={
3940
# DECIMAL without precision is "up to 131072 digits before the decimal point; up to 16383 digits after the decimal point"

tests/core/engine_adapter/integration/test_integration.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2652,3 +2652,17 @@ def execute(
26522652
{"id": 1, "name": "foo"} if ctx.dialect != "snowflake" else {"ID": 1, "NAME": "foo"}
26532653
)
26542654
assert df.iloc[0].to_dict() == expected_result
2655+
2656+
2657+
def test_identifier_length_limit(ctx: TestContext):
2658+
adapter = ctx.engine_adapter
2659+
if adapter.MAX_IDENTIFIER_LENGTH is None:
2660+
pytest.skip(f"Engine {adapter.dialect} does not have identifier length limits set.")
2661+
2662+
long_table_name = "a" * (adapter.MAX_IDENTIFIER_LENGTH + 1)
2663+
2664+
with pytest.raises(
2665+
SQLMeshError,
2666+
match=f"Identifier name {long_table_name} (length {len(long_table_name)}) exceeds {adapter.dialect.capitalize()}'s max identifier limit of {adapter.MAX_IDENTIFIER_LENGTH} characters",
2667+
):
2668+
adapter.create_table(long_table_name, {"col": exp.DataType.build("int")})

0 commit comments

Comments
 (0)