Skip to content

Commit 06fa96d

Browse files
authored
refactor(native): split the native crate into a layered workspace (#983)
* refactor(native): rename the native crate to sqlbuild-python under a versioned workspace The extension crate becomes the Python boundary crate of a Cargo workspace. The release version moves to the workspace package so every native crate shares it, and maturin, uv cache keys, release automation and fensu paths follow the new location. * refactor(native): keep PyO3 classes and conversions in the bindings module ProjectCatalog, CompactAnalysisJob, BindingPositions and ParsedRulesRequest become thin Python classes in bindings that wrap plain native models, and catalog mapping requests are converted from Python at the boundary. Method names, signatures, GIL release, panic handling and exception types are unchanged, so later crates can stay free of PyO3. * refactor(native): extract sqlbuild-core with shared constants and the panic boundary The constants used by more than one future crate and the PyO3-free panic catcher move to a dependency-free sqlbuild-core crate, with the panic tests. The Python crate keeps the named exception and guards, and the build identity now hashes every workspace crate. * refactor(native): extract sqlbuild-sqltext for lexical scanning and model headers SQL scanning, the rules quote policy, model header tokenization and matching, static variable substitution and static reference extraction move with their tests to a polyglot-free sqlbuild-sqltext crate. Items other crates use become public; behaviour is unchanged. * refactor(native): extract sqlbuild-analysis for polyglot query analysis Query analysis, the binding catalog, semantic validation and usage, column references, SQL tokens and SQL-test extraction, planning and rendering move with their tests and the function-name data to sqlbuild-analysis, the lowest crate that depends on polyglot. A test-support feature exposes one test-only helper to dependent crates' tests, and the workspace states the insertion-ordered JSON feature every crate already relied on. * refactor(native): extract sqlbuild-rules for rules, lint, quality and formatting Built-in rules, the rules engine and custom-rule host, SQL lint, quality checks, the formatter, rules configuration and the request models move with their tests to sqlbuild-rules. It owns the build identity script, which still hashes every workspace crate, so sqlbuild-python is left with only the PyO3 bindings. * refactor(native): route cross-crate use through main entries and declare the crates to fensu Items other crates use are reached through main entry points, models and types modules instead of helpers: the static reference type moves to types.rs, the rules dialect and the test-only built-in function list gain main entries, and the panic boundary splits into one entry per function with its message in constants. fensu now declares every workspace crate and its exceptions follow the moved files. * refactor(native): enforce one-way crate layering and matching versions in check-ci The workspace declares its crates from the bottom up, and a check run by make check-ci fails when a crate depends on a later layer (resolving aliased workspace dependencies to their packages), when any crate other than sqlbuild-python reaches PyO3, or when a crate below sqlbuild-analysis reaches polyglot. It also fails when the workspace, pyproject and release-please versions differ, since the version is part of the build identity. crates/README.md documents the crates, the graph and the check. * fix(native): include the workspace manifest in the build identity Dependency features and release profiles live in the workspace Cargo.toml, so changing them left BUILD_IDENTITY and every cache keyed on it unchanged. The build script now hashes and watches the workspace manifest next to the lockfile and exports the hashed workspace files, which a test asserts.
1 parent 89d64a5 commit 06fa96d

374 files changed

Lines changed: 2325 additions & 1186 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/release-please.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ jobs:
186186
done
187187
188188
unexpected_files="$(gh pr view "${RELEASE_PR}" --repo "${GITHUB_REPOSITORY}" \
189-
--json files --jq '[.files[].path | select(. != ".release-please-manifest.json" and . != "CHANGELOG.md" and . != "pyproject.toml" and . != "uv.lock" and . != "Cargo.lock" and . != "crates/sqlbuild-rules-native/Cargo.toml")] | join("\n")')"
189+
--json files --jq '[.files[].path | select(. != ".release-please-manifest.json" and . != "CHANGELOG.md" and . != "pyproject.toml" and . != "uv.lock" and . != "Cargo.lock" and . != "Cargo.toml")] | join("\n")')"
190190
if [[ -n "${unexpected_files}" ]]; then
191191
echo "Release pull request contains non-metadata changes:" >&2
192192
echo "${unexpected_files}" >&2

‎Cargo.lock‎

Lines changed: 47 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
11
[workspace]
22
members = [
3-
"crates/sqlbuild-rules-native",
3+
"crates/sqlbuild-core",
4+
"crates/sqlbuild-sqltext",
5+
"crates/sqlbuild-analysis",
6+
"crates/sqlbuild-rules",
7+
"crates/sqlbuild-python",
48
]
59
resolver = "2"
610

11+
# Native crates from the bottom up: each crate may depend only on crates listed before it.
12+
# `make check-ci` enforces this, keeps PyO3 in the Python boundary crate and keeps polyglot
13+
# out of every crate below the polyglot floor. See crates/README.md.
14+
[workspace.metadata.native-layers]
15+
order = [
16+
"sqlbuild-core",
17+
"sqlbuild-sqltext",
18+
"sqlbuild-analysis",
19+
"sqlbuild-rules",
20+
"sqlbuild-python",
21+
]
22+
python-boundary = "sqlbuild-python"
23+
polyglot-floor = "sqlbuild-analysis"
24+
725
[workspace.package]
26+
version = "0.136.0"
827
edition = "2024"
928
license = "Apache-2.0"
1029
publish = false
@@ -36,8 +55,13 @@ quote = "1"
3655
rayon = "1"
3756
regex = "1"
3857
serde = { version = "1", features = ["derive"] }
39-
serde_json = "1"
58+
# Every crate relies on insertion-ordered JSON objects, which the extension has always built with.
59+
serde_json = { version = "1", features = ["preserve_order"] }
4060
sha2 = "0.10"
61+
sqlbuild-analysis = { path = "crates/sqlbuild-analysis" }
62+
sqlbuild-core = { path = "crates/sqlbuild-core" }
63+
sqlbuild-rules = { path = "crates/sqlbuild-rules" }
64+
sqlbuild-sqltext = { path = "crates/sqlbuild-sqltext" }
4165
stacker = "0.1"
4266
sqlparser = { version = "0.58", features = ["visitor"] }
4367
syn = { version = "2", features = ["full", "visit", "extra-traits"] }

‎Makefile‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
SHELL := /bin/bash
22

3-
.PHONY: verify verify-quick verify-pg coverage cli-preview rust-check \
3+
.PHONY: verify verify-quick verify-pg coverage cli-preview rust-check check-native-layering \
44
check-e2e-shards test-e2e-duckdb test-e2e-duckdb-build-core \
55
test-e2e-duckdb-build-incremental \
66
test-e2e-duckdb-cli-data test-e2e-duckdb-cli test-e2e-duckdb-inspection \
@@ -566,14 +566,18 @@ verify-quick:
566566
exit $$status
567567

568568

569-
check-ci: check-e2e-shards
569+
check-ci: check-e2e-shards check-native-layering
570570
uv run ruff format --check .
571571
uv run ruff check .
572572
uv run ty check src tests
573573
uv run pytest tests/unit/src/sqlbuild/adapter/contract/classes/strict_adapter/test_strict_adapter.py -q
574574
uv run fensu check
575575

576576

577+
check-native-layering:
578+
uv run python -m scripts.check_native_layering
579+
580+
577581
check-pr-metadata:
578582
@test -n "$(PR_TITLE)" || { echo "PR_TITLE is required" >&2; exit 2; }
579583
@test -n "$(PR_BODY_FILE)" || { echo "PR_BODY_FILE is required" >&2; exit 2; }

‎crates/README.md‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Native crates
2+
3+
SQLBuild's native code is a Cargo workspace that builds one Python extension module,
4+
`sqlbuild._native`. Only `sqlbuild-python` knows about Python; every other crate is plain Rust
5+
that can be tested and benchmarked with `cargo test` alone.
6+
7+
| Crate | Responsibility |
8+
|---|---|
9+
| `sqlbuild-core` | Constants shared by several crates and the panic boundary that turns an unwinding native failure into a named compiler error. |
10+
| `sqlbuild-sqltext` | Lexical SQL text without polyglot: comment, quote and parenthesis scanning, the rules quote policy, model header tokenization and matching, static variable substitution and static reference extraction. |
11+
| `sqlbuild-analysis` | SQL analysis over polyglot: SQL tokens, query analysis, the binding catalog, semantic validation and usage, column references, and SQL-test extraction, planning and rendering. |
12+
| `sqlbuild-rules` | Built-in rules and the rules engine, the custom-rule host, SQL lint, quality checks and formatting, rules configuration and the request models. It also owns the build identity script. |
13+
| `sqlbuild-python` | The only PyO3 crate: the `_native` module, its Python classes and functions, conversions from Python objects, and the process allocator. |
14+
15+
## Layering
16+
17+
Dependencies point one way, from the top of this graph to the bottom:
18+
19+
```text
20+
sqlbuild-python
21+
-> sqlbuild-rules -> sqlbuild-analysis -> sqlbuild-sqltext -> sqlbuild-core
22+
```
23+
24+
Each crate may depend only on crates below it, and may also skip layers. The order is declared
25+
in `[workspace.metadata.native-layers]` in the workspace `Cargo.toml`, and
26+
`make check-native-layering` (part of `make check-ci`) fails when:
27+
28+
- a crate depends on a crate that is not in an earlier layer, or a workspace crate is missing
29+
from the order;
30+
- any crate other than `sqlbuild-python` reaches `pyo3`, directly or through another package;
31+
- a crate below `sqlbuild-analysis` reaches polyglot.
32+
33+
The same check fails when the workspace version, the `pyproject.toml` version and
34+
`.release-please-manifest.json` disagree, because the version is part of the build identity.
35+
36+
`fensu check` applies the Rust structure rules inside each crate: other crates use items through
37+
`main/` entry points, `models.rs`, `types.rs` and `constants.rs`, not through `_helpers/`.
38+
39+
## Working on the crates
40+
41+
- Tests live next to the module they cover; run them for one crate with
42+
`cargo test -p <crate>` or for all crates with `cargo test --workspace`.
43+
- `sqlbuild-analysis` has a `test-support` feature that exposes test-only helpers to the tests of
44+
dependent crates. Enable it only from `[dev-dependencies]`.
45+
- The build identity (`sqlbuild._native.BUILD_IDENTITY`) hashes the sources of every crate in
46+
this directory, so editing any crate invalidates the native caches keyed on it.
47+
- maturin builds `crates/sqlbuild-python`; the release version lives in
48+
`[workspace.package]` and is shared by every crate.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[package]
2+
name = "sqlbuild-analysis"
3+
version.workspace = true
4+
description = "Polyglot-based SQL analysis, binding and semantic validation for SQLBuild."
5+
edition.workspace = true
6+
license.workspace = true
7+
publish.workspace = true
8+
rust-version.workspace = true
9+
10+
[features]
11+
default = []
12+
# Expose test-only helpers to the tests of dependent crates.
13+
test-support = []
14+
15+
[dependencies]
16+
polyglot-sql.workspace = true
17+
polyglot-sql-function-catalogs.workspace = true
18+
rayon.workspace = true
19+
regex.workspace = true
20+
serde.workspace = true
21+
serde_json.workspace = true
22+
sha2.workspace = true
23+
sqlbuild-core.workspace = true
24+
sqlbuild-sqltext.workspace = true
25+
26+
[lints]
27+
workspace = true

crates/sqlbuild-rules-native/function_names/snowflake.txt renamed to crates/sqlbuild-analysis/function_names/snowflake.txt

File renamed without changes.

crates/sqlbuild-rules-native/src/column_references/_helpers/references.rs renamed to crates/sqlbuild-analysis/src/column_references/_helpers/references.rs

File renamed without changes.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub fn analyze_json(request_json: &str) -> Result<String, String> {
2+
crate::column_references::references::analyze_json(request_json)
3+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod analyze;

0 commit comments

Comments
 (0)