Skip to content

Commit ca665da

Browse files
jnsdlsclaude
andauthored
perf: build queries up to 2.7x faster by reusing ones already built (#40)
## Summary Ports upstream 1.11.3's query interning to the builder. When a step repeats one taken before, the builder returns the query it built then instead of building it again. ```text query.where(field, op, value) key = (step, every argument, type-tagged) # 1 / 1.0 / True / "1" stay apart, floats by hex if parent._children[key] is alive: return it # children held weakly, parent strongly build the child, remember it under key hold it in _RECENT (last 4096 built) # CPython frees a dropped chain at once; V8 waits for GC ``` Subqueries (`related`, `exists`) are keyed by the identity of their query's AST. The new `CorrelatedSubquery.source` field keeps that query alive. A two-hop `exists` builds its junction AST new every time, so it's keyed by its junction condition instead. Python's keys are exact where upstream's aren't: upstream folds a `flip` of `'yes'` into `true`. Stacked on #39, which this wins back the builder slowdown for. ## Evidence Wall-clock, Python median, same machine: | case | `main` (1.10.0) | #39 | this PR | | --- | --- | --- | --- | | build.simple | 3.5 µs | 4.6 µs | 1.3 µs | | build.complex | 20.8 µs | 25.2 µs | 13.7 µs | | query.simple.b100 | 834 µs | 907 µs | 547 µs | | query.complex.b100 | 3.69 ms | 4.17 ms | 2.78 ms | - `test_rebuilt_queries_match_upstream`: the same programs three times in one batch through one builder, compared with the npm package. They cover every step kind: scalar and float `where`, `whereFn` with a static parameter and `exists`, two-hop `whereExists`, a raw condition, `related` with and without a callback, `orderBy`, nested `start`, `limit` and `one`. - **Before** the two-hop key fix (0d4781b): fails. `scalar: true` then `false` returned the first query. - **After:** passes. - `uv run pytest`: 784 passed, 100% coverage. `uv run python -m tests.compat`: fully exercised. ## Merge Danger **Door:** two-way **Blast Radius:** builder Queries built from the same builder now share objects. Their ASTs equal what fresh building gives, including when a caller later mutates a list it passed in, which upstream does the same way. Memory is bounded: children are held weakly, plus at most 4096 recently built queries. Reviewed with sol-loop: 2 rounds with gpt-6.1-sol. Sol verdict: approved 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- codesmith:footer --> --- <a href="https://app.blacksmith.sh/agent-labs-dev/codesmith/zero-python/pr/40?autoLogin=true&ref=codesmith_pr_footer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v3.svg"><img alt="View with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png" width="162" height="28"></picture></a> <a href="https://backend.blacksmith.sh/track/enable-autofix?expires=1795813402&installation_model_id=434381&pr_number=40&ref=codesmith_pr_footer&repository=agent-labs-dev%2Fzero-python&return_to=https%3A%2F%2Fgithub.com%2Fagent-labs-dev%2Fzero-python%2Fpull%2F40&signature=2340a83be99f9f3903c8c5f9ca8c50963658bc75b1f75129e8997f65aa18a38e"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img alt="Autofix with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png" width="190" height="28"></picture></a> <sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you need.</sup> <!-- codesmith:autofix:disabled --> <!-- /codesmith:footer --> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 9e95bb1 commit ca665da

5 files changed

Lines changed: 259 additions & 32 deletions

File tree

‎docs/compat.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ These are the cases where the proof holds Python to something narrower than "ide
7070
- **Interned queries.** Upstream's builder hands back the query it built before when a step repeats one,
7171
keyed so that some values JavaScript's types rule out share a key: a `flip` of `'yes'` with `true`, any
7272
`orderBy` direction but `'asc'` with `'desc'`. In one process, such a value can come back as whichever
73-
was built first. Python builds what it is given. The harness gives each program a fresh schema, which
73+
was built first. Python interns as well, but by exact keys, so it builds what it is given. The harness gives each program a fresh schema, which
7474
upstream interns by, so that no comparison depends on the programs before it.
7575
- **A mutator returning `null`.** Upstream records a mutator's return value as result data unless it is
7676
`undefined`, so a TypeScript mutator can return `null` as data. Python has only `None`, which the port

‎src/zero_server/protocol/ast.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
import functools
1313
from collections.abc import Callable, Sequence
14-
from dataclasses import dataclass
14+
from dataclasses import dataclass, field
1515
from typing import TYPE_CHECKING, Any, ClassVar, Literal, Protocol
1616

1717
from zero_server.protocol.wire import dump_json, json_model, parse_wire
@@ -192,6 +192,9 @@ class CorrelatedSubquery:
192192
hidden: bool | None = None
193193
subquery: AST
194194
system: System | None = None
195+
# The builder query `subquery` is the AST of, kept alive with it: the builder interns a query by
196+
# the identity of its subqueries' ASTs, so they must outlive the query keyed by them.
197+
source: Any = field(default=None, repr=False, compare=False)
195198

196199
def to_json(self) -> dict[str, Any]:
197200
out: dict[str, Any] = {"correlation": self.correlation.to_json()}

0 commit comments

Comments
 (0)