3737from sqlbuild .compiler .discovery .constants import SQL_ANALYSIS_CONFIG_KEY
3838from sqlbuild .compiler .sql_analysis .constants import BINDING_UNKNOWN_TABLE_INTERNAL_CODE
3939from sqlbuild .compiler .sql_analysis .main ._normalize_analysis import normalize_analysis_sql
40+ from sqlbuild .compiler .sql_analysis .main ._resolve_binding_column import resolve_binding_column
4041from sqlbuild .compiler .sql_analysis .main ._schema_validation import get_schema_validations
4142from sqlbuild .compiler .sql_analysis .models import (
4243 SqlBindingDiagnostic ,
5960 r"\b__empty_fixture\s*\(\s*\)" , flags = re .IGNORECASE
6061)
6162_OPEN_EMPTY_FIXTURE_RELATION : str = "__sqlbuild_empty_fixture"
63+ _BUILT_IN_AUDIT_ROOT : str = "<built-in>"
64+ _COLUMN_ONLY_BUILT_IN_AUDITS : frozenset [str ] = frozenset ({"not_null" , "unique" })
6265_UNKNOWN_TABLE_MESSAGE_PREFIX : str = "Unknown table "
6366_SYNTAX_ERROR_CODE : str = "P001"
6467_RESOURCE_KIND_LABELS : dict [CompiledResourceType , str ] = {
@@ -102,7 +105,7 @@ def get_resource_sql_diagnostics(
102105 resource
103106 for resource in dict .fromkeys (
104107 (
105- * _audit_resources (project = project ),
108+ * _audit_resources (project = project , shapes = shapes , profile = profile ),
106109 * _sql_test_resources (project = project ),
107110 * _hook_resources (project = project ),
108111 )
@@ -112,19 +115,22 @@ def get_resource_sql_diagnostics(
112115 if not resources :
113116 return ()
114117 dialect : str | None = profile .sql_analysis_dialect
115- cleaned : tuple [str , ...] = tuple (
116- normalize_analysis_sql (
117- sql = cursor_intrinsics_analysis_sql (sql = resource .sql , cursor_type = None ),
118- dialect = dialect ,
119- )
120- for resource in resources
121- )
118+ normalized : dict [str , str ] = {}
119+ for resource in resources :
120+ if resource .sql not in normalized :
121+ normalized [resource .sql ] = normalize_analysis_sql (
122+ sql = cursor_intrinsics_analysis_sql (sql = resource .sql , cursor_type = None ),
123+ dialect = dialect ,
124+ )
125+ cleaned : tuple [str , ...] = tuple (normalized [resource .sql ] for resource in resources )
122126 known_functions : tuple [str , ...] = known_function_names (project .functions )
123127 known_types : tuple [str , ...] = known_declared_types (
124128 functions = project .functions , column_types = shapes
125129 )
126- results : tuple [SqlBindingResult , ...] = get_schema_validations (
127- requests = tuple (
130+ requests : dict [tuple [str , frozenset [str ]], SqlSchemaValidationRequest ] = {}
131+ for resource , cleaned_sql in zip (resources , cleaned , strict = True ):
132+ _ = requests .setdefault (
133+ (cleaned_sql , resource .relations ),
128134 SqlSchemaValidationRequest (
129135 sql = cleaned_sql ,
130136 dialect = dialect ,
@@ -133,9 +139,14 @@ def get_resource_sql_diagnostics(
133139 known_types = known_types ,
134140 quoted_identifiers_ignore_case = profile .quoted_identifiers_ignore_case ,
135141 catalog = project .binding_catalog ,
136- )
137- for resource , cleaned_sql in zip (resources , cleaned , strict = True )
142+ ),
138143 )
144+ unique_results : dict [tuple [str , frozenset [str ]], SqlBindingResult ] = dict (
145+ zip (requests , get_schema_validations (requests = tuple (requests .values ())), strict = True )
146+ )
147+ results : tuple [SqlBindingResult , ...] = tuple (
148+ unique_results [(cleaned_sql , resource .relations )]
149+ for resource , cleaned_sql in zip (resources , cleaned , strict = True )
139150 )
140151 diagnostics : dict [tuple [object , ...], CompilerDiagnostic ] = {}
141152 for resource , cleaned_sql , result in zip (resources , cleaned , results , strict = True ):
@@ -246,11 +257,18 @@ def _reported_diagnostic(
246257 )
247258
248259
249- def _audit_resources (* , project : CompiledProject ) -> tuple [_ResourceSql , ...]:
260+ def _audit_resources (
261+ * ,
262+ project : CompiledProject ,
263+ shapes : dict [str , dict [str , str ]],
264+ profile : ExpressionInferenceProfile ,
265+ ) -> tuple [_ResourceSql , ...]:
250266 analysis_disabled : frozenset [str ] = _analysis_disabled_models (project )
251267 resources : list [_ResourceSql ] = []
252268 for audit in project .audits :
253- if audit .attached_target_name in analysis_disabled :
269+ if audit .attached_target_name in analysis_disabled or _binds_cleanly (
270+ audit = audit , shapes = shapes , profile = profile
271+ ):
254272 continue
255273 relations : frozenset [str ] = _relations (audit .references )
256274 authored_bodies : tuple [str | None , ...] = (
@@ -286,6 +304,30 @@ def _audit_resources(*, project: CompiledProject) -> tuple[_ResourceSql, ...]:
286304 return tuple (resources )
287305
288306
307+ def _binds_cleanly (
308+ * , audit : CompiledAudit , shapes : dict [str , dict [str , str ]], profile : ExpressionInferenceProfile
309+ ) -> bool :
310+ """A built-in single-column audit on a column its closed target shape contains."""
311+
312+ if (
313+ audit .definition_name not in _COLUMN_ONLY_BUILT_IN_AUDITS
314+ or audit .audit_file .file_path .parts [:1 ] != (_BUILT_IN_AUDIT_ROOT ,)
315+ or audit .attached_target_name is None
316+ or audit .attached_column_name is None
317+ ):
318+ return False
319+ shape : dict [str , str ] = shapes .get (audit .attached_target_name ) or {}
320+ return bool (shape ) and (
321+ resolve_binding_column (
322+ name = audit .attached_column_name ,
323+ columns = shape ,
324+ dialect = profile .sql_analysis_dialect ,
325+ ignore_quoted_case = profile .quoted_identifiers_ignore_case ,
326+ )
327+ is not None
328+ )
329+
330+
289331def _audit_label (audit : CompiledAudit ) -> str :
290332 label : str = f"audit '{ audit .definition_name } '"
291333 if audit .attached_target_name is not None :
@@ -298,13 +340,13 @@ def _audit_label(audit: CompiledAudit) -> str:
298340def _sql_test_resources (* , project : CompiledProject ) -> tuple [_ResourceSql , ...]:
299341 analysis_disabled : frozenset [str ] = _analysis_disabled_models (project )
300342 resources : list [_ResourceSql ] = []
343+ prepared : dict [str , tuple [str , frozenset [str ]]] = {}
301344 for test in project .sql_tests :
302345 if not test .sql_body .strip () or analysis_disabled .intersection (test .expected_model_names ):
303346 continue
304- sql : str = _EMPTY_FIXTURE_CTE_PATTERN .sub (
305- lambda match : match .group ("prefix" ) + match .group ("name" ), test .sql_body
306- )
307- sql = _EMPTY_FIXTURE_CALL_PATTERN .sub (_OPEN_EMPTY_FIXTURE_RELATION , sql )
347+ if test .sql_body not in prepared :
348+ prepared [test .sql_body ] = _prepared_test_sql (test .sql_body )
349+ sql , relations = prepared [test .sql_body ]
308350 resources .append (
309351 _ResourceSql (
310352 resource_type = CompiledResourceType .SQL_TEST ,
@@ -320,20 +362,26 @@ def _sql_test_resources(*, project: CompiledProject) -> tuple[_ResourceSql, ...]
320362 contents = test .test_file .contents ,
321363 body = test .test_block .sql_body ,
322364 ),
323- relations = frozenset (
324- (
325- * _REFERENCE_CALL_PATTERN .findall (test .sql_body ),
326- * (
327- match .group ("name" )
328- for match in _EMPTY_FIXTURE_CTE_PATTERN .finditer (test .sql_body )
329- ),
330- )
331- ),
365+ relations = relations ,
332366 )
333367 )
334368 return tuple (resources )
335369
336370
371+ def _prepared_test_sql (sql_body : str ) -> tuple [str , frozenset [str ]]:
372+ """Test SQL with empty fixtures opened, and the relations it reads."""
373+
374+ sql : str = _EMPTY_FIXTURE_CTE_PATTERN .sub (
375+ lambda match : match .group ("prefix" ) + match .group ("name" ), sql_body
376+ )
377+ return _EMPTY_FIXTURE_CALL_PATTERN .sub (_OPEN_EMPTY_FIXTURE_RELATION , sql ), frozenset (
378+ (
379+ * _REFERENCE_CALL_PATTERN .findall (sql_body ),
380+ * (match .group ("name" ) for match in _EMPTY_FIXTURE_CTE_PATTERN .finditer (sql_body )),
381+ )
382+ )
383+
384+
337385def _hook_resources (* , project : CompiledProject ) -> tuple [_ResourceSql , ...]:
338386 hook_files : dict [Path , str ] = {
339387 hook_file .relative_path : hook_file .contents for hook_file in project .sql_hook_files
0 commit comments