Skip to content

refactor: read analysis natively, plan SQL tests on a native thread and trim hot spots - #1118

Merged
kvlonge merged 13 commits into
mainfrom
feat/chi599-batch-2
Oct 16, 2026
Merged

kvlonge merged 13 commits into
mainfrom
feat/chi599-batch-2

Conversation

@kvlonge

@kvlonge kvlonge commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Why

After the port, Rust stages still read compiled-model analysis and ran SQL test planning through Python objects and threads, and a profile showed avoidable CPU in pure-Rust hot spots.

Changes

  • Analysis readers (semantic shapes and coverage, type recovery, lineage, SQL rule paths) read compiled models' analysis through typed native accessors instead of Python attributes; InferredNullability is native.
  • SQL test planning runs on a native thread with a result handle and a bounded exit wait, replacing the Python thread and future; per-model planner helpers are called directly.
  • Pure-Rust hot spots: scanner and lint fast paths, single-pass select-list end, no second hash of stored compile output, fast hashing for in-memory maps.
  • The custom-rule host payload is pickled straight to disk, lowering rules-phase memory.

Verification

  • make test and make check-ci passed with all optional dependencies available.
  • Compiler differential: 371 projects, unchanged goldens. Planner differential: 11 projects matched the main baseline.
  • Compile outputs and exit codes match the reference on synthetic and private compile-only projects.
  • Windows cross-check of the whole workspace passed.
  • Independent review: no blocking findings; both low findings fixed.

InferredNullability becomes a native StrEnum registered with the compiler
enums, so Rust reads it through the typed registry instead of importing
sqlbuild.compiler.lineage.types in thirteen places. Python callers import
it from sqlbuild._native and the shape-only module is deleted. Stage
captures keep the original captured shape name.
Semantic shapes, coverage, type recovery, completion, metadata and lineage
read each compiled model's inferred columns, declared columns, star flags,
unchecked outputs, lineage, binding validation and dynamic proof through
typed accessors. Native model views answer from their owner's Rust facts
without building Python projections; hand-built records are converted at
the read.
…d and future

SQL test planning ran on a Python threading.Thread whose target was the
native planning function, reporting through a concurrent.futures.Future.
It now runs on a Rust thread that attaches to the interpreter and sends
its outcome over a channel; the planner and `start_sql_test_planning`
read it through the native `SqlTestPlanning` handle. The atexit hook
waits, without the GIL and for at most five seconds, until no planning
thread runs, so finalization never ends one inside native code. The
planning entry is still looked up on the module, so tests can replace it.
…the module

Model plan entries, audit entries, relation contexts and cursor
resolution looked up native helpers on `sqlbuild._native` and called
them with keyword dictionaries once or more per model. They now call the
Rust functions directly: authored config strings and cursor bounds, the
model plan action, SQL, hooks, full-refresh and microbatch limit
resolution, model plan entry assembly, audit selection, attachment kind,
effective run scope and lineage reads. Results are unchanged.
Dialect non-code scanning is called once per byte by many SQL test and
analysis scanners. Return early when the byte cannot open a block
comment, line comment, dollar quote or quoted text, instead of running
prefix comparisons at every offset. Results are unchanged.
… starts

Lint and format site patterns can only match at '@', '$' or '_'; skip the
anchored regex search at every other code byte.
Projection-name validation scanned each branch once per clause keyword.
Scan once and stop at the first top-level FROM or clause keyword with the
same name and IS [NOT] DISTINCT FROM rules.
The store's whole-body checksum already verifies the stdout record stored
under its digest, so reading a project reuse entry no longer hashes the
full compile output a second time. Persisted bytes are unchanged.
Add rustc-hash (already in the lock file) as the workspace fast hasher.
Index hosted shape names once instead of scanning every shape per lookup,
match UNION BY NAME outputs through a name index instead of quadratic
scans, and use FxHash for hot sets and maps in function-name validation,
CTE column rules, lint additional facts and SQL test identifier tokens.
None of these maps' iteration order reaches output.
The parent pickled the whole host payload into one bytes object before
writing it, holding a second copy of the payload (about 285 MB for a
10,000-model project) alive during the rules phase. Pickle straight into
the partial file instead; the atomic rename and the payload the hosts
read are unchanged.
@kvlonge
kvlonge enabled auto-merge (squash) October 16, 2026 10:15
@kvlonge
kvlonge merged commit be45fd8 into main Oct 16, 2026
76 of 78 checks passed
@kvlonge
kvlonge deleted the feat/chi599-batch-2 branch October 16, 2026 11:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant