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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/workflows/compiler-differential.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
run: uv python install 3.12
- name: Install dependencies
run: uv sync --locked --all-groups --all-extras
- name: Compare dense projects with and without custom rules (native vs native-preview)
- name: Compile dense projects with and without custom rules
env:
MODELS: ${{ matrix.models }}
run: make compiler-differential-dense COMPILER_DIFFERENTIAL_DENSE_MODELS="${MODELS}"
Expand Down
45 changes: 11 additions & 34 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ jobs:
run: make ${{ matrix.target }}

compiler_differential:
name: Compiler engine differential
name: Compiler golden outputs
needs: [changes, lockfile]
if: needs.changes.outputs.compiler_differential == 'true'
runs-on: ubuntu-24.04
Expand All @@ -303,22 +303,13 @@ jobs:
run: uv python install 3.12
- name: Install dependencies
run: uv sync --locked --all-groups --all-extras
- name: Compare fixtures, examples, generated seeds and the failure corpus (native vs native-preview, goldens)
- name: Check fixtures, examples, generated seeds and the failure corpus against the goldens
run: >-
make compiler-differential
COMPILER_DIFFERENTIAL_ARGS="--stage-captures --require-discovery-coverage
--require-render-coverage --require-analysis-coverage
--wheel-site-report ${{ runner.temp }}/compiler-differential-wheel-sites.json
--evidence-dir ${{ runner.temp }}/compiler-differential-evidence/preview"
- name: Upload the Polyglot wheel-site and deferral report
if: always()
uses: actions/upload-artifact@v4
with:
name: compiler-differential-wheel-sites-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/compiler-differential-wheel-sites.json
if-no-files-found: warn
retention-days: 30
- name: Upload differing projects' targets, stderr and compiled-test stats
--evidence-dir ${{ runner.temp }}/compiler-differential-evidence"
- name: Upload failing projects' targets, stderr and compiled-test stats
if: failure()
uses: actions/upload-artifact@v4
with:
Expand Down Expand Up @@ -355,21 +346,12 @@ jobs:
run: uv python install ${{ matrix.python }}
- name: Install dependencies
run: uv sync --locked --all-groups --all-extras --python ${{ matrix.python }}
- name: Require native discovery to support this Python
run: >-
uv run --no-sync python -c "import sys, unicodedata, sqlbuild._native as n;
assert n.native_text_supported(sys.version_info[:2], unicodedata.unidata_version)"
- name: Compare native text helpers with this Python
run: >-
uv run --no-sync pytest
tests/integration/src/sqlbuild/compiler/test_native_python_compat_oracle.py
-n auto --dist loadfile
- name: Compare native with native-preview and the goldens on fixtures, examples, the failure corpus and a few seeds
- name: Check fixtures, examples, the failure corpus and a few seeds against the goldens
env:
PYTHONUNBUFFERED: "1"
run: >-
uv run --no-sync python -m scripts.run_compiler_differential
--engines native native-preview --goldens check
--goldens check
--corpus fixtures examples failures seeds --seeds 4 --jobs 4

native_discovery_windows:
Expand Down Expand Up @@ -401,27 +383,22 @@ jobs:
run: |
uv sync --locked --no-dev --python 3.12
uv pip install "pytest>=8.4.2" "pytest-xdist>=3.8.0"
- name: Compare native text, YAML and TOML readers and the lineage fingerprint with this Python
- name: Check the native YAML and TOML readers and the lineage fingerprint with this Python
run: >-
uv run --no-sync pytest
tests/integration/src/sqlbuild/compiler/test_native_python_compat_oracle.py
tests/integration/src/sqlbuild/compiler/discovery/test_native_yaml_oracle.py
tests/integration/src/sqlbuild/compiler/discovery/test_native_toml_oracle.py
tests/unit/src/sqlbuild/cli/commands/_helpers/lineage/test_cache.py
-n auto --dist loadfile
- name: Compile playground projects on the default and preview engines, and build
- name: Compile and build playground projects
run: |
project="${RUNNER_TEMP}/waffle-shop"
oracle="${RUNNER_TEMP}/waffle-shop-preview"
uv run --no-sync sqb playground "${project}"
cp -r "${project}" "${oracle}"
uv run --no-sync sqb --project-dir "${project}" --no-color compile
SQLBUILD_COMPILER_ENGINE=native-preview uv run --no-sync sqb --project-dir "${oracle}" --no-color compile
diff -r "${oracle}/target/compiled" "${project}/target/compiled"
if ! test -f "${project}/target/cache/compiler-native-v1/macro-calls.bin"; then
echo "The default compile did not write the macro-call store; macro bridge debug log:"
rm -rf "${oracle}/target"
uv run --no-sync sqb --project-dir "${oracle}" --no-color --debug compile 2>&1 \
echo "The compile did not write the macro-call store; macro bridge debug log:"
rm -rf "${project}/target"
uv run --no-sync sqb --project-dir "${project}" --no-color --debug compile 2>&1 \
| grep "macro_bridge" || true
exit 1
fi
Expand Down
12 changes: 6 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ E2E_DUCKDB_BUILD_CORE_PATHS := \
tests/e2e/src/sqlbuild/cli/commands/main/build/test_expression_sources.py \
tests/e2e/src/sqlbuild/cli/commands/main/build/test_lifecycle_commands.py \
tests/e2e/src/sqlbuild/cli/commands/main/build/test_manifest_artifact_gating.py \
tests/e2e/src/sqlbuild/cli/commands/main/build/test_model_config_engines.py \
tests/e2e/src/sqlbuild/cli/commands/main/build/test_model_config_build.py \
tests/e2e/src/sqlbuild/cli/commands/main/build/test_no_tests_no_audits_flags.py \
tests/e2e/src/sqlbuild/cli/commands/main/build/test_plan_command_surface.py \
tests/e2e/src/sqlbuild/cli/commands/main/build/test_python_hooks.py \
Expand Down Expand Up @@ -297,22 +297,22 @@ COMPILER_DIFFERENTIAL_JOBS ?= 4
COMPILER_DIFFERENTIAL_SEEDS ?= 12
COMPILER_DIFFERENTIAL_DENSE_MODELS ?= 3000
COMPILER_DIFFERENTIAL_ARGS ?=
# Native-to-Python fallbacks and analysis deferrals must match the allow-list exactly, and every
# output its golden. `make compiler-baselines` rewrites both (see scripts/compiler_differential/README.md).
COMPILER_DIFFERENTIAL_GATES := --native-fallbacks check --goldens check
# Every output must match its golden. `make compiler-baselines` rewrites them
# (see scripts/compiler_differential/README.md).
COMPILER_DIFFERENTIAL_GATES := --goldens check

compiler-differential:
env PYTHONUNBUFFERED=1 uv run python -m scripts.run_compiler_differential \
--jobs $(COMPILER_DIFFERENTIAL_JOBS) --seeds $(COMPILER_DIFFERENTIAL_SEEDS) \
$(COMPILER_DIFFERENTIAL_GATES) $(COMPILER_DIFFERENTIAL_ARGS)

# Rewrite the goldens (from the native oracle) and the fallback allow-list from the CI run.
# Rewrite the goldens from the CI run.
# Review the diff like code; see scripts/compiler_differential/README.md.
COMPILER_DIFFERENTIAL_CI_ARGS := --stage-captures --require-discovery-coverage --require-render-coverage
compiler-baselines:
rm -rf tests/goldens/compiler
$(MAKE) compiler-differential COMPILER_DIFFERENTIAL_ARGS="$(COMPILER_DIFFERENTIAL_CI_ARGS) \
--require-analysis-coverage --native-fallbacks update --goldens update"
--require-analysis-coverage --goldens update"

compiler-differential-dense:
env PYTHONUNBUFFERED=1 uv run python -m scripts.run_compiler_differential \
Expand Down
39 changes: 11 additions & 28 deletions crates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ that can be tested and benchmarked with `cargo test` alone.
| `sqlbuild-core` | Constants shared by several crates, the panic boundary that turns an unwinding native failure into a named compiler error, passive identity, diagnostic and SQL value types, text positions, and a JSON emitter whose output matches Python's `json.dumps` and `orjson` byte for byte. |
| `sqlbuild-cache` | The shared native store every native cache uses (one atomically replaced, checksummed file per cache kind, keyed by content digests) and the one fingerprinting scheme for cache keys: the content digest and the project file fingerprint. |
| `sqlbuild-sqltext` | Lexical SQL text without polyglot: comment, quote and parenthesis scanning, the rules quote policy, model header tokenization and matching, static variable substitution, static reference extraction, and the general reference scan with table-function call arguments under each adapter's lexical rules. |
| `sqlbuild-config` | Configuration file reading without Python: `tomllib`-compatible TOML, PyYAML `safe_load`-compatible YAML 1.1, and typed project and local config readers for the fields discovery needs. Errors carry their kind and position; YAML outside the supported subset is an `Unsupported` error. |
| `sqlbuild-config` | Configuration file reading without Python: TOML, YAML 1.2 with the core schema and merge keys, and typed project and local config readers for the fields discovery needs. Errors carry their kind and position; YAML outside the supported subset is an `Unsupported` error. |
| `sqlbuild-discovery` | Native project discovery and the only implementation of the model, SQL test, scenario, source and schema file collections and the declaration layout: the shared directory walk with Python's glob and sort semantics, file reading with Python's newline handling and read errors, and parsing of authored files with Python's exact discovery messages. Results are plain data the Python discovery facade materialises. |
| `sqlbuild-scopes` | Declaration scopes, visibility, relationship grants, placement, report queries and the offline scope cache, including dialect-aware SQL-test and scenario scans. |
| `sqlbuild-model-config` | MODEL configuration, columns and audits, template expansion over caller values, and dynamic-column name patterns using Rust regex syntax. |
Expand Down Expand Up @@ -54,37 +54,20 @@ The same check fails when the workspace version, the `pyproject.toml` version an
`fensu check` applies the Rust structure rules inside each crate: other crates use items through
`main/` entry points, `models.rs`, `types.rs` and `constants.rs`, not through `_helpers/`.

## Compiler engines
## Compiler

`SQLBUILD_COMPILER_ENGINE` (or the hidden `--compiler-engine` flag) selects which compiler
stages run natively:

| Engine | Runs |
|---|---|
| `native` | The default: discovery, rendering and analysis, and every native stage that passed its flip gate (`shipped` tier). |
| `native-preview` | Opt-in: shipped stages plus stages still in development (`preview` tier). |

The Python compiler and its `python` engine were removed once discovery, rendering and analysis
shipped natively; setting `python` is an error that says so. Generic compiler stages do not
rescue native failures through a Python implementation. User macros, custom rules and adapter
callbacks retain their explicit Python boundaries. The recorder in
`scripts/compiler_differential/native_fallbacks.toml` tracks native answers and the remaining
extension/test-mock callbacks. A failing render raises its first error directly and runs each macro call at most
once; a divergence the bridge detects is reported as a native stage mismatch. Because the
macro-call store is always on, the
The compiler runs discovery, rendering and analysis natively. Generic compiler stages do not
rescue native failures through a Python implementation; user macros, custom rules and adapter
callbacks keep their explicit Python boundaries. A failing render raises its first error directly
and runs each macro call at most once; a divergence the bridge detects is reported as a native
stage mismatch. Because the macro-call store is always on, the
[macro determinism contract](../website/src/content/docs/docs/concepts/macros.mdx) applies to
every compile.

Each native stage declares its tier once, in `NATIVE_STAGE_TIERS` in
`src/sqlbuild/compiler/frontier/constants.py`. A stage moves from `preview` to `shipped` by
changing that line, after its flip gate passes: a byte-identical real project, a green
differential, and no measured slowdown on cold, edit and no-change compiles. Every engine keeps
its own compiler, Rules and compile-reuse stores, so preview output is never reused by `native`.

`make compiler-differential` compares `native` with `native-preview` on the full per-PR corpus
and checks both against the goldens in `tests/goldens/compiler`, with stage captures and full
discovery, render and analysis coverage required. CI also runs it on Python 3.13 and 3.14, and
on Windows checks that both engines compile a playground project to the same files.
`make compiler-differential` runs the full per-PR corpus once and checks every output against
the goldens in `tests/goldens/compiler`, with stage captures and full discovery, render and
analysis coverage required. CI also runs it on Python 3.13 and 3.14, and compiles and builds a
playground project on Windows.

## Working on the crates

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ use sha2::{Digest, Sha256};
use crate::assembly::analysis_session::_helpers::catalog_state::SessionCatalog;
use crate::assembly::analysis_session::_helpers::mappings::{ShapeTable, native_dialect};
use crate::assembly::analysis_session::constants::{
BATCH_CHUNK_MEMBERS, BINDING_SEVERITIES, COMPACT_WORKERS, CONFIDENCE_CODES,
ASCII_SPACES, BATCH_CHUNK_MEMBERS, BINDING_SEVERITIES, COMPACT_WORKERS, CONFIDENCE_CODES,
DEFAULT_BINDING_MESSAGE, FACT_LENGTH, INVALID_NATIVE_RESPONSE, LEGACY_FALLBACK,
LEGACY_RESPONSE_LENGTH, MIN_SHARED_MEMBERS, NULLABILITY_BY_CODE, PYTHON_ASCII_SPACES,
QUALIFIED_SCAN_TOKENS, REFERENCE_MARKER_PATTERN, RELATION_STUB_PREFIX, RESPONSE_LENGTH,
SOURCE_LENGTH, TRANSFORM_CODES, UNSTUBBED_REFERENCE_KIND,
LEGACY_RESPONSE_LENGTH, MIN_SHARED_MEMBERS, NULLABILITY_BY_CODE, QUALIFIED_SCAN_TOKENS,
REFERENCE_MARKER_PATTERN, RELATION_STUB_PREFIX, RESPONSE_LENGTH, SOURCE_LENGTH,
TRANSFORM_CODES, UNSTUBBED_REFERENCE_KIND,
};
use crate::assembly::analysis_session::models::{ColumnFact, LineageRow};
use crate::assembly::analysis_session::types::{Pairs, Shapes};
Expand Down Expand Up @@ -1004,7 +1004,7 @@ fn is_qualifier(folded_sql: &str, name: &str) -> bool {
let mut end: usize = start + name.len();
let bounded: bool = (start == 0 || !identifier(bytes[start - 1]))
&& (end == bytes.len() || !identifier(bytes[end]));
while bounded && end < bytes.len() && PYTHON_ASCII_SPACES.as_bytes().contains(&bytes[end]) {
while bounded && end < bytes.len() && ASCII_SPACES.as_bytes().contains(&bytes[end]) {
end += 1;
}
bounded && bytes.get(end) == Some(&b'.')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ use std::rc::Rc;
use polyglot_sql::Expression;
use polyglot_sql::traversal::ExpressionWalk;
use serde_json::{Map, Value};
use sqlbuild_core::text::main::active_python_text::active_python_text;
use sqlbuild_core::text::main::python_casefold::python_casefold;
use sqlbuild_core::text::main::python_upper::python_upper;

use crate::assembly::analysis_session::_helpers::dict_walk::{
PyValue, alias_or_name, arg, children, column_table_name, direct_tables, expression_args,
Expand Down Expand Up @@ -1537,12 +1534,12 @@ fn cast_target_type(target: &Map<String, Value>) -> Option<String> {

/// Python's `str.casefold`.
fn casefold(text: &str) -> String {
python_casefold(active_python_text(), text)
str::to_lowercase(text)
}

/// Python's `str.upper`.
fn upper(text: &str) -> String {
python_upper(active_python_text(), text)
str::to_uppercase(text)
}

fn dict_get<'a, V>(dict: &'a [(String, V)], key: &str) -> Option<&'a V> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ use polyglot_sql::expressions::Cte;
use polyglot_sql::traversal::ExpressionWalk;
use polyglot_sql::{ComplexityGuardOptions, Dialect, Expression, ParseOptions, ast_json};
use serde_json::{Map, Value, json};
use sqlbuild_core::text::main::active_python_text::active_python_text;
use sqlbuild_core::text::main::python_casefold::python_casefold;
use sqlbuild_core::text::main::python_upper::python_upper;
use sqlbuild_core::text::models::PythonText;

use crate::assembly::analysis_session::models::ProjectionFacts;

Expand Down Expand Up @@ -165,12 +161,12 @@ pub(crate) fn python_int(value: Option<&Value>) -> Option<String> {

/// Python's `str.casefold`.
pub(crate) fn casefold(text: &str) -> String {
python_casefold(active_python_text(), text)
str::to_lowercase(text)
}

/// Python's `str.upper`.
pub(crate) fn upper(text: &str) -> String {
python_upper(active_python_text(), text)
str::to_uppercase(text)
}

/// A value the wheel hands Python for one payload key: an expression, a list of them, or data.
Expand Down Expand Up @@ -465,7 +461,6 @@ fn first_select(payload: &Value) -> Option<&Map<String, Value>> {
}

fn collected_facts(select: &Map<String, Value>) -> ProjectionFacts {
let python = active_python_text();
let mut facts: ProjectionFacts = ProjectionFacts::default();
let mut quoted: Vec<String> = Vec::new();
let mut nulls: Vec<String> = Vec::new();
Expand All @@ -485,7 +480,7 @@ fn collected_facts(select: &Map<String, Value>) -> ProjectionFacts {
let Some(Value::String(name)) = alias.get("name") else {
continue;
};
let folded: String = python_casefold(python, name);
let folded: String = str::to_lowercase(name);
if alias.get("quoted") == Some(&Value::Bool(true)) {
quoted.push(folded.clone());
}
Expand Down Expand Up @@ -523,10 +518,7 @@ fn simple_type(kind: &str, payload: &Value) -> Option<&'static str> {
.get("name")
.and_then(Value::as_str)
.unwrap_or_default();
return lookup(
&FUNCTION_FIXTURE_TYPES,
&python_upper(active_python_text(), name),
);
return lookup(&FUNCTION_FIXTURE_TYPES, &str::to_uppercase(name));
}
None
}
Expand All @@ -553,23 +545,22 @@ pub(crate) fn helper_parsed_reads(
sql: &str,
keys: &[String],
) -> Result<Option<Vec<String>>, String> {
let python: PythonText = active_python_text();
let folded: String = python_casefold(python, sql);
let folded: String = str::to_lowercase(sql);
if !keys.iter().any(|key| folded.contains(key.as_str())) {
return Ok(Some(Vec::new()));
}
let Some(parsed) = parse_one(sql, HELPER_READS_DIALECT)? else {
return Ok(None);
};
let nested: Vec<String> = defined_cte_keys(python, &to_dict(&parsed)?);
let nested: Vec<String> = defined_cte_keys(&to_dict(&parsed)?);
let mut reads: Vec<String> = Vec::new();
for table in find_all(&parsed, TABLE_AST_KIND) {
if arg(Some(table), SCHEMA_TABLE_ARG)?.is_some()
|| arg(Some(table), CATALOG_TABLE_ARG)?.is_some()
{
continue;
}
let key: String = python_casefold(python, name(Some(table)));
let key: String = str::to_lowercase(name(Some(table)));
if keys.contains(&key) && !nested.contains(&key) && !reads.contains(&key) {
reads.push(key);
}
Expand All @@ -578,7 +569,7 @@ pub(crate) fn helper_parsed_reads(
}

/// Python's `_defined_cte_keys`: every CTE alias at any depth, casefolded.
fn defined_cte_keys(python: PythonText, tree: &Value) -> Vec<String> {
fn defined_cte_keys(tree: &Value) -> Vec<String> {
let mut keys: Vec<String> = Vec::new();
let mut pending: Vec<&Value> = vec![tree];
while let Some(value) = pending.pop() {
Expand All @@ -588,8 +579,7 @@ fn defined_cte_keys(python: PythonText, tree: &Value) -> Vec<String> {
if let Some(Value::Array(ctes)) = map.get(CTE_LIST_PAYLOAD_KEY) {
keys.extend(ctes.iter().filter_map(|cte| {
let alias: &Value = cte.as_object()?.get(CTE_ALIAS_PAYLOAD_KEY)?;
Some(python_casefold(
python,
Some(str::to_lowercase(
alias.as_object()?.get(NAME_PAYLOAD_KEY)?.as_str()?,
))
}));
Expand Down
Loading
Loading