5353from databricks .labs .dqx .errors import InvalidCheckError , InvalidConfigError , InvalidParameterError
5454from databricks .labs .dqx .utils import list_tables , safe_strip_file_from_path , resolve_variables , VariableValue
5555from databricks .labs .dqx .io import is_one_time_trigger
56- from .semantic_validator import SemanticValidator
56+ from .semantic_validator import SemanticValidator , SemanticValidationMode
5757logger = logging .getLogger (__name__ )
5858
59-
6059class DQEngineCore (DQEngineCoreBase ):
6160 """Core engine to apply data quality checks to a DataFrame.
6261
@@ -291,43 +290,46 @@ def apply_checks_by_metadata_and_split(
291290 return good_df , bad_df
292291
293292 @staticmethod
294- def validate_checks (
293+ def validate_checks (
295294 checks : list [dict ],
296295 custom_check_functions : dict [str , Callable ] | None = None ,
297296 validate_custom_check_functions : bool = True ,
297+ semantic_validation_mode : str | None = SemanticValidationMode .WARN ,
298298 ) -> ChecksValidationStatus :
299299 """
300300 Validate checks defined as metadata to ensure they conform to the expected
301- structure and types.
302-
303- This method validates the presence of required keys, the existence and
304- callability of functions, the types of arguments passed to those functions,
305- and performs semantic validation to reject destructive SQL expressions.
306-
301+ structure and types, and are semantically consistent as a ruleset.
302+
303+ Structural validation checks for required keys, callable functions, and
304+ correct argument types. Semantic validation detects duplicate rules and
305+ similar rules with conflicting arguments (e.g. two is_in_range checks on
306+ the same column with different thresholds).
307+
308+ Note:
309+ Rules using raw Spark SQL expressions are not deeply inspected during
310+ semantic validation — only structured metadata is compared.
311+
307312 Args:
308313 checks: List of checks to apply to the DataFrame. Each check should be a dictionary.
309314 custom_check_functions: Optional dictionary with custom check functions
310315 (e.g., *globals()* of the calling module).
311316 validate_custom_check_functions: If True, validate custom check functions.
312-
317+ semantic_validation_mode: Controls how semantic issues are surfaced.
318+ Use ``SemanticValidationMode.WARN`` (default) to log warnings,
319+ ``SemanticValidationMode.FAIL`` to raise on any issue, or
320+ ``None`` to skip semantic validation entirely.
321+
313322 Returns:
314- ChecksValidationStatus indicating the validation result.
323+ ChecksValidationStatus indicating the structural validation result.
324+
325+ Raises:
326+ ValueError: If semantic_validation_mode is FAIL and issues are found.
315327 """
316- # Run structural validation first; collect all errors into the status object.
317328 status = ChecksValidator .validate_checks (checks , custom_check_functions , validate_custom_check_functions )
318-
319- # Run semantic validation on top; add any findings into the same status.
320- for check in checks :
321- if not isinstance (check , dict ):
322- continue
323- expr = check .get ("kwargs" , {}).get ("expression" ) or check .get ("expression" )
324- if not expr :
325- continue
326- try :
327- SemanticValidator .validate_sql_expression (expr )
328- except ValueError as e :
329- status .add_error (str (e ))
330-
329+
330+ if semantic_validation_mode is not None :
331+ SemanticValidator .apply (checks , mode = semantic_validation_mode )
332+
331333 return status
332334
333335 def get_invalid (self , df : DataFrame ) -> DataFrame :
@@ -1205,44 +1207,55 @@ def save_results_in_table(
12051207 )
12061208
12071209 @telemetry_logger ("engine" , "load_checks" )
1208- def load_checks (
1209- self , config : BaseChecksStorageConfig , variables : dict [str , VariableValue ] | None = None
1210+ def load_checks (
1211+ self ,
1212+ config : BaseChecksStorageConfig ,
1213+ variables : dict [str , VariableValue ] | None = None ,
1214+ semantic_validation_mode : str | None = SemanticValidationMode .WARN , # <-- ADD THIS
12101215 ) -> list [dict ]:
12111216 """Load DQ rules (checks) from the storage backend described by *config*.
1212-
1217+
12131218 This method delegates to a storage handler selected by the factory
12141219 based on the concrete type of *config* and returns the parsed list
12151220 of checks (as dictionaries) ready for *apply_checks_by_metadata*.
1216-
1221+
12171222 Supported storage configurations include, for example:
12181223 - *FileChecksStorageConfig* (local file);
12191224 - *WorkspaceFileChecksStorageConfig* (Databricks workspace file);
12201225 - *TableChecksStorageConfig* (table-backed storage);
12211226 - *LakebaseChecksStorageConfig* (Lakebase table);
12221227 - *InstallationChecksStorageConfig* (installation directory);
12231228 - *VolumeFileChecksStorageConfig* (Unity Catalog volume file);
1224-
1229+
12251230 Per-call *variables* are merged with engine-level defaults from
12261231 *ExtraParams.variables* (per-call values take precedence on conflict).
1227-
1232+
12281233 **Security note:** variable values substituted into **sql_expression** checks are
12291234 not sanitized. Callers must ensure that variable values come from trusted sources.
1230-
1235+
12311236 Args:
12321237 config: Configuration object describing the storage backend.
12331238 variables: Optional mapping of placeholder names to replacement values. Replaces placeholders
12341239 in all string values of the check definitions before returning.
1235-
1240+ semantic_validation_mode: Controls semantic validation behavior after loading.
1241+ Use ``SemanticValidationMode.WARN`` (default) to log warnings and continue,
1242+ ``SemanticValidationMode.FAIL`` to raise if issues are found, or
1243+ ``None`` to skip semantic validation entirely.
1244+
12361245 Returns:
12371246 List of DQ rules (checks) represented as dictionaries.
1238-
1247+
12391248 Raises:
12401249 InvalidConfigError: If the configuration type is unsupported.
1250+ ValueError: If semantic_validation_mode is FAIL and issues are found.
12411251 """
12421252 handler = self ._checks_handler_factory .create (config )
12431253 checks = handler .load (config )
12441254 merged_variables = self ._merge_variables (variables )
1245- return resolve_variables (checks = checks , variables = merged_variables )
1255+ resolved = resolve_variables (checks = checks , variables = merged_variables ) # <-- RENAME from return value
1256+ if semantic_validation_mode is not None : # <-- ADD THIS
1257+ SemanticValidator .apply (resolved , mode = semantic_validation_mode ) # <-- ADD THIS
1258+ return resolved # <-- CHANGE return
12461259
12471260 def _merge_variables (self , per_call : dict [str , VariableValue ] | None ) -> dict [str , VariableValue ] | None :
12481261 """Merge engine-level default variables with per-call overrides.
@@ -1259,46 +1272,54 @@ def _merge_variables(self, per_call: dict[str, VariableValue] | None) -> dict[st
12591272 return {** defaults , ** per_call }
12601273
12611274 @telemetry_logger ("engine" , "save_checks" )
1262- def save_checks (
1275+ def save_checks (
12631276 self ,
12641277 checks : list [dict ],
12651278 config : BaseChecksStorageConfig ,
12661279 variables : dict [str , VariableValue ] | None = None ,
1280+ semantic_validation_mode : str | None = SemanticValidationMode .WARN , # <-- ADD THIS
12671281 ) -> None :
12681282 """Persist DQ rules (checks) to the storage backend described by *config*.
1269-
1283+
12701284 The appropriate storage handler is resolved from the configuration
12711285 type and used to write the provided checks. Any write semantics
12721286 (e.g., append/overwrite) are controlled by fields on *config*
12731287 such as *mode* where applicable.
1274-
1288+
12751289 Supported storage configurations include, for example:
12761290 - *FileChecksStorageConfig* (local file);
12771291 - *WorkspaceFileChecksStorageConfig* (Databricks workspace file);
12781292 - *TableChecksStorageConfig* (table-backed storage);
12791293 - *LakebaseChecksStorageConfig* (Lakebase table);
12801294 - *InstallationChecksStorageConfig* (installation directory);
12811295 - *VolumeFileChecksStorageConfig* (Unity Catalog volume file);
1282-
1296+
12831297 Per-call *variables* are merged with engine-level defaults from
12841298 *ExtraParams.variables* (per-call values take precedence on conflict).
12851299 Variables are resolved before computing fingerprints and persisting,
12861300 ensuring that stored checks and their fingerprints are consistent.
1287-
1301+
12881302 Args:
12891303 checks: List of DQ rules (checks) to save (as dictionaries).
12901304 config: Configuration object describing the storage backend and write options.
12911305 variables: Optional mapping of placeholder names to replacement values. Replaces placeholders
12921306 in all string values of the check definitions before saving.
1293-
1307+ semantic_validation_mode: Controls semantic validation behavior before saving.
1308+ Use ``SemanticValidationMode.WARN`` (default) to log warnings and continue,
1309+ ``SemanticValidationMode.FAIL`` to abort saving if issues are found, or
1310+ ``None`` to skip semantic validation entirely.
1311+
12941312 Returns:
12951313 None
1296-
1314+
12971315 Raises:
12981316 InvalidConfigError: If the configuration type is unsupported.
1317+ ValueError: If semantic_validation_mode is FAIL and issues are found.
12991318 """
13001319 merged_variables = self ._merge_variables (variables )
13011320 resolved_checks = resolve_variables (checks = checks , variables = merged_variables )
1321+ if semantic_validation_mode is not None : # <-- ADD THIS
1322+ SemanticValidator .apply (resolved_checks , mode = semantic_validation_mode ) # <-- ADD THIS
13021323 handler = self ._checks_handler_factory .create (config )
13031324 handler .save (resolved_checks , config )
13041325
0 commit comments