Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions sqlmesh/core/engine_adapter/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ class EngineAdapter:
SUPPORTS_REPLACE_TABLE = True
DEFAULT_CATALOG_TYPE = DIALECT
QUOTE_IDENTIFIERS_IN_VIEWS = True
MAX_IDENTIFIER_LENGTH: t.Optional[int] = None

def __init__(
self,
Expand Down Expand Up @@ -2138,14 +2139,12 @@ def execute(
)
with self.transaction():
for e in ensure_list(expressions):
sql = t.cast(
str,
(
self._to_sql(e, quote=quote_identifiers, **to_sql_kwargs)
if isinstance(e, exp.Expression)
else e
),
)
if isinstance(e, exp.Expression):
self._check_identifier_length(e)
sql = self._to_sql(e, quote=quote_identifiers, **to_sql_kwargs)
else:
sql = t.cast(str, e)

self._log_sql(
sql,
expression=e if isinstance(e, exp.Expression) else None,
Expand Down Expand Up @@ -2516,6 +2515,18 @@ def ping(self) -> None:
def _select_columns(cls, columns: t.Iterable[str]) -> exp.Select:
return exp.select(*(exp.column(c, quoted=True) for c in columns))

def _check_identifier_length(self, expression: exp.Expression) -> None:
if self.MAX_IDENTIFIER_LENGTH is None or not isinstance(expression, exp.DDL):
return

for identifier in expression.find_all(exp.Identifier):
name = identifier.name
name_length = len(name)
if name_length > self.MAX_IDENTIFIER_LENGTH:
raise SQLMeshError(
f"Identifier name '{name}' (length {name_length}) exceeds {self.dialect.capitalize()}'s max identifier limit of {self.MAX_IDENTIFIER_LENGTH} characters"
)


class EngineAdapterWithIndexSupport(EngineAdapter):
SUPPORTS_INDEXES = True
Expand Down
1 change: 1 addition & 0 deletions sqlmesh/core/engine_adapter/mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class MySQLEngineAdapter(
MAX_TABLE_COMMENT_LENGTH = 2048
MAX_COLUMN_COMMENT_LENGTH = 1024
SUPPORTS_REPLACE_TABLE = False
MAX_IDENTIFIER_LENGTH = 64
SCHEMA_DIFFER = SchemaDiffer(
parameterized_type_defaults={
exp.DataType.build("BIT", dialect=DIALECT).this: [(1,)],
Expand Down
1 change: 1 addition & 0 deletions sqlmesh/core/engine_adapter/postgres.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class PostgresEngineAdapter(
HAS_VIEW_BINDING = True
CURRENT_CATALOG_EXPRESSION = exp.column("current_catalog")
SUPPORTS_REPLACE_TABLE = False
MAX_IDENTIFIER_LENGTH = 63
SCHEMA_DIFFER = SchemaDiffer(
parameterized_type_defaults={
# DECIMAL without precision is "up to 131072 digits before the decimal point; up to 16383 digits after the decimal point"
Expand Down
1 change: 1 addition & 0 deletions sqlmesh/core/engine_adapter/risingwave.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class RisingwaveEngineAdapter(PostgresEngineAdapter):
COMMENT_CREATION_VIEW = CommentCreationView.UNSUPPORTED
SUPPORTS_MATERIALIZED_VIEWS = True
SUPPORTS_TRANSACTIONS = False
MAX_IDENTIFIER_LENGTH = None

def _truncate_table(self, table_name: TableName) -> None:
return self.execute(exp.Delete(this=exp.to_table(table_name)))
18 changes: 16 additions & 2 deletions tests/core/engine_adapter/integration/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import os
import pathlib
import re
import sys
import typing as t
import shutil
Expand Down Expand Up @@ -1571,7 +1572,6 @@ def test_init_project(ctx: TestContext, tmp_path_factory: pytest.TempPathFactory

# normalize object names for snowflake
if ctx.dialect == "snowflake":
import re

def _normalize_snowflake(name: str, prefix_regex: str = "(sqlmesh__)(.*)"):
match = re.search(prefix_regex, name)
Expand Down Expand Up @@ -1789,7 +1789,6 @@ def test_to_time_column(
# Clickhouse does not have natively timezone-aware types and does not accept timestrings
# with UTC offset "+XX:XX". Therefore, we remove the timezone offset and set a timezone-
# specific data type to validate what is returned.
import re

time_column = re.match(r"^(.*?)\+", time_column).group(1)
time_column_type = exp.DataType.build("TIMESTAMP('UTC')", dialect="clickhouse")
Expand Down Expand Up @@ -2652,3 +2651,18 @@ def execute(
{"id": 1, "name": "foo"} if ctx.dialect != "snowflake" else {"ID": 1, "NAME": "foo"}
)
assert df.iloc[0].to_dict() == expected_result


def test_identifier_length_limit(ctx: TestContext):
adapter = ctx.engine_adapter
if adapter.MAX_IDENTIFIER_LENGTH is None:
pytest.skip(f"Engine {adapter.dialect} does not have identifier length limits set.")

long_table_name = "a" * (adapter.MAX_IDENTIFIER_LENGTH + 1)

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"
with pytest.raises(
SQLMeshError,
match=re.escape(match),
):
adapter.create_table(long_table_name, {"col": exp.DataType.build("int")})