Skip to content

Commit 0ee4e71

Browse files
committed
chore: update Databricks adapter to version 1.10.15-5 and add SQL binding formatting method
### Description This commit updates the Databricks adapter version to 1.10.15-5 and introduces a new static method `format_bindings_for_sql` in the `SqlUtils` class. This method formats bindings as SQL literals for safe string substitution in session mode, enhancing SQL injection safety and syntax correctness. Additionally, the `SessionCursorWrapper` class has been updated to utilize this new method for handling bindings during SQL execution. A comprehensive set of unit tests for session mode components has also been added to ensure functionality and correctness.
1 parent 2e133b3 commit 0ee4e71

4 files changed

Lines changed: 450 additions & 8 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version = "1.10.15-3"
1+
version = "1.10.15-5"

dbt/adapters/databricks/handle.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,47 @@ def translate_bindings(bindings: Optional[Sequence[Any]]) -> Optional[Sequence[A
288288
return list(map(lambda x: float(x) if isinstance(x, decimal.Decimal) else x, bindings))
289289
return None
290290

291+
@staticmethod
292+
def format_bindings_for_sql(bindings: Optional[Sequence[Any]]) -> Optional[Sequence[str]]:
293+
"""
294+
Format bindings as SQL literals for string substitution in session mode.
295+
296+
This method properly quotes string values and handles special cases to ensure
297+
SQL injection safety and correct SQL syntax. Used when executing SQL via
298+
SparkSession.sql() which doesn't support parameterized queries.
299+
300+
Args:
301+
bindings: Sequence of binding values (strings, numbers, None, etc.)
302+
303+
Returns:
304+
Sequence of SQL literal strings, or None if bindings is None/empty
305+
"""
306+
if not bindings:
307+
return None
308+
309+
formatted = []
310+
for value in bindings:
311+
if value is None:
312+
formatted.append("NULL")
313+
elif isinstance(value, bool):
314+
formatted.append("TRUE" if value else "FALSE")
315+
elif isinstance(value, str):
316+
# Escape single quotes by doubling them, then wrap in quotes
317+
escaped = value.replace("'", "''")
318+
formatted.append(f"'{escaped}'")
319+
elif isinstance(value, (int, float, decimal.Decimal)):
320+
# Numbers don't need quotes
321+
if isinstance(value, decimal.Decimal):
322+
formatted.append(str(float(value)))
323+
else:
324+
formatted.append(str(value))
325+
else:
326+
# For other types, convert to string and quote
327+
escaped = str(value).replace("'", "''")
328+
formatted.append(f"'{escaped}'")
329+
330+
return formatted
331+
291332
@staticmethod
292333
def clean_sql(sql: str) -> str:
293334
cleaned = sql.strip()

dbt/adapters/databricks/session.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,14 @@ def execute(
5050
"""Execute a SQL statement and store the resulting DataFrame."""
5151
cleaned_sql = SqlUtils.clean_sql(sql)
5252

53-
# Handle bindings by simple string substitution if provided
53+
# Handle bindings by formatting as SQL literals and substituting
54+
# This ensures string values are properly quoted (e.g., 'k. A.' instead of k. A.)
5455
if bindings:
5556
translated = SqlUtils.translate_bindings(bindings)
5657
if translated:
57-
cleaned_sql = cleaned_sql % tuple(translated)
58+
formatted_bindings = SqlUtils.format_bindings_for_sql(translated)
59+
if formatted_bindings:
60+
cleaned_sql = cleaned_sql % tuple(formatted_bindings)
5861

5962
logger.debug(f"Session mode executing SQL: {cleaned_sql[:200]}...")
6063
self._df = self._spark.sql(cleaned_sql)
@@ -165,7 +168,8 @@ def create(
165168
166169
Args:
167170
catalog: Optional catalog to set as current
168-
schema: Optional schema to set as current database
171+
schema: Optional schema (not used - dbt uses fully qualified names and
172+
creates schemas during execution)
169173
session_properties: Optional session configuration properties
170174
171175
Returns:
@@ -246,10 +250,12 @@ def create(
246250
# Fall back to USE CATALOG for older Spark versions
247251
spark.sql(f"USE CATALOG {catalog}")
248252

249-
# Set schema/database if provided
250-
if schema:
251-
spark.catalog.setCurrentDatabase(schema)
252-
logger.debug(f"Set current database to: {schema}")
253+
# Note: We intentionally do NOT call setCurrentDatabase(schema) here.
254+
# The schema from the profile is used as a base/prefix for generated schema names
255+
# (e.g., "dbt" -> "dbt_seeds", "dbt_bronze" via generate_schema_name macro).
256+
# dbt creates these schemas during execution via CREATE SCHEMA IF NOT EXISTS,
257+
# and uses fully qualified names (catalog.schema.table) for all operations.
258+
# Setting the current database would fail if the schema doesn't exist yet.
253259

254260
# Apply session properties
255261
if session_properties:

0 commit comments

Comments
 (0)