diff --git a/pyproject.toml b/pyproject.toml index 8f5ba21f04..1ebe985c00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ dependencies = [ "duckdb>=1.3.2", "filelock>=3.20.3", "orjson>=3.10.0", - "polyglot-sql-chio==0.9.3", + "polyglot-sql==0.11.0", "pydantic-settings>=2,<3", "PyYAML>=6.0.2", "rich>=15.0.0", diff --git a/scripts/rules_benchmark/_helpers/workflow.py b/scripts/rules_benchmark/_helpers/workflow.py index d6fb84d897..c90c1dba21 100644 --- a/scripts/rules_benchmark/_helpers/workflow.py +++ b/scripts/rules_benchmark/_helpers/workflow.py @@ -1099,7 +1099,7 @@ def _hardware_payload() -> dict[str, object]: def _version_payload() -> dict[str, str]: return { "sqlbuild": importlib.metadata.version("sqlbuild"), - "polyglot_sql": importlib.metadata.version("polyglot-sql-chio"), + "polyglot_sql": importlib.metadata.version("polyglot-sql"), } diff --git a/src/sqlbuild/cli/commands/_helpers/compile/sql_test_artifact_cache.py b/src/sqlbuild/cli/commands/_helpers/compile/sql_test_artifact_cache.py index fb1bc99e8e..5885cd4940 100644 --- a/src/sqlbuild/cli/commands/_helpers/compile/sql_test_artifact_cache.py +++ b/src/sqlbuild/cli/commands/_helpers/compile/sql_test_artifact_cache.py @@ -42,7 +42,7 @@ def build_sql_test_artifact_identity_context( { "algorithm": _ALGORITHM_FINGERPRINT, "sqlbuild_version": _package_version("sqlbuild"), - "polyglot_version": _package_version("polyglot-sql-chio"), + "polyglot_version": _package_version("polyglot-sql"), "python_version": platform.python_version_tuple()[:2], "adapter_class": f"{type(adapter).__module__}.{type(adapter).__qualname__}", "set_difference_operator": adapter.render_set_difference_operator(), diff --git a/src/sqlbuild/compiler/compile/_helpers/analysis/cache.py b/src/sqlbuild/compiler/compile/_helpers/analysis/cache.py index 08a422ec41..c18261a73d 100644 --- a/src/sqlbuild/compiler/compile/_helpers/analysis/cache.py +++ b/src/sqlbuild/compiler/compile/_helpers/analysis/cache.py @@ -85,7 +85,7 @@ def build_analysis_cache_context( "algorithm": _ANALYSIS_ALGORITHM_FINGERPRINT, "cache_version": _ANALYSIS_CACHE_VERSION, "sqlbuild_version": _package_version("sqlbuild"), - "polyglot_version": _package_version("polyglot-sql-chio"), + "polyglot_version": _package_version("polyglot-sql"), "python_version": platform.python_version_tuple()[:2], "allow_compact_analysis": allow_compact_analysis, "inference_profile": profile_payload, diff --git a/src/sqlbuild/compiler/compile/_helpers/analysis/columns.py b/src/sqlbuild/compiler/compile/_helpers/analysis/columns.py index 7044ffb4b7..24f3b84265 100644 --- a/src/sqlbuild/compiler/compile/_helpers/analysis/columns.py +++ b/src/sqlbuild/compiler/compile/_helpers/analysis/columns.py @@ -10,8 +10,8 @@ from sqlbuild.adapter.contract.models import ExpressionInferenceProfile from sqlbuild.adapter.contract.types import FunctionNullabilityRule from sqlbuild.compiler.compile._helpers.analysis.cte_facts import ( + _polyglot_cte_passthrough_facts, _polyglot_cte_passthrough_nullability_from_parsed, - _polyglot_cte_passthrough_type_facts, _polyglot_cte_passthrough_types_from_parsed, ) from sqlbuild.compiler.compile.constants import ( @@ -27,6 +27,7 @@ CompiledLineageColumnFact, CompiledLineageSourceFact, CompileSqlReference, + CteFactResolvers, InferredColumn, PolyglotAnalysisResult, ) @@ -233,6 +234,9 @@ TIMESTAMP_WITH_TIME_ZONE_SQL_TYPE_NAME as _TIMESTAMP_WITH_TIME_ZONE_SQL_TYPE_NAME, ) from sqlbuild.compiler.sql_analysis.main._find_matching_paren import find_matching_paren +from sqlbuild.compiler.sql_analysis.main._normalize_for_polyglot import ( + normalize_sql_for_polyglot, +) from sqlbuild.compiler.sql_analysis.main._schema_validation import get_schema_validations from sqlbuild.compiler.sql_analysis.main.import_polyglot_sql import import_polyglot_sql from sqlbuild.compiler.sql_analysis.models import ( @@ -268,7 +272,10 @@ def infer_columns_with_sql_analysis( profile: ExpressionInferenceProfile = inference_profile or ExpressionInferenceProfile() - cleaned_sql: str = _replace_refs_with_stubs(query_sql) + cleaned_sql: str = _replace_refs_with_stubs( + query_sql=query_sql, + dialect=profile.sql_analysis_dialect, + ) if placeholders: cleaned_sql = substitute_placeholder_defaults( query_sql=cleaned_sql, placeholders=placeholders @@ -295,7 +302,10 @@ def analyze_columns_with_polyglot( """Infer columns with one Polyglot parse, returning False when unavailable.""" profile: ExpressionInferenceProfile = inference_profile or ExpressionInferenceProfile() - cleaned_sql: str = _replace_refs_with_stubs(query_sql) + cleaned_sql: str = _replace_refs_with_stubs( + query_sql=query_sql, + dialect=profile.sql_analysis_dialect, + ) if placeholders: cleaned_sql = substitute_placeholder_defaults( query_sql=cleaned_sql, placeholders=placeholders @@ -324,7 +334,10 @@ def analyze_columns_and_lineage_with_polyglot( polyglot_module: Any = import_polyglot_sql() profile: ExpressionInferenceProfile = inference_profile or ExpressionInferenceProfile() - cleaned_sql: str = _replace_refs_with_stubs(query_sql) + cleaned_sql: str = _replace_refs_with_stubs( + query_sql=query_sql, + dialect=profile.sql_analysis_dialect, + ) if placeholders: cleaned_sql = substitute_placeholder_defaults( query_sql=cleaned_sql, placeholders=placeholders @@ -398,7 +411,7 @@ def get_complete_schema_binding_request( ) -> SqlSchemaValidationRequest: """Build one stable native schema-validation request.""" - cleaned_sql: str = _replace_refs_with_stubs(query_sql) + cleaned_sql: str = _replace_refs_with_stubs(query_sql=query_sql, dialect=dialect) if placeholders: cleaned_sql = substitute_placeholder_defaults( query_sql=cleaned_sql, @@ -470,18 +483,24 @@ def _analyze_columns_and_lineage_with_compact_polyglot( return None reference_map: dict[str, tuple[CompiledResourceType, str]] = _lineage_reference_map(references) relation_alias_by_name: dict[str, str | None] = _compact_relation_alias_by_name(analysis) - cte_passthrough_types, direct_cte_outputs = ( - _polyglot_cte_passthrough_type_facts( + cte_passthrough_types, cte_passthrough_nullability, direct_cte_outputs = ( + _polyglot_cte_passthrough_facts( polyglot_module=polyglot_module, cleaned_sql=cleaned_sql, dialect=dialect, column_types_by_table=column_types_by_table, + column_nullability_by_table=column_nullability_by_table, inference_profile=inference_profile, analysis=analysis, - expression_type_resolver=_polyglot_expression_type, + resolvers=CteFactResolvers( + expression_type=_polyglot_expression_type, + nullability=_infer_polyglot_nullability, + shallow_nullability=_infer_polyglot_shallow_nullability, + alias_nullability=_polyglot_alias_nullability_from_select, + ), ) if recover_cte_facts - else ({}, frozenset()) + else ({}, {}, frozenset()) ) columns: list[InferredColumn] = [] lineage_columns: list[CompiledLineageColumnFact] = [] @@ -507,9 +526,13 @@ def _analyze_columns_and_lineage_with_compact_polyglot( projection=projection, inference_profile=inference_profile ) ), - nullability=_compact_projection_nullability( - projection=projection, - infer_nullability=infer_nullability, + nullability=( + cte_passthrough_nullability.get(output_column) + if output_column in direct_cte_outputs + else None + ) + or _compact_projection_nullability( + projection=projection, infer_nullability=infer_nullability ), ) ) @@ -1578,7 +1601,7 @@ def _replacer(match: re.Match[str]) -> str: return _PLACEHOLDER_PATTERN.sub(_replacer, query_sql) -def _replace_refs_with_stubs(query_sql: str) -> str: +def _replace_refs_with_stubs(*, query_sql: str, dialect: str | None = None) -> str: """Replace SQLBuild marker calls with parseable SQL stubs.""" result: str = _REF_PATTERN.sub(r"\1", query_sql) @@ -1587,7 +1610,7 @@ def _replace_refs_with_stubs(query_sql: str) -> str: result = _DBT_REF_PATTERN.sub(r"\1", result) result = _UDF_PATTERN.sub(r"__sqlbuild_udf_\1", result) result = _replace_table_function_calls_with_stubs(result) - return result + return normalize_sql_for_polyglot(sql=result, dialect=dialect) def _replace_table_function_calls_with_stubs(query_sql: str) -> str: diff --git a/src/sqlbuild/compiler/compile/_helpers/analysis/cte_facts.py b/src/sqlbuild/compiler/compile/_helpers/analysis/cte_facts.py index 299458d95a..da0e49dbcb 100644 --- a/src/sqlbuild/compiler/compile/_helpers/analysis/cte_facts.py +++ b/src/sqlbuild/compiler/compile/_helpers/analysis/cte_facts.py @@ -9,6 +9,7 @@ from sqlbuild.adapter.type_system.main.normalize_type import normalize_type from sqlbuild.adapter.type_system.main.types_equal import types_equal from sqlbuild.compiler.compile.constants import SQL_WILDCARD_TOKEN +from sqlbuild.compiler.compile.models import CteFactResolvers from sqlbuild.compiler.lineage.types import InferredNullability from sqlbuild.compiler.sql_analysis.constants import ( POLYGLOT_BINARY_OPERAND_COUNT as _POLYGLOT_BINARY_OPERAND_COUNT, @@ -97,36 +98,57 @@ def __call__( ) -> dict[str, InferredNullability]: ... -def _polyglot_cte_passthrough_type_facts( +def _polyglot_cte_passthrough_facts( *, polyglot_module: Any, cleaned_sql: str, dialect: str | None, column_types_by_table: dict[str, dict[str, str]], + column_nullability_by_table: dict[str, dict[str, InferredNullability]], inference_profile: ExpressionInferenceProfile, analysis: dict[str, Any], - expression_type_resolver: _ExpressionTypeResolver, -) -> tuple[dict[str, str], frozenset[str]]: - """Recover types that compact analysis incorrectly skips across direct CTE reads.""" + resolvers: CteFactResolvers, +) -> tuple[dict[str, str], dict[str, InferredNullability], frozenset[str]]: + """Recover facts that compact analysis skips across direct CTE reads.""" - if not column_types_by_table or not analysis.get("cteFacts"): - return {}, frozenset() + if not analysis.get("cteFacts") or not (column_types_by_table or column_nullability_by_table): + return {}, {}, frozenset() try: parsed: Any = polyglot_module.parse_one(cleaned_sql, dialect=dialect or "generic") except polyglot_module.PolyglotError: - return {}, frozenset() + return {}, {}, frozenset() ctes: tuple[tuple[str, Any, bool], ...] = _polyglot_top_level_ctes(parsed) if not ctes: - return {}, frozenset() - inferred_types: dict[str, str] = _polyglot_cte_passthrough_types_from_parsed( - parsed=parsed, - column_types_by_table=column_types_by_table, - inference_profile=inference_profile, - expression_type_resolver=expression_type_resolver, + return {}, {}, frozenset() + inferred_types: dict[str, str] = ( + _polyglot_cte_passthrough_types_from_parsed( + parsed=parsed, + column_types_by_table=column_types_by_table, + inference_profile=inference_profile, + expression_type_resolver=resolvers.expression_type, + ) + if column_types_by_table + else {} ) - return inferred_types, _polyglot_direct_cte_output_names( - parsed=parsed, - cte_names=frozenset(name for name, _, _ in ctes), + inferred_nullability: dict[str, InferredNullability] = ( + _polyglot_cte_passthrough_nullability_from_parsed( + parsed=parsed, + column_nullability_by_table=column_nullability_by_table, + inference_profile=inference_profile, + nullability_resolver=resolvers.nullability, + shallow_nullability_resolver=resolvers.shallow_nullability, + alias_nullability_resolver=resolvers.alias_nullability, + ) + if column_nullability_by_table + else {} + ) + return ( + inferred_types, + inferred_nullability, + _polyglot_direct_cte_output_names( + parsed=parsed, + cte_names=frozenset(name for name, _, _ in ctes), + ), ) diff --git a/src/sqlbuild/compiler/compile/_helpers/analysis/required_columns.py b/src/sqlbuild/compiler/compile/_helpers/analysis/required_columns.py index 8ff2d08a86..95f06c4702 100644 --- a/src/sqlbuild/compiler/compile/_helpers/analysis/required_columns.py +++ b/src/sqlbuild/compiler/compile/_helpers/analysis/required_columns.py @@ -71,7 +71,7 @@ def resolve_required_external_columns( polyglot_module: Any = import_polyglot_sql() try: parsed: Any = polyglot_module.parse_one( - _replace_refs_with_stubs(query_sql), + _replace_refs_with_stubs(query_sql=query_sql, dialect=dialect), dialect=dialect or "generic", ) except polyglot_module.PolyglotError: diff --git a/src/sqlbuild/compiler/compile/_helpers/analysis/validation.py b/src/sqlbuild/compiler/compile/_helpers/analysis/validation.py index 771ee3260b..a697e0e16b 100644 --- a/src/sqlbuild/compiler/compile/_helpers/analysis/validation.py +++ b/src/sqlbuild/compiler/compile/_helpers/analysis/validation.py @@ -24,7 +24,7 @@ def validate_sql_syntax( ) -> None: """Validate that the model query SQL is parseable by Polyglot.""" - cleaned_sql: str = _replace_refs_with_stubs(query_sql) + cleaned_sql: str = _replace_refs_with_stubs(query_sql=query_sql, dialect=dialect) if placeholders: cleaned_sql = substitute_placeholder_defaults( query_sql=cleaned_sql, placeholders=placeholders @@ -171,6 +171,7 @@ def _validate_sql_syntax_with_message( cleaned_sql: str = _clean_sql_for_validation( query_sql=query_sql, placeholders=placeholders, + dialect=dialect, ) polyglot_module: Any = import_polyglot_sql() try: @@ -194,14 +195,20 @@ def _validate_hook_sql_with_message( cleaned_sql: str = _clean_sql_for_validation( query_sql=query_sql, placeholders=placeholders, + dialect=dialect, ) error_message: str | None = _validate_sql_with_polyglot(sql=cleaned_sql, dialect=dialect) if error_message is not None: _raise_sql_validation_error(error_prefix=error_prefix, error_message=error_message) -def _clean_sql_for_validation(*, query_sql: str, placeholders: dict[str, str] | None) -> str: - cleaned_sql: str = _replace_refs_with_stubs(query_sql) +def _clean_sql_for_validation( + *, + query_sql: str, + placeholders: dict[str, str] | None, + dialect: str | None, +) -> str: + cleaned_sql: str = _replace_refs_with_stubs(query_sql=query_sql, dialect=dialect) if placeholders: return substitute_placeholder_defaults( query_sql=cleaned_sql, diff --git a/src/sqlbuild/compiler/compile/models.py b/src/sqlbuild/compiler/compile/models.py index f2f9ce481b..37eed5cf47 100644 --- a/src/sqlbuild/compiler/compile/models.py +++ b/src/sqlbuild/compiler/compile/models.py @@ -5,6 +5,7 @@ from collections.abc import Callable, Mapping from dataclasses import dataclass, field from pathlib import Path +from typing import Any from sqlbuild.compiler.auditing.models import MeasurementContract, MeasurementThresholds from sqlbuild.compiler.auditing.types import AuditEvaluationMode, AuditSeverity @@ -143,6 +144,16 @@ class InferredColumn: nullability: InferredNullability = InferredNullability.UNKNOWN +@dataclass(frozen=True) +class CteFactResolvers: + """Expression resolvers used for conservative CTE fact recovery.""" + + expression_type: Callable[..., Any] + nullability: Callable[..., Any] + shallow_nullability: Callable[..., Any] + alias_nullability: Callable[..., Any] + + @dataclass(frozen=True) class CompileModelConfig: """Pre-semantic effective config layers attached to a model input.""" diff --git a/src/sqlbuild/compiler/sql_analysis/_helpers/polyglot_normalization.py b/src/sqlbuild/compiler/sql_analysis/_helpers/polyglot_normalization.py new file mode 100644 index 0000000000..ee054f8514 --- /dev/null +++ b/src/sqlbuild/compiler/sql_analysis/_helpers/polyglot_normalization.py @@ -0,0 +1,149 @@ +"""Dialect compatibility normalization for Polyglot analysis input.""" + +from __future__ import annotations + +import re + +from sqlbuild.compiler.sql_analysis._helpers.scanning import ( + skip_block_comment_impl, + skip_line_comment_impl, + skip_quoted_text_impl, +) +from sqlbuild.compiler.sql_analysis.constants import ( + SNOWFLAKE_DIALECT_NAME, + SQL_CLOSE_BRACKET, + SQL_DOLLAR_QUOTE_DELIMITER, + SQL_ESCAPE_CHARACTER, + SQL_OPEN_BRACKET, + SQL_QUOTE_CHARACTERS, + SQL_STRING_QUOTE_CHARACTER, +) + +_IDENTIFIER: str = r'(?:[A-Za-z_][A-Za-z0-9_$]*|"(?:""|[^"])+")' +_VARIANT_PATH_SUFFIX: re.Pattern[str] = re.compile( + rf"(?P{_IDENTIFIER}(?:\.{_IDENTIFIER})*:{_IDENTIFIER}" + rf"(?:(?::|\.){_IDENTIFIER})*)$" +) +_SIMPLE_BRACKET_KEY: re.Pattern[str] = re.compile(rf"\s*(?:{_IDENTIFIER}|[0-9]+|'(?:''|[^'])*')\s*") +_SCAN_SPECIAL: re.Pattern[str] = re.compile(r"[-/$'\"`\[]") + + +def normalize_sql_for_polyglot_impl(*, sql: str, dialect: str | None) -> str: + """Return analysis-equivalent SQL for Polyglot's supported dialect surface.""" + + if dialect is None or dialect.casefold() != SNOWFLAKE_DIALECT_NAME: + return sql + replacements: list[tuple[int, int, str]] = [] + index: int = 0 + while index < len(sql): + special: re.Match[str] | None = _SCAN_SPECIAL.search(sql, index) + if special is None: + break + index = special.start() + if sql.startswith("--", index): + index = skip_line_comment_impl(sql=sql, start=index) + continue + if sql.startswith("/*", index): + index = skip_block_comment_impl( + sql=sql, + start=index, + context="Snowflake SQL analysis", + ) + continue + if sql.startswith(SQL_DOLLAR_QUOTE_DELIMITER, index): + index = _skip_dollar_quoted_text(sql=sql, start=index) + continue + if sql[index] in SQL_QUOTE_CHARACTERS: + index = _skip_snowflake_quoted_text(sql=sql, start=index) + continue + close_bracket: int | None = _matching_bracket(sql=sql, open_bracket=index) + if close_bracket is None: + index += 1 + continue + key_sql: str = sql[index + 1 : close_bracket] + normalized_key: str = normalize_sql_for_polyglot_impl(sql=key_sql, dialect=dialect) + base_end: int = _trailing_trivia_start(sql=sql, end=index) + base_match: re.Match[str] | None = _VARIANT_PATH_SUFFIX.search(sql[:base_end]) + if base_match is not None and _SIMPLE_BRACKET_KEY.fullmatch(key_sql) is None: + base_sql: str = base_match.group("base") + trivia: str = sql[base_end:index] + replacements.append( + ( + base_match.start("base"), + close_bracket + 1, + f"GET({base_sql}{trivia}, {normalized_key})", + ) + ) + elif normalized_key != key_sql: + replacements.append((index + 1, close_bracket, normalized_key)) + index = close_bracket + 1 + normalized: str = sql + for start, end, replacement in reversed(replacements): + normalized = f"{normalized[:start]}{replacement}{normalized[end:]}" + return normalized + + +def _matching_bracket(*, sql: str, open_bracket: int) -> int | None: + depth: int = 1 + index: int = open_bracket + 1 + while index < len(sql): + if sql.startswith("--", index): + index = skip_line_comment_impl(sql=sql, start=index) + continue + if sql.startswith("/*", index): + index = skip_block_comment_impl( + sql=sql, + start=index, + context="Snowflake SQL analysis", + ) + continue + if sql.startswith(SQL_DOLLAR_QUOTE_DELIMITER, index): + index = _skip_dollar_quoted_text(sql=sql, start=index) + continue + if sql[index] in SQL_QUOTE_CHARACTERS: + index = _skip_snowflake_quoted_text(sql=sql, start=index) + continue + if sql[index] == SQL_OPEN_BRACKET: + depth += 1 + elif sql[index] == SQL_CLOSE_BRACKET: + depth -= 1 + if depth == 0: + return index + index += 1 + return None + + +def _skip_snowflake_quoted_text(*, sql: str, start: int) -> int: + if sql[start] != SQL_STRING_QUOTE_CHARACTER: + return skip_quoted_text_impl(sql=sql, start=start, context="Snowflake SQL analysis") + index: int = start + 1 + while index < len(sql): + if sql[index] == SQL_ESCAPE_CHARACTER: + index += 2 + continue + if sql[index] == SQL_STRING_QUOTE_CHARACTER: + if index + 1 < len(sql) and sql[index + 1] == SQL_STRING_QUOTE_CHARACTER: + index += 2 + continue + return index + 1 + index += 1 + return len(sql) + + +def _skip_dollar_quoted_text(*, sql: str, start: int) -> int: + closing: int = sql.find(SQL_DOLLAR_QUOTE_DELIMITER, start + len(SQL_DOLLAR_QUOTE_DELIMITER)) + return len(sql) if closing == -1 else closing + len(SQL_DOLLAR_QUOTE_DELIMITER) + + +def _trailing_trivia_start(*, sql: str, end: int) -> int: + current: int = end + while True: + trimmed: int = current + while trimmed > 0 and sql[trimmed - 1].isspace(): + trimmed -= 1 + if not sql[:trimmed].endswith("*/"): + return trimmed + opening: int = sql.rfind("/*", 0, trimmed - 2) + if opening == -1: + return trimmed + current = opening diff --git a/src/sqlbuild/compiler/sql_analysis/constants.py b/src/sqlbuild/compiler/sql_analysis/constants.py index 9a0bde27e6..0dc4fc4a4d 100644 --- a/src/sqlbuild/compiler/sql_analysis/constants.py +++ b/src/sqlbuild/compiler/sql_analysis/constants.py @@ -6,7 +6,13 @@ SQL_QUOTE_CHARACTERS: frozenset[str] = frozenset({*SQL_ESCAPABLE_QUOTE_CHARACTERS, "`"}) SQL_OPEN_PARENTHESIS: str = "(" SQL_CLOSE_PARENTHESIS: str = ")" +SQL_OPEN_BRACKET: str = "[" +SQL_CLOSE_BRACKET: str = "]" +SQL_STRING_QUOTE_CHARACTER: str = "'" +SQL_ESCAPE_CHARACTER: str = "\\" +SQL_DOLLAR_QUOTE_DELIMITER: str = "$$" SQL_IDENTIFIER_PREFIX: str = "_" +SNOWFLAKE_DIALECT_NAME: str = "snowflake" BINDING_UNKNOWN_TABLE_INTERNAL_CODE: str = "B000" POLYGLOT_KIND_ALIAS: str = "alias" diff --git a/src/sqlbuild/compiler/sql_analysis/main/_normalize_for_polyglot.py b/src/sqlbuild/compiler/sql_analysis/main/_normalize_for_polyglot.py new file mode 100644 index 0000000000..1a7261eacd --- /dev/null +++ b/src/sqlbuild/compiler/sql_analysis/main/_normalize_for_polyglot.py @@ -0,0 +1,13 @@ +"""Public dialect compatibility normalization for Polyglot analysis input.""" + +from __future__ import annotations + +from sqlbuild.compiler.sql_analysis._helpers.polyglot_normalization import ( + normalize_sql_for_polyglot_impl, +) + + +def normalize_sql_for_polyglot(*, sql: str, dialect: str | None) -> str: + """Return analysis-equivalent SQL for Polyglot's supported dialect surface.""" + + return normalize_sql_for_polyglot_impl(sql=sql, dialect=dialect) diff --git a/tests/integration/src/sqlbuild/cli/commands/main/_test_types.py b/tests/integration/src/sqlbuild/cli/commands/main/_test_types.py index 0abcf05719..fd1501e51a 100644 --- a/tests/integration/src/sqlbuild/cli/commands/main/_test_types.py +++ b/tests/integration/src/sqlbuild/cli/commands/main/_test_types.py @@ -46,6 +46,17 @@ class TypedContractRuleIntegrationTestCase: expected_contract_106_findings: int +@dataclass(frozen=True) +class SnowflakeCompileIntegrationTestCase: + """One Snowflake SQL compatibility expectation through compile.""" + + description: str + query_sql: str + expected_exit_code: int + expected_diagnostics: tuple[str, ...] + expected_query_fragment: str + + @dataclass(frozen=True) class FormatCompileIntegrationTestCase: """One format-to-compile compatibility expectation.""" diff --git a/tests/integration/src/sqlbuild/cli/commands/main/test_compile.py b/tests/integration/src/sqlbuild/cli/commands/main/test_compile.py new file mode 100644 index 0000000000..c4b40a5e84 --- /dev/null +++ b/tests/integration/src/sqlbuild/cli/commands/main/test_compile.py @@ -0,0 +1,69 @@ +"""Integration coverage for compiler SQL dialect support.""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import cast + +import pytest + +from sqlbuild.cli.commands.main.entrypoint.entry import main +from tests.integration.src.sqlbuild.cli.commands.main._test_types import ( + SnowflakeCompileIntegrationTestCase, +) + + +@pytest.mark.parametrize( + "test_case", + ( + SnowflakeCompileIntegrationTestCase( + description="grouping sets include a nested variant dynamic key", + query_sql="""WITH orders AS ( + SELECT + 'ready' AS category, + OBJECT_CONSTRUCT('labels', OBJECT_CONSTRUCT('1', 'priority')) AS attributes, + 1 AS item_id, + 10 AS amount +) +SELECT + category, + attributes:labels[TO_VARCHAR(item_id)] AS attribute_value, + SUM(amount) AS total_amount +FROM orders +GROUP BY GROUPING SETS ( + (category, attributes:labels[TO_VARCHAR(item_id)]), + () +) +""", + expected_exit_code=0, + expected_diagnostics=(), + expected_query_fragment="attributes:labels[TO_VARCHAR(item_id)]", + ), + ), + ids=lambda case: case.description, +) +def test_given_supported_snowflake_aggregation_when_compiling_then_project_succeeds( + test_case: SnowflakeCompileIntegrationTestCase, + tmp_path: Path, + capsys: pytest.CaptureFixture[str], +) -> None: + (tmp_path / "sqlbuild_project.toml").write_text( + 'name = "orders"\nadapter = "snowflake"\n', encoding="utf-8" + ) + model: Path = tmp_path / "models" / "order_summary.sql" + model.parent.mkdir() + model.write_text( + f"MODEL (database warehouse, schema analytics);\n\n{test_case.query_sql}", + encoding="utf-8", + ) + + exit_code: int = main(["--project-dir", str(tmp_path), "compile", "--json", "--no-cache"]) + result: dict[str, object] = json.loads(capsys.readouterr().out) + diagnostics: list[dict[str, object]] = cast(list[dict[str, object]], result["diagnostics"]) + resources: dict[str, object] = cast(dict[str, object], result["resources"]) + models: list[dict[str, object]] = cast(list[dict[str, object]], resources["models"]) + + assert exit_code == test_case.expected_exit_code + assert tuple(item["code"] for item in diagnostics) == test_case.expected_diagnostics + assert test_case.expected_query_fragment in cast(str, models[0]["query_sql"]) diff --git a/tests/unit/src/sqlbuild/cli/project_metadata/test_dependencies.py b/tests/unit/src/sqlbuild/cli/project_metadata/test_dependencies.py index 0775c6698a..2647325323 100644 --- a/tests/unit/src/sqlbuild/cli/project_metadata/test_dependencies.py +++ b/tests/unit/src/sqlbuild/cli/project_metadata/test_dependencies.py @@ -16,14 +16,14 @@ "test_case", [ ProjectDependencyTestCase( - description="scope-fixed polyglot distribution is a core dependency", - dependency_name="polyglot-sql-chio", + description="upstream polyglot distribution is a core dependency", + dependency_name="polyglot-sql", expected_in_core_dependencies=True, expected_optional_extra_absent=True, ), ProjectDependencyTestCase( - description="upstream polyglot distribution is not a direct project dependency", - dependency_name="polyglot-sql", + description="legacy polyglot fork is not a direct project dependency", + dependency_name="polyglot-sql-chio", expected_in_core_dependencies=False, expected_optional_extra_absent=True, ), diff --git a/tests/unit/src/sqlbuild/compiler/compile/_helpers/_test_types.py b/tests/unit/src/sqlbuild/compiler/compile/_helpers/_test_types.py index a8056f2bf2..a532095bea 100644 --- a/tests/unit/src/sqlbuild/compiler/compile/_helpers/_test_types.py +++ b/tests/unit/src/sqlbuild/compiler/compile/_helpers/_test_types.py @@ -528,6 +528,7 @@ class ValidateSqlSyntaxTestCase: query_sql: str expected_valid: bool expected_error_fragment: str | None = None + dialect: str | None = None @dataclass(frozen=True) diff --git a/tests/unit/src/sqlbuild/compiler/compile/_helpers/test_sql_analysis_columns.py b/tests/unit/src/sqlbuild/compiler/compile/_helpers/test_sql_analysis_columns.py index a5e3b1a819..b60a5df3b8 100644 --- a/tests/unit/src/sqlbuild/compiler/compile/_helpers/test_sql_analysis_columns.py +++ b/tests/unit/src/sqlbuild/compiler/compile/_helpers/test_sql_analysis_columns.py @@ -900,19 +900,8 @@ def raise_parse_error(*args: object, **kwargs: object) -> object: InferredColumn(name="status", type="TEXT"), InferredColumn(name="nullable_status", type="TEXT"), ), - expected_lineage_columns=( - CompiledLineageColumnFact( - output_column="amount", - upstream_columns=(), - transform_kind=ColumnTransformKind.CONSTANT, - confidence=ColumnLineageConfidence.HIGH, - ), - CompiledLineageColumnFact( - output_column="status", - upstream_columns=(), - transform_kind=ColumnTransformKind.CONSTANT, - confidence=ColumnLineageConfidence.HIGH, - ), + expected_lineage_columns=direct_orders_lineage("amount", "status") + + ( CompiledLineageColumnFact( output_column="nullable_status", upstream_columns=( @@ -986,7 +975,7 @@ def raise_parse_error(*args: object, **kwargs: object) -> object: expected_has_star=False, ), PolyglotAnalysisTestCase( - description="declines renamed star type recovery", + description="preserves renamed star lineage without recovering type", query_sql=( "WITH typed AS (" "SELECT CAST(amount AS INTEGER) AS amount, " @@ -1002,14 +991,7 @@ def raise_parse_error(*args: object, **kwargs: object) -> object: column_types_by_table={"orders": {"amount": "VARCHAR"}}, inference_profile=ExpressionInferenceProfile(sql_analysis_dialect="snowflake"), expected_columns=(InferredColumn(name="amount"),), - expected_lineage_columns=( - CompiledLineageColumnFact( - output_column="amount", - upstream_columns=(), - transform_kind=ColumnTransformKind.CONSTANT, - confidence=ColumnLineageConfidence.HIGH, - ), - ), + expected_lineage_columns=direct_orders_lineage("amount"), expected_has_star=False, ), ], @@ -1093,7 +1075,10 @@ def test_given_cte_chain_when_analyzing_direct_passthrough_then_type_is_conserva function_return_types={"TO_DATE": "DATE"}, ), expected_columns=( - InferredColumn(name="source_name"), + InferredColumn( + name="source_name", + nullability=InferredNullability.NON_NULL, + ), InferredColumn(name="active", type="BOOLEAN"), InferredColumn(name="selected", type="BOOLEAN"), InferredColumn(name="full_name", type="TEXT"), diff --git a/tests/unit/src/sqlbuild/compiler/compile/_helpers/test_sql_analysis_validation.py b/tests/unit/src/sqlbuild/compiler/compile/_helpers/test_sql_analysis_validation.py index 19ed8044e4..8cb0f0d63b 100644 --- a/tests/unit/src/sqlbuild/compiler/compile/_helpers/test_sql_analysis_validation.py +++ b/tests/unit/src/sqlbuild/compiler/compile/_helpers/test_sql_analysis_validation.py @@ -53,6 +53,20 @@ query_sql=('SELECT id FROM __ref("orders") UNION ALL SELECT id FROM __ref("returns")'), expected_valid=True, ), + ValidateSqlSyntaxTestCase( + description="accepts Snowflake grouping sets", + query_sql=( + "SELECT category, SUM(amount) FROM orders GROUP BY GROUPING SETS ((category), ())" + ), + expected_valid=True, + dialect="snowflake", + ), + ValidateSqlSyntaxTestCase( + description="accepts Snowflake dynamic brackets after variant paths", + query_sql="SELECT attributes:labels[TO_VARCHAR(item_id)] FROM orders", + expected_valid=True, + dialect="snowflake", + ), ], ids=lambda case: case.description, ) @@ -63,6 +77,7 @@ def test_given_valid_sql_when_validating_syntax_then_does_not_raise( query_sql=test_case.query_sql, model_name=_MODEL_NAME, file_path=_FILE_PATH, + dialect=test_case.dialect, ) assert test_case.expected_valid is True @@ -100,6 +115,7 @@ def test_given_invalid_sql_when_validating_syntax_then_raises_compile_error( query_sql=test_case.query_sql, model_name=_MODEL_NAME, file_path=_FILE_PATH, + dialect=test_case.dialect, ) assert test_case.expected_valid is False diff --git a/tests/unit/src/sqlbuild/compiler/sql_analysis/main/_test_types.py b/tests/unit/src/sqlbuild/compiler/sql_analysis/main/_test_types.py index 01bade17d4..98c2d40ddf 100644 --- a/tests/unit/src/sqlbuild/compiler/sql_analysis/main/_test_types.py +++ b/tests/unit/src/sqlbuild/compiler/sql_analysis/main/_test_types.py @@ -10,3 +10,11 @@ class SchemaValidationScopeTestCase: schema: dict[str, dict[str, str]] dialects: tuple[str, ...] expected_diagnostic_count: int + + +@dataclass(frozen=True) +class PolyglotSqlNormalizationTestCase: + description: str + sql: str + dialect: str | None + expected_sql: str diff --git a/tests/unit/src/sqlbuild/compiler/sql_analysis/main/test_normalize_for_polyglot.py b/tests/unit/src/sqlbuild/compiler/sql_analysis/main/test_normalize_for_polyglot.py new file mode 100644 index 0000000000..b43a94e2e0 --- /dev/null +++ b/tests/unit/src/sqlbuild/compiler/sql_analysis/main/test_normalize_for_polyglot.py @@ -0,0 +1,93 @@ +from __future__ import annotations + +import pytest + +from sqlbuild.compiler.sql_analysis.main._normalize_for_polyglot import ( + normalize_sql_for_polyglot, +) +from tests.unit.src.sqlbuild.compiler.sql_analysis.main._test_types import ( + PolyglotSqlNormalizationTestCase, +) + + +@pytest.mark.parametrize( + "test_case", + ( + PolyglotSqlNormalizationTestCase( + description="rewrites a dynamic key after a Snowflake variant path", + sql="SELECT payload:labels[TO_VARCHAR(payload:item_id)] FROM events", + dialect="snowflake", + expected_sql="SELECT GET(payload:labels, TO_VARCHAR(payload:item_id)) FROM events", + ), + PolyglotSqlNormalizationTestCase( + description="preserves a static key after a Snowflake variant path", + sql="SELECT payload:labels['priority'] FROM events", + dialect="snowflake", + expected_sql="SELECT payload:labels['priority'] FROM events", + ), + PolyglotSqlNormalizationTestCase( + description="preserves dynamic brackets outside Snowflake variant paths", + sql="SELECT labels[TO_VARCHAR(item_id)] FROM events", + dialect="snowflake", + expected_sql="SELECT labels[TO_VARCHAR(item_id)] FROM events", + ), + PolyglotSqlNormalizationTestCase( + description="preserves another dialect", + sql="SELECT payload:labels[TO_VARCHAR(item_id)] FROM events", + dialect="duckdb", + expected_sql="SELECT payload:labels[TO_VARCHAR(item_id)] FROM events", + ), + PolyglotSqlNormalizationTestCase( + description="preserves matching syntax in quoted text and comments", + sql=( + "SELECT 'payload:labels[TO_VARCHAR(item_id)]' AS text " + "-- payload:labels[TO_VARCHAR(item_id)]\nFROM events" + ), + dialect="snowflake", + expected_sql=( + "SELECT 'payload:labels[TO_VARCHAR(item_id)]' AS text " + "-- payload:labels[TO_VARCHAR(item_id)]\nFROM events" + ), + ), + PolyglotSqlNormalizationTestCase( + description="preserves Snowflake backslash-escaped quoted text", + sql=r"SELECT 'it\'s ready' AS label", + dialect="snowflake", + expected_sql=r"SELECT 'it\'s ready' AS label", + ), + PolyglotSqlNormalizationTestCase( + description="preserves Snowflake dollar-quoted text", + sql="SELECT $$it's ready$$ AS label", + dialect="snowflake", + expected_sql="SELECT $$it's ready$$ AS label", + ), + PolyglotSqlNormalizationTestCase( + description="rewrites a nested dynamic variant access", + sql="SELECT labels[payload:groups[TO_VARCHAR(group_id)]] FROM events", + dialect="snowflake", + expected_sql="SELECT labels[GET(payload:groups, TO_VARCHAR(group_id))] FROM events", + ), + PolyglotSqlNormalizationTestCase( + description="rewrites a dynamic variant access after whitespace", + sql="SELECT payload:labels [TO_VARCHAR(item_id)] FROM events", + dialect="snowflake", + expected_sql="SELECT GET(payload:labels , TO_VARCHAR(item_id)) FROM events", + ), + PolyglotSqlNormalizationTestCase( + description="rewrites a dynamic variant access after a block comment", + sql="SELECT payload:labels/* item lookup */[TO_VARCHAR(item_id)] FROM events", + dialect="snowflake", + expected_sql=( + "SELECT GET(payload:labels/* item lookup */, TO_VARCHAR(item_id)) FROM events" + ), + ), + ), + ids=lambda case: case.description, +) +def test_given_sql_when_normalizing_for_polyglot_then_expected_sql_is_returned( + test_case: PolyglotSqlNormalizationTestCase, +) -> None: + assert ( + normalize_sql_for_polyglot(sql=test_case.sql, dialect=test_case.dialect) + == test_case.expected_sql + ) diff --git a/uv.lock b/uv.lock index bb1ef7a02b..f2dabbb096 100644 --- a/uv.lock +++ b/uv.lock @@ -2865,30 +2865,30 @@ wheels = [ ] [[package]] -name = "polyglot-sql-chio" -version = "0.9.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/9b/864155bc385abe8cbac7141e972e657d9c61135ce502d5cf79a995eb9931/polyglot_sql_chio-0.9.3.tar.gz", hash = "sha256:3f5c33c49e31d26ba89ca57e955a4c52e5e98fcb757683b14b784c0eb1dbc7dc", size = 1854393, upload-time = "2026-09-10T00:17:41.936Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/9a/a135fc7efa613b068f36769d5faae5938ffcd09c8c72bb3beb02685fe987/polyglot_sql_chio-0.9.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e8f0e1236a505c46def7bd040f6fd066fe2a6b16923a02475ae1adfc5c5a4fd1", size = 11393565, upload-time = "2026-09-10T00:16:52.182Z" }, - { url = "https://files.pythonhosted.org/packages/80/cb/35e10749167e43aeff0c9676cc79d898c45988b37eecc12dad2466fd55e9/polyglot_sql_chio-0.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:447a6bc410b9059bf8b0d97193561da72ae36c221e27758511f3ee061f5aa468", size = 10820461, upload-time = "2026-09-10T00:16:54.87Z" }, - { url = "https://files.pythonhosted.org/packages/e7/a1/325cd1a0dc1a14f59fe741705093c59a3d893a04cbac36638f1fc8d554a2/polyglot_sql_chio-0.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a98d42c2f18009cfd60a5ab529746b458de30b02567fefd3ff41aa89ef52935", size = 11627017, upload-time = "2026-09-10T00:16:56.969Z" }, - { url = "https://files.pythonhosted.org/packages/35/fc/5286a82079d4c8d321c03a076d70cc5086e82cd897ff1adf25ebc6e499d0/polyglot_sql_chio-0.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03c5758ce0c4c2e25462c020713325d38551ccea33eb2f30100dc2d697c90724", size = 10505124, upload-time = "2026-09-10T00:16:59.106Z" }, - { url = "https://files.pythonhosted.org/packages/0c/6f/abcb8901100797c2a0beb26029ed68c72c78b4851e0bcb19d12e6eb440bc/polyglot_sql_chio-0.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:e58351267601d406a3aed8e544e83627129f4de105b60ab50efbae5cc3f23b45", size = 10319622, upload-time = "2026-09-10T00:17:01.345Z" }, - { url = "https://files.pythonhosted.org/packages/26/ad/99e9aa32f73f66010ce2ea39d7b570e911ce91559187e31d2af99a6ce449/polyglot_sql_chio-0.9.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:56994aa736dd5cb1bc77b2c0727a9fdb82c28328f9924d9e172163905fbdf94c", size = 11393667, upload-time = "2026-09-10T00:17:03.642Z" }, - { url = "https://files.pythonhosted.org/packages/93/ad/2b55ec0f102822871a51d2d03734977ed67ab5add2c2108eea844523ebd4/polyglot_sql_chio-0.9.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8e4849f1a2e1ff5fa37cc4a338542b1efc325fe9b7b37fb450a6eb3dcd0313b4", size = 10818953, upload-time = "2026-09-10T00:17:05.848Z" }, - { url = "https://files.pythonhosted.org/packages/e9/b0/5bc21e26f5a3592affcd26ba1757ed81986e7e13c25b1ecf838d2a2ff2ee/polyglot_sql_chio-0.9.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cfa2828b6f135380a08ef8d3a409f2a2fa4e151f14747a12b702da132bd1bd", size = 11627708, upload-time = "2026-09-10T00:17:08.05Z" }, - { url = "https://files.pythonhosted.org/packages/9e/b9/b0ab139eecfbfff601a2f6fa8809a1d9f41e03adc5de174ced3f4d1bdc82/polyglot_sql_chio-0.9.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:205eae5f0b626b106bfc4ee94af72ed893e238fbf9b8a83a68b6f696d63e58b4", size = 10504681, upload-time = "2026-09-10T00:17:10.083Z" }, - { url = "https://files.pythonhosted.org/packages/ec/6d/4cabfb62bf58e1376c0fd4a160f3ef88d8f971d56dced7d797ba87155527/polyglot_sql_chio-0.9.3-cp313-cp313-win_amd64.whl", hash = "sha256:bf18b1ff4c3b9289b6b6c6b8282a54351638b39f2153dbf5b77d94ebde0be37a", size = 10317575, upload-time = "2026-09-10T00:17:12.15Z" }, - { url = "https://files.pythonhosted.org/packages/58/49/4f142a06c00a8d6f2dc60f30a6476e8a35481664038806b5948e075076bf/polyglot_sql_chio-0.9.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1d2187550aa7955b55115a336ef83e9c59baba7e859dc27505d0c781a2b12a2", size = 11393444, upload-time = "2026-09-10T00:17:14.175Z" }, - { url = "https://files.pythonhosted.org/packages/91/40/4649efd6ab7cead4914e940a608ff81a3bb862b91557c5c4f58b55038df1/polyglot_sql_chio-0.9.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:91ef468499789af754e28ce44bf538fdf6efc08f65b8d155a52d1e18c18ee936", size = 10821231, upload-time = "2026-09-10T00:17:16.175Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6b/8801cdbfe719e3991bd2c4364e5e34d40ffdd2eaae05531e5f3d8918b87f/polyglot_sql_chio-0.9.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bfa5411aef5600329c9d62237d91b80d4f7330ee9d3fd568a19106b695dfe7e3", size = 11627939, upload-time = "2026-09-10T00:17:18.249Z" }, - { url = "https://files.pythonhosted.org/packages/68/9a/fe5dff0c8ca361bc03028c2cf2f15811ac518e3283a518f65ca9e0cdc32f/polyglot_sql_chio-0.9.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc6c4ef318f4acff806ab019b8771da364579a166544ec57fa9f20326cf695a1", size = 10506705, upload-time = "2026-09-10T00:17:20.664Z" }, - { url = "https://files.pythonhosted.org/packages/b5/a4/f7ed5d617dc4902104fc301708e175128e80182e2c5a52ab9c07366ef6bc/polyglot_sql_chio-0.9.3-cp314-cp314-win_amd64.whl", hash = "sha256:60942fb55409f80b310e7d1160c6d1da517aacb28ec8cc4b2c26d54feed4b86b", size = 10319872, upload-time = "2026-09-10T00:17:23.205Z" }, - { url = "https://files.pythonhosted.org/packages/52/d0/b83b91973a3cb352c9045a3e816db0a7f368a9f887600afe1708c3e54da7/polyglot_sql_chio-0.9.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0043607078ee18450e18b556b24494d4ef04120ecc4cac8618d683056e265a0", size = 11726880, upload-time = "2026-09-10T00:17:25.358Z" }, - { url = "https://files.pythonhosted.org/packages/54/04/3619ebc5db504d10a1a1804cc6fa91122e85335108ea14548b31951ffb21/polyglot_sql_chio-0.9.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f98944c95285bcce3388c08067554c9a278872a3532ac5b490c9e60071b288c6", size = 10580844, upload-time = "2026-09-10T00:17:27.45Z" }, - { url = "https://files.pythonhosted.org/packages/13/b1/bd629fd2a45212bddcd01092b541f2b26166b5a72f2ae107b26c147c92bb/polyglot_sql_chio-0.9.3-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee8ee12302b8f78c2ca9415b66d90bd3428b1f4766e2656bd4e490ae7311389d", size = 10506703, upload-time = "2026-09-10T00:17:29.381Z" }, - { url = "https://files.pythonhosted.org/packages/fe/a8/09b16a4455eae951d29ef15054b2d9e73a287160b151a4b939e4068a24fd/polyglot_sql_chio-0.9.3-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3605f24ba6b2802d0ef61d8d094e6cee5d179a64d6a909f419a57fe9d8871a23", size = 10580844, upload-time = "2026-09-10T00:17:31.334Z" }, +name = "polyglot-sql" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/bb/3e60486d308cd29baa09c214528d8fffa81fc6221ac3ab1f0acb9f79fb40/polyglot_sql-0.11.0.tar.gz", hash = "sha256:e504c00684ee878b25433cdc4b5daeb096606c6025632a57346e4a2398c85a52", size = 1915236, upload-time = "2026-09-16T13:44:57.26Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/89/89818644cd56e7cf0ff41743a95d3a54a1ba28c89acaf6da26c01b720796/polyglot_sql-0.11.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:955076f56806c3b566fcb982c26bde62ebe1b879108c9c45eae723fab461d078", size = 11688358, upload-time = "2026-09-16T13:44:01.622Z" }, + { url = "https://files.pythonhosted.org/packages/5a/2e/d54bf5ddbc0d755a444401e497cd7223e566fe3e46478b7db6eeccee903c/polyglot_sql-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a17e06999e0ada81604742819d789433a8911ddf16327d89b8383d8e8625ae9", size = 11022059, upload-time = "2026-09-16T13:44:04.077Z" }, + { url = "https://files.pythonhosted.org/packages/c6/5a/67225199c258add9472acd5cecff8529f1b4bccb0af35fa6d1d71a5f6130/polyglot_sql-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928863c8ce70b17d87a655ffeef598524e64230391a0d70b085b3cec978496b", size = 11849411, upload-time = "2026-09-16T13:44:06.552Z" }, + { url = "https://files.pythonhosted.org/packages/2f/1b/8615412a3f14f32da812866fc134a89b51b70f3aaa4fe1690a538206e856/polyglot_sql-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7d4095a5e0b9c45d397d050e727c71d0f48b787ecc42228777ed10e2fc22578", size = 10869867, upload-time = "2026-09-16T13:44:08.878Z" }, + { url = "https://files.pythonhosted.org/packages/3c/31/23108146b5de65f83955be9cfd4205bee24c0154359ca3e1ab34ab7720b2/polyglot_sql-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:0071af0022d891ac5234383111619c6534dcd5acb6681d251bcdeeb19b654264", size = 10662540, upload-time = "2026-09-16T13:44:11.363Z" }, + { url = "https://files.pythonhosted.org/packages/b6/40/19f9e3f13d588c02a71b1aed462006dc063cac8e334d3db568fc6241717b/polyglot_sql-0.11.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:087ea83398f33ba5fad869ca77953141778a607b5d71ff18cb13bbdf77d0f48b", size = 11688451, upload-time = "2026-09-16T13:44:13.547Z" }, + { url = "https://files.pythonhosted.org/packages/59/4d/961019c4e3e8ec7793ee030c170a3d56b5a1d3aba153a27e89990a900153/polyglot_sql-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:245d0c4aebe4f47a2f5f74770f588aaad8e95b60a3dd192b7882acb997aef163", size = 11022291, upload-time = "2026-09-16T13:44:15.829Z" }, + { url = "https://files.pythonhosted.org/packages/ad/af/5186612b8c6293d59dd8ac225250d060d46b08af46bdb4f7c9672b7d9cc6/polyglot_sql-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1610bd0f5fd9399077c1cd174f2bb72508f12b2f6adfaa409e456afe841c1651", size = 11849762, upload-time = "2026-09-16T13:44:18.201Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a3/a99de05374b27a1b67c1d87419569234dd563eff00461cfd209e8e433c96/polyglot_sql-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3e73273aceac69ad924beaec71c3046ae7771d8389f166d2c0c1eeeea9076db", size = 10869177, upload-time = "2026-09-16T13:44:20.696Z" }, + { url = "https://files.pythonhosted.org/packages/8a/7c/bb9ff3da17c0ab5508a0d68e8324dbe0306d74c02acf2fcf5bcbf9801709/polyglot_sql-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:f35d50aee210625413ec90ae5fd1cfca9cb552415ae1e984662af795af525db7", size = 10662753, upload-time = "2026-09-16T13:44:22.919Z" }, + { url = "https://files.pythonhosted.org/packages/9a/c2/a53bf3552adb5b35b97a0525d054f3dc45c08627069e379f39ce29115715/polyglot_sql-0.11.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:248af7dc342c7ac67357a8b81a99090949bc9a2e5e160c4f8dc0347f81cf7cde", size = 11690665, upload-time = "2026-09-16T13:44:25.236Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b3/ba45e7a2b8267fa8aa94c0df6e9489441e34a04fb40cf282d3ea508ad9f0/polyglot_sql-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b3cc1a6087617544916371323aaca768e4176d161a296f2cf8ac1010de0d79e7", size = 11024851, upload-time = "2026-09-16T13:44:27.663Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a6/54664d1d4fb69980ac898c64b718c26616db339129c69574c681d11224fb/polyglot_sql-0.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb92979fc9dff12b96a354bb12123a76691d447aa67747047dbf3ea7e21b7ac2", size = 11849921, upload-time = "2026-09-16T13:44:30.131Z" }, + { url = "https://files.pythonhosted.org/packages/7f/4a/0c8678b42932dcc649fa602b12dda58a8e8a27cf4e7db68e5832c8547970/polyglot_sql-0.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cba73a661835769af6dee21c44aefa969e49cdf0954aba548155afd747e508b9", size = 10870596, upload-time = "2026-09-16T13:44:32.506Z" }, + { url = "https://files.pythonhosted.org/packages/92/6a/580c46b88263f0da637aeaf524ab84810cd5a2c720ececf3ef81c606497c/polyglot_sql-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:54aa8bd89d119ffa264756972a44d548e600e3d8d1d400416aa8924b6bff6e41", size = 10663183, upload-time = "2026-09-16T13:44:35.057Z" }, + { url = "https://files.pythonhosted.org/packages/46/5b/392a00b7a18bfc9af4b790acdbcf45e5c3fbd794060b7d2aa4116dbbc7b4/polyglot_sql-0.11.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8013bbd5f4ce53980f58919bdede2d397e21f6c04985cc13bf4f510881cc7380", size = 11950739, upload-time = "2026-09-16T13:44:37.367Z" }, + { url = "https://files.pythonhosted.org/packages/e9/55/8c9e2e7e5970dfc66943d9a5671a59c438a7812efc052619b96c9aa1de24/polyglot_sql-0.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b9b9eb16724e2b96fdcf487e69a2047d91ea309e8d9080125c55598ef336fcf", size = 10944134, upload-time = "2026-09-16T13:44:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5f/8d47b870e93f69bdf02e0d767b4d722ed6d29f784a6958dac7b8881c380e/polyglot_sql-0.11.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b362bcb815ecb0e46cbd2cf7be6e17caa1424352fcca381c531a53459b14c3f6", size = 10870596, upload-time = "2026-09-16T13:44:41.894Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9c/3cff0f115fc1505124727a7ced3dc8f88900e8dc860d2ec476dcc4679cee/polyglot_sql-0.11.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f1408701b8a867dae2fa1775985f803dab73c71f9dbccfff70bee74696936f", size = 10944136, upload-time = "2026-09-16T13:44:44.388Z" }, ] [[package]] @@ -4157,7 +4157,7 @@ dependencies = [ { name = "duckdb" }, { name = "filelock" }, { name = "orjson" }, - { name = "polyglot-sql-chio" }, + { name = "polyglot-sql" }, { name = "pydantic-settings" }, { name = "pyyaml" }, { name = "rich" }, @@ -4232,7 +4232,7 @@ requires-dist = [ { name = "ingestr", marker = "extra == 'ingestr'", specifier = ">=1.0.0" }, { name = "jinja2", marker = "extra == 'dbt'", specifier = ">=3.1" }, { name = "orjson", specifier = ">=3.10.0" }, - { name = "polyglot-sql-chio", specifier = "==0.9.3" }, + { name = "polyglot-sql", specifier = "==0.11.0" }, { name = "psycopg", extras = ["binary"], marker = "extra == 'postgres'", specifier = ">=3.2.0" }, { name = "pydantic-settings", specifier = ">=2,<3" }, { name = "pymssql", marker = "extra == 'sqlserver'", specifier = ">=2.3.0" },