|
11 | 11 | from datetime import date, datetime |
12 | 12 | from decimal import Decimal |
13 | 13 | from pathlib import Path |
14 | | -from typing import Any, ClassVar |
| 14 | +from typing import Any, ClassVar, cast |
15 | 15 |
|
16 | 16 | from sqlbuild._native import ( |
17 | 17 | SeedCsvSettings, |
18 | 18 | SourceFreshnessRecord, |
19 | 19 | SqlLexicalSyntax, |
20 | 20 | SqlValue, |
| 21 | + SqlValueKind, |
21 | 22 | render_insert_source_freshness_records_sql, |
22 | 23 | ) |
23 | 24 | from sqlbuild.adapter.contract.classes.base_adapter import ( |
24 | 25 | BaseAdapter, |
25 | 26 | _encode_typed_json, |
26 | 27 | _render_ansi_typed_scalar, |
27 | 28 | _render_typed_value_list, |
| 29 | + _typed_scalar_payload, |
28 | 30 | ) |
29 | 31 | from sqlbuild.adapter.contract.classes.consumption import ConsumptionMixin |
30 | 32 | from sqlbuild.adapter.contract.classes.historical_check_snapshot_sql import ( |
|
132 | 134 | DETAILED_TABLE_INFORMATION_MARKER, |
133 | 135 | NON_ROW_RESULT_COLUMN_NAMES, |
134 | 136 | RELATION_NOT_FOUND_ERROR_CLASSES, |
| 137 | + STRING_LITERAL_ESCAPES, |
135 | 138 | TABLE_RELATION_METADATA_TYPES, |
136 | 139 | TEMPORARY_FUNCTION_PREFIX, |
137 | 140 | UNDEFINED_TABLE_SQLSTATE, |
@@ -884,6 +887,8 @@ def render_loader_logical_type(self, type_name: LoaderLogicalType) -> str: |
884 | 887 | return "STRING" |
885 | 888 |
|
886 | 889 | def render_typed_scalar(self, *, value: SqlValue) -> str: |
| 890 | + if value.kind == SqlValueKind.STRING: |
| 891 | + return self._quote_sql_string(cast(str, _typed_scalar_payload(value))) |
887 | 892 | return _render_ansi_typed_scalar(value=value) |
888 | 893 |
|
889 | 894 | def render_typed_value_list(self, *, value: SqlValue) -> str: |
@@ -961,7 +966,7 @@ def _loader_row_sql_type(self, column_type: str | None) -> str: |
961 | 966 | return self._to_databricks_type(column_type) |
962 | 967 |
|
963 | 968 | def _quote_sql_string(self, value: str) -> str: |
964 | | - return "'" + value.replace("'", "''") + "'" |
| 969 | + return "'" + value.translate(STRING_LITERAL_ESCAPES) + "'" |
965 | 970 |
|
966 | 971 | def render_create_initial_snapshot_destination( |
967 | 972 | self, |
@@ -1830,8 +1835,8 @@ def render_cursor_bound_literal(self, *, value: str, cursor_type: str | None) -> |
1830 | 1835 | if cursor_type == CursorKind.INTEGER: |
1831 | 1836 | return value |
1832 | 1837 | if cursor_type == CursorKind.TIMESTAMP: |
1833 | | - return f"TIMESTAMP '{value}'" |
1834 | | - return f"'{value}'" |
| 1838 | + return f"TIMESTAMP {self._quote_sql_string(value)}" |
| 1839 | + return self._quote_sql_string(value) |
1835 | 1840 |
|
1836 | 1841 | def render_drop(self, *, destination: str, if_exists: bool = True) -> tuple[str, ...]: |
1837 | 1842 | exists_clause: str = " IF EXISTS" if if_exists else "" |
@@ -1960,13 +1965,9 @@ def read_view_definition( |
1960 | 1965 | connection=connection, |
1961 | 1966 | sql=( |
1962 | 1967 | f"SELECT view_definition FROM {views} WHERE lower(table_schema) = lower(" |
1963 | | - + "'" |
1964 | | - + schema.replace("'", "''") |
1965 | | - + "'" |
| 1968 | + + self._quote_sql_string(schema) |
1966 | 1969 | + ") AND lower(table_name) = lower(" |
1967 | | - + "'" |
1968 | | - + name.replace("'", "''") |
1969 | | - + "'" |
| 1970 | + + self._quote_sql_string(name) |
1970 | 1971 | + ")" |
1971 | 1972 | ), |
1972 | 1973 | ).fetchall() |
@@ -2444,7 +2445,7 @@ def render_max_cursor_at_or_before( |
2444 | 2445 | ) -> str: |
2445 | 2446 | quoted_cursor: str = self.render_identifier(cursor_column) |
2446 | 2447 | maximum_literal: str = ( |
2447 | | - f"CAST('{maximum_allowed}' AS DATE)" |
| 2448 | + f"CAST({self._quote_sql_string(maximum_allowed)} AS DATE)" |
2448 | 2449 | if is_date |
2449 | 2450 | else self.render_cursor_bound_literal(value=maximum_allowed, cursor_type=cursor_type) |
2450 | 2451 | ) |
@@ -3191,8 +3192,7 @@ def _normalize_session_value(value: object | None) -> str | None: |
3191 | 3192 |
|
3192 | 3193 | @staticmethod |
3193 | 3194 | def _string_literal(value: str) -> str: |
3194 | | - escaped_value: str = value.replace("'", "''") |
3195 | | - return f"'{escaped_value}'" |
| 3195 | + return "'" + value.translate(STRING_LITERAL_ESCAPES) + "'" |
3196 | 3196 |
|
3197 | 3197 | @staticmethod |
3198 | 3198 | def _information_schema(database: str) -> str: |
@@ -3221,13 +3221,13 @@ def _to_databricks_type(column_type: str) -> str: |
3221 | 3221 | def _render_cursor_value_literal(cursor_value: CursorValue) -> str: |
3222 | 3222 | if cursor_value.kind == CursorKind.INTEGER: |
3223 | 3223 | return str(cursor_value.value) |
3224 | | - return f"TIMESTAMP '{cursor_value.value}'" |
| 3224 | + return f"TIMESTAMP '{str(cursor_value.value).translate(STRING_LITERAL_ESCAPES)}'" |
3225 | 3225 |
|
3226 | 3226 | @staticmethod |
3227 | 3227 | def _render_cursor_bound_string(value: str) -> str: |
3228 | 3228 | if value.isdigit(): |
3229 | 3229 | return value |
3230 | | - return f"TIMESTAMP '{value}'" |
| 3230 | + return f"TIMESTAMP '{value.translate(STRING_LITERAL_ESCAPES)}'" |
3231 | 3231 |
|
3232 | 3232 | @staticmethod |
3233 | 3233 | def _to_int(value: object) -> int: |
|
0 commit comments