Skip to content

Commit

Permalink
Update identifier escaping logic #1055
Browse files Browse the repository at this point in the history
Signed-off-by: Marcel Coetzee <[email protected]>
  • Loading branch information
Pipboyguy committed Mar 14, 2024
1 parent 0df32f5 commit c1f106b
Show file tree
Hide file tree
Showing 2 changed files with 9 additions and 47 deletions.
41 changes: 2 additions & 39 deletions dlt/common/data_writers/escape.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,42 +137,5 @@ def escape_databricks_literal(v: Any) -> Any:
return "NULL" if v is None else str(v)


# https://github.com/ClickHouse/ClickHouse/blob/master/docs/en/sql-reference/syntax.md#string
CLICKHOUSE_ESCAPE_DICT = {
"'": "''",
"\\": "\\\\",
"\n": "\\n",
"\t": "\\t",
"\b": "\\b",
"\f": "\\f",
"\r": "\\r",
"\0": "\\0",
"\a": "\\a",
"\v": "\\v",
}

CLICKHOUSE_ESCAPE_RE = _make_sql_escape_re(CLICKHOUSE_ESCAPE_DICT)


def escape_clickhouse_literal(v: Any) -> Any:
if isinstance(v, str):
return _escape_extended(
v, prefix="'", escape_dict=CLICKHOUSE_ESCAPE_DICT, escape_re=CLICKHOUSE_ESCAPE_RE
)
if isinstance(v, (datetime, date, time)):
return f"'{v.isoformat()}'"
if isinstance(v, (list, dict)):
return _escape_extended(
json.dumps(v),
prefix="'",
escape_dict=CLICKHOUSE_ESCAPE_DICT,
escape_re=CLICKHOUSE_ESCAPE_RE,
)
if isinstance(v, bytes):
return f"'{v.hex()}'"
return "NULL" if v is None else str(v)


def escape_clickhouse_identifier(v: str, quote_char: str = "`") -> str:
quote_char = quote_char if quote_char in {'"', "`"} else "`"
return quote_char + v.replace(quote_char, quote_char * 2).replace("\\", "\\\\") + quote_char
def escape_clickhouse_identifier(v: str) -> str:
return '`' + v.replace('`', '``').replace("\\", "\\\\") + '"'
15 changes: 7 additions & 8 deletions dlt/destinations/impl/clickhouse/__init__.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
from dlt.common.arithmetics import DEFAULT_NUMERIC_PRECISION, DEFAULT_NUMERIC_SCALE
from dlt.common.data_writers.escape import escape_clickhouse_identifier, escape_clickhouse_literal
from dlt.common.data_writers.escape import escape_clickhouse_identifier
from dlt.common.destination import DestinationCapabilitiesContext


def capabilities() -> DestinationCapabilitiesContext:
caps = DestinationCapabilitiesContext()

caps.preferred_loader_file_format = "parquet"
caps.supported_loader_file_formats = ["jsonl", "parquet", "insert_values"]
caps.preferred_staging_file_format = "parquet"
caps.supported_staging_file_formats = ["jsonl", "parquet"]
caps.preferred_loader_file_format = "jsonl"
caps.supported_loader_file_formats = ["jsonl", "parquet", "arrow"]
caps.preferred_staging_file_format = "jsonl"
caps.supported_staging_file_formats = ["jsonl", "parquet", "arrow"]

caps.escape_identifier = escape_clickhouse_identifier
caps.escape_literal = escape_clickhouse_literal

caps.schema_supports_numeric_precision = True
# Use 'Decimal128' with these defaults.
Expand All @@ -25,8 +24,8 @@ def capabilities() -> DestinationCapabilitiesContext:
caps.is_max_query_length_in_bytes = True
caps.max_query_length = 262144

# Clickhouse has limited support for transactional semantics, especially for `ReplicatedMergeTree`, the default ClickHouse Cloud engine.
# It does, however, provide atomicity for individual DDL operations like `ALTER TABLE`.
# Clickhouse has limited support for transactional semantics, especially for `ReplicatedMergeTree`,
# the default ClickHouse Cloud engine. It does, however, provide atomicity for individual DDL operations like `ALTER TABLE`.
# https://clickhouse-driver.readthedocs.io/en/latest/dbapi.html#clickhouse_driver.dbapi.connection.Connection.commit
# https://clickhouse.com/docs/en/guides/developer/transactional#transactions-commit-and-rollback
caps.supports_transactions = False
Expand Down

0 comments on commit c1f106b

Please sign in to comment.