Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1486 +/- ##
==========================================
- Coverage 92.88% 92.80% -0.09%
==========================================
Files 142 142
Lines 13864 13875 +11
Branches 151 151
==========================================
- Hits 12878 12877 -1
- Misses 917 929 +12
Partials 69 69
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
✅ 1029/1029 passed, 63 skipped, 5h31m56s total Running from acceptance #5899 |
|
✅ 1/1 passed, 26m5s total Running from mcp #648 |
|
✅ 195/195 passed, 1 skipped, 6h51m1s total Running from anomaly #2013 |
There was a problem hiding this comment.
Pull request overview
This PR addresses #1481 by ensuring column names that require SQL identifier escaping (spaces, non-ASCII, etc.) are safely handled during check execution (not just validation), preventing INVALID_IDENTIFIER failures when building Spark expressions.
Changes:
- Add
utils.normalize_column_expr()and use it incheck_funcs._get_column_expr()so string column inputs are safely passed toF.expr. - Adjust
get_normalized_column_and_expr()so display/normalized names come from the original user input string (not the escaped Spark expression). - Extend unit + integration coverage, including contract-rule generation for escaped columns.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/databricks/labs/dqx/utils.py |
Introduces normalize_column_expr() and supporting regexes to decide when to back-quote identifiers vs pass through SQL expressions. |
src/databricks/labs/dqx/check_funcs.py |
Uses normalize_column_expr() when converting string inputs via F.expr, and tweaks name normalization/display behavior. |
src/databricks/labs/dqx/datacontract/contract_rules_generator.py |
Uses normalize_column_expr() when embedding column names into generated sql_expression rules. |
tests/unit/test_utils.py |
Adds parametric unit tests for normalize_column_expr() behavior (identifiers, nested paths, expressions). |
tests/unit/test_row_checks.py |
Adds unit test ensuring normalized/display names derive from the raw input column string. |
tests/unit/test_datacontract_generator.py |
Adds unit test verifying generated SQL expressions back-quote a column requiring escaping. |
tests/integration/test_apply_checks.py |
Adds integration tests proving row-level checks run end-to-end on columns requiring escaping (metadata + class-based APIs). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| col_expr = _get_column_expr(column) | ||
| column_str = get_column_name_or_alias(col_expr) | ||
| col_str_norm = get_column_name_or_alias(col_expr, normalize=True) | ||
| if isinstance(column, str): |
There was a problem hiding this comment.
shouldn't this logic be implemented inside get_column_name_or_alias?
We already handle the case there:
if isinstance(column, str):
col_str = column
we just need to extend it with an option to normalize:
if isinstance(column, str):
if normalize:
col_str = normalize_col_str(column)
else:
col_str = column
Then we don't need to change check_funcs.py
column_str = get_column_name_or_alias(col_expr)
col_str_norm = get_column_name_or_alias(col_expr, normalize=True)
There was a problem hiding this comment.
This is still not resolved. The current implementation branches on isinstance(column, str) inside get_normalized_column_and_expr and calls unquote_column_name + normalize_col_str directly, rather than adding a normalize option to get_column_name_or_alias as suggested — so check_funcs.py was still changed. Leaving open to confirm whether we want to centralize this in get_column_name_or_alias instead.
| return column | ||
|
|
||
| return ".".join( | ||
| segment if VALID_UNQUOTED_IDENTIFIER_PATTERN.match(segment) else quote_column_name(segment) |
There was a problem hiding this comment.
Finding: a column whose name equals a zero-arg SQL function resolves to the function, not the column (silent wrong result)
A bare identifier that matches VALID_UNQUOTED_IDENTIFIER_PATTERN is returned unchanged, so a real column literally named current_date, current_timestamp, or current_user is passed to F.expr unquoted and evaluates the SQL function instead of the column.
Failure scenario: a DataFrame has a column named current_date. normalize_column_expr("current_date") returns "current_date" (valid identifier, left alone), so F.expr("current_date") yields today's date rather than the column's value. is_not_null etc. then check the wrong value with no error surfaced. Only closable by always back-quoting a name-classified segment, or schema-aware resolution. Lower priority (needs an unusually-named column), flagging for awareness.
There was a problem hiding this comment.
This is also DBSQL's convention. Users must escape zero-arg identifiers in any DBSQL query if they use them as column names. Should we change this or align with DBSQL?
There was a problem hiding this comment.
sounds good, I think it's better to stay aligned with DBSQL
| The column name without surrounding back-quotes. | ||
| """ | ||
| if len(name) >= 2 and name.startswith("`") and name.endswith("`"): | ||
| return name[1:-1].replace("``", "`") |
There was a problem hiding this comment.
Finding: dotted path of separately back-quoted segments is mangled (cosmetic)
unquote_column_name only strips the outer back-quotes, so a path whose segments are individually quoted comes back malformed.
Failure scenario: column="Odd Name.field" → unquote_column_name(...) returns "Odd Name.field", from which normalize_col_str derives a garbled check name and the _errors/_warnings message shows a corrupted column name. Resolution itself is fine (normalize_column_expr leaves the string unchanged and F.expr resolves it), so this is display-only — but user-visible. Low severity.
|
|
||
| def normalize_column_expr(column: str) -> str: | ||
| """ | ||
| Prepares a column reference string for use with ``F.expr``, back-quoting names that require SQL |
There was a problem hiding this comment.
Nit: docstring uses double-backtick object references, against the repo convention
AGENTS.md (Docstrings) says: No backticks around object names — use italics instead (e.g., arg1, column). Backticks cause rendering issues in API docs. This docstring uses F.expr (and quote_column_name / unquote_column_name use F.expr and :func: cross-refs) — should be italics, e.g. F.expr. Note _get_column_expr in check_funcs.py already does this correctly (normalize_column_expr).
mwojtyczka
left a comment
There was a problem hiding this comment.
Previous comments all addressed except 1, added a few new comments
|
@mwojtyczka ready for another review. |
Changes
This PR adds escaping for column names during check execution.
Note: Auto-generated check names (e.g. when checks are not named by the user) are normalized to replace reserved characters with
_.Linked issues
Resolves #1481
Tests
Documentation and Demos