55import random
66from collections .abc import Callable
77from dataclasses import dataclass
8+ from functools import partial
89from pathlib import Path
910from typing import cast
1011
2324 resolve_audit_run_scope ,
2425 resolve_audit_severity ,
2526)
27+ from sqlbuild .compiler .compile ._helpers .render import macros
2628from sqlbuild .compiler .compile ._helpers .render .cursor_intrinsics import reject_cursor_intrinsics
2729from sqlbuild .compiler .compile ._helpers .render .sql_vars import expand_authored_sql_result
2830from sqlbuild .compiler .compile .exceptions import CompileInputError
3638from sqlbuild .compiler .discovery .main .discover import discover_project_inputs
3739from sqlbuild .compiler .discovery .models import DiscoveredProjectInputs
3840from sqlbuild .compiler .frontier .constants import COMPILER_ENGINE_ENV_VAR
39- from sqlbuild .compiler .frontier .types import CompilerEngine
41+ from sqlbuild .compiler .frontier .main ._compile_frontier import compile_frontier
42+ from sqlbuild .compiler .frontier .types import CompilerEngine , CompilerStage
4043from sqlbuild .compiler .planner .constants import (
4144 MICROBATCH_END_SENTINEL ,
4245 MICROBATCH_START_SENTINEL ,
@@ -289,6 +292,11 @@ def _python_rendering(audit: GeneratedAudit) -> tuple[object, ...] | str:
289292 '[connections.local]\n database = "orders.duckdb"\n \n '
290293 '[targets.dev]\n connection = "local"\n schema = "dev"\n '
291294 ),
295+ "macros/labels.py" : (
296+ "def tidy_label(expression: str) -> str:\n "
297+ ' """Trim and lower-case a label."""\n '
298+ ' return f"LOWER(TRIM({expression}))"\n '
299+ ),
292300 "seeds/channel_codes.csv" : "id,label\n 1,web\n 2,store\n " ,
293301 "seeds/channel_codes.yml" : (
294302 "seeds:\n - name: channel_codes\n description: Channel codes.\n columns:\n "
@@ -302,26 +310,41 @@ def _python_rendering(audit: GeneratedAudit) -> tuple[object, ...] | str:
302310 "sources/events.yml" : (
303311 "sources:\n - name: order_events\n "
304312 f" description: \" Order feed ${{coalesce(ENV:{ _MISSING_ENV } , 'events')}}\" \n "
305- " expression: \" (SELECT 1 AS id, '@@region' AS region)\" \n "
313+ " expression: \" (SELECT 1 AS id, @tidy_label( '@@region') AS region)\" \n "
306314 " columns:\n - name: id\n type: INTEGER\n audits:\n "
307315 " - accepted_values:\n values: [1, 2]\n "
308316 " - name: region\n type: VARCHAR\n "
309317 ),
310318 "functions/sql/order_label.sql" : (
311319 'FUNCTION (\n description "Label an order status.",\n '
312320 f" schema \" ${{coalesce(ENV:{ _MISSING_ENV } , 'udfs')}}\" ,\n "
313- " arguments (raw_status STRING),\n returns STRING,\n );\n \n UPPER(raw_status)\n "
321+ " arguments (raw_status STRING),\n returns STRING,\n );\n \n "
322+ 'UPPER(@tidy_label("raw_status"))\n '
314323 ),
315324 "audits/generic/amount_floor.sql" : (
316325 'AUDIT ();\n \n SELECT *\n FROM __ref("@model")\n WHERE @column < @minimum '
317- "AND label <> @'label'\n "
326+ "AND @tidy_label( \" label\" ) <> @'label'\n "
318327 ),
319328 "models/orders.sql" : (
320329 'MODEL (\n materialized table,\n description "Orders.",\n '
321330 ' audits [amount_floor (column amount, minimum -5, label "it\' s", severity warn)],\n '
322331 ");\n \n SELECT c.id, 1.5 AS amount, c.label\n "
323332 'FROM __seed("channel_codes") c\n JOIN __source("order_events") e ON e.id = c.id\n '
324333 ),
334+ "tests/unit/test_orders.sql" : (
335+ "TEST();\n \n WITH\n __seed__channel_codes AS (\n "
336+ " SELECT 1 AS id, @tidy_label(\" ' Web '\" ) AS label\n ),\n "
337+ "__source__order_events AS (\n SELECT 1 AS id, 'north' AS region\n ),\n "
338+ "__expected__orders AS (\n SELECT 1 AS id, 1.5 AS amount, 'web' AS label\n )\n "
339+ "SELECT 1\n "
340+ ),
341+ "tests/scenarios/orders_scenario.sql" : (
342+ 'SCENARIO (\n description "Orders join their channel"\n );\n \n '
343+ "WITH\n __seed__channel_codes AS (\n SELECT 1 AS id, @tidy_label(\" 'Web'\" ) AS label\n ),\n "
344+ "__source__order_events AS (\n SELECT 1 AS id, 'north' AS region\n ),\n "
345+ "__expected__orders AS (\n SELECT 1 AS id, 1.5 AS amount, 'web' AS label\n )\n "
346+ "SELECT 1\n "
347+ ),
325348}
326349
327350
@@ -340,8 +363,14 @@ def attachment_engine_outcome(
340363 called : set [str ] = set ()
341364 for name in NATIVE_ATTACHMENT_ENTRIES :
342365 monkeypatch .setattr (_native , name , _recorded (called = called , name = name ))
366+ bridged : list [Path ] = []
367+ monkeypatch .setattr (macros , "_bridged_sql_macros" , _recorded_bridge (bridged = bridged ))
343368 monkeypatch .setenv (COMPILER_ENGINE_ENV_VAR , engine .value )
344- inputs : CompileProjectInputs = _compile_inputs (project_dir = project_dir )
369+ inputs : CompileProjectInputs = compile_frontier (
370+ until = CompilerStage .COMPILE_PROJECT_INPUTS ,
371+ python_stage = partial (_compile_inputs , project_dir = project_dir ),
372+ )
373+ called .update (f"bridged:{ str (path ).replace (str (project_dir ), '<project>' )} " for path in bridged )
345374 return frozenset (called ), repr (
346375 (
347376 inputs .seed_inputs ,
@@ -377,6 +406,16 @@ def _compile_inputs(*, project_dir: Path) -> CompileProjectInputs:
377406 )
378407
379408
409+ def _recorded_bridge (* , bridged : list [Path ]) -> Callable [..., object ]:
410+ entry : Callable [..., object ] = cast (Callable [..., object ], macros ._bridged_sql_macros )
411+
412+ def recorded (* , consumer_path : Path , ** arguments : object ) -> object :
413+ bridged .append (consumer_path )
414+ return entry (consumer_path = consumer_path , ** arguments )
415+
416+ return recorded
417+
418+
380419def _recorded (* , called : set [str ], name : str ) -> Callable [..., object ]:
381420 entry : Callable [..., object ] = getattr (_native , name )
382421
0 commit comments