Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fensu-policy = "=0.13.0"
globset = "0.4"
proc-macro2 = { version = "1", features = ["span-locations"] }
pyo3 = { version = "0.26", features = ["abi3-py312"] }
polyglot-sql = { git = "https://github.com/chio-labs/polyglot", rev = "1307cb6a2ead0bd9bdfe1fb82887f37e42c291bb", default-features = false, features = [
polyglot-sql = { git = "https://github.com/chio-labs/polyglot", rev = "afd2c73a2fe40f59e275aa918264c41ae83cbe94", default-features = false, features = [
"ast-tools",
"builder",
"dialect-bigquery",
Expand Down
18 changes: 7 additions & 11 deletions src/sqlbuild/compiler/compile/_helpers/analysis/cte_facts.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,11 +456,7 @@ def _polyglot_non_null_filter_context(
select: Any,
column_nullability_by_table: dict[str, dict[str, InferredNullability]],
) -> NonNullFilterContext | None:
select_args: object = getattr(select, "args", None)
if not isinstance(select_args, dict):
return None
select_args_dict: dict[str, object] = cast(dict[str, object], select_args)
where_payload: object = select_args_dict.get(_POLYGLOT_PAYLOAD_WHERE_CLAUSE)
where_payload: object = select.arg(_POLYGLOT_PAYLOAD_WHERE_CLAUSE)
if not isinstance(where_payload, dict):
return None
predicate: object = cast(dict[str, object], where_payload).get(_POLYGLOT_PAYLOAD_THIS)
Expand Down Expand Up @@ -616,7 +612,7 @@ def _polyglot_star_output_types(


def _polyglot_top_level_ctes(root: Any) -> tuple[tuple[str, Any, bool], ...]:
with_payload: object = getattr(root, "args", {}).get("with")
with_payload: object = root.arg("with")
raw_ctes: object = (
cast(dict[str, object], with_payload).get("ctes")
if isinstance(with_payload, dict)
Expand Down Expand Up @@ -678,10 +674,10 @@ def _polyglot_set_operation_output_types(
inference_profile: ExpressionInferenceProfile,
expression_type_resolver: _ExpressionTypeResolver,
) -> dict[str, str]:
if bool(getattr(operation, "args", {}).get("by_name")):
if bool(operation.arg("by_name")):
branch_types: list[dict[str, str]] = []
for side in ("left", "right"):
branch: Any = getattr(operation, "args", {}).get(side)
branch: Any = operation.arg(side)
if branch is None:
return {}
branch_types.append(
Expand All @@ -707,7 +703,7 @@ def _polyglot_set_operation_output_types(
}
branch_slots: list[tuple[tuple[str, str | None], ...]] = []
for side in ("left", "right"):
branch: Any = getattr(operation, "args", {}).get(side)
branch: Any = operation.arg(side)
if branch is None:
return {}
inferred: tuple[tuple[str, str | None], ...] = _polyglot_select_output_type_slots(
Expand Down Expand Up @@ -751,7 +747,7 @@ def _polyglot_select_output_type_slots(
if str(getattr(select, "kind", "")) in _POLYGLOT_SET_OPERATION_KINDS:
branch_slots: list[tuple[tuple[str, str | None], ...]] = []
for side in ("left", "right"):
branch: Any = getattr(select, "args", {}).get(side)
branch: Any = select.arg(side)
if branch is None:
return ()
slots: tuple[tuple[str, str | None], ...] = _polyglot_select_output_type_slots(
Expand All @@ -764,7 +760,7 @@ def _polyglot_select_output_type_slots(
return ()
branch_slots.append(slots)
left_slots, right_slots = branch_slots
if bool(getattr(select, "args", {}).get("by_name")):
if bool(select.arg("by_name")):
right_types: dict[str, str | None] = dict(right_slots)
return tuple(
(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Compile performance benchmarks

SQLBuild maintains complementary generated projects because model count alone does not predict
compile cost. Every fixture uses neutral orders-style names and synthetic SQL.

## Layered scale guard

The layered guard combines 976 models, 232 sources, 46 seeds, 23 SQL functions, 700 attached
audits, 130 native tests, two hooks, and 54 execution layers. Its model files follow a bounded SQL
size distribution up to roughly 520 KB. The graph contains a 54-layer spine and independent
eight-model chains.

This guard measures cold, unchanged, leaf-model edit, central-model edit, test edit, macro edit,
and project-config edit paths. It protects mixed-resource scaling and cache invalidation while the
separate 3,000/10,000-model and test-heavy guards retain their narrower contracts.

Budgets:

- cold compile: under 7 seconds;
- unchanged warm compile: under 3 seconds;
- one-model, one-test, and one-macro edits: under 4 seconds;
- project-config edit: under 8 seconds.

## Semantic density guard

The semantic guard exercises costs that SQL-size padding cannot represent:

| Characteristic | Generated guard |
| --- | ---: |
| Models | 976 |
| Sources | 232 |
| Seeds | 46 |
| SQL functions | 23 |
| Attached audits | 1,645 |
| Native test cases | 958 |
| Declared model columns | about 33,000 |
| Model SQL | about 5.8 MB |
| Compiled test SQL | about 13 MB |
| Hooks | 2 |
| Execution layers | 54 |

Declared column widths range from three to 538 columns. The SQL uses real expression trees rather
than comment-only padding: casts, conditional expressions, CTEs, derived tables, macros, functions,
seed joins, assertions, and a 267-branch set operation feeding a 270-column aggregate. The large set
operation specifically protects schema validation and CTE type inference from nested-tree
materialization regressions.

The guard records phase timings for cold and unchanged warm compiles. It enforces a 60-second cold
budget and a 12-second warm budget. Resource counts, declared-column count, model SQL bytes, and
compiled-test bytes are asserted so simplifying the fixture cannot make the performance check pass.

Both guards initialize the process-local SQL runtime with an unrelated 32-model project before
measurement. “Cold” means the measured project has no compiler cache or target artifacts; it does
not include one-time lazy initialization noise from the Python test worker. Phase timings are
attribution spans rather than additive buckets.

This file was deleted.

20 changes: 20 additions & 0 deletions tests/e2e/src/sqlbuild/cli/commands/main/compile/_test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,26 @@ class LayeredProductionCompilePerformanceGuardTestCase:
expected_config_edit_max_seconds: float


@dataclass(frozen=True)
class SemanticCompilePerformanceGuardTestCase:
description: str
model_count: int
source_count: int
seed_count: int
function_count: int
macro_count: int
test_count: int
audit_count: int
expected_min_declared_columns: int
expected_max_declared_columns: int
expected_min_model_sql_bytes: int
expected_max_model_sql_bytes: int
expected_min_compiled_test_bytes: int
expected_max_compiled_test_bytes: int
expected_cold_max_seconds: float
expected_warm_max_seconds: float


@dataclass(frozen=True)
class NamespaceCompileTestCase:
description: str
Expand Down
Loading
Loading