Skip to content

fix(tesseract): memory leaks in the native planner#11120

Merged
waralexrom merged 7 commits into
masterfrom
tesseract-memory-investigation
Jun 18, 2026
Merged

fix(tesseract): memory leaks in the native planner#11120
waralexrom merged 7 commits into
masterfrom
tesseract-memory-investigation

Conversation

@waralexrom

Copy link
Copy Markdown
Member

Summary

Fixes two memory leaks in the Tesseract native SQL planner that OOM'd the refresh worker when CUBEJS_TESSERACT_SQL_PLANNER is enabled. Per-query state was never reclaimed: one leak was a Rust Rc reference cycle, the other was native (neon) proxy objects created per member-sql evaluation that N-API never freed.

Changes

  • Break the Rc cycle (82154224fa). Split a new State { query_tools, compiler, join_tree_cache } out of QueryTools (with Deref<Target = QueryTools>), severing the cycle QueryTools → join_tree_cache → JoinTree → BaseCube → QueryTools. Transient planners now hold Rc<State>. Adds a no_query_tools_leak regression test (Weak-after-drop).
  • Move member-sql compilation to JS (e18d53a7bf). New stateless recorder MemberSqlTemplateCompiler.js runs the member sql function under JS-side proxies ({CUBE}, FILTER_PARAMS, FILTER_GROUP, SECURITY_CONTEXT, SQL_UTILS) and returns the template plus recorded dependencies. Rust now calls compile_member_sql over the bridge instead of constructing neon proxy objects, removing ~580 lines of Rust proxy code that leaked N-API structures. clone_to_context is intentionally kept for future compile-model-once reuse.

Testing

  • cubesqlplanner (Rust): 1014 passing
  • member-sql-template-compiler (Jest): 17 passing
  • Pre-aggregations through Tesseract: 17 passing
  • Full Postgres integration through Tesseract (CUBEJS_TESSERACT_SQL_PLANNER=true CUBEJS_TESSERACT_PRE_AGGREGATIONS=true): 35 suites, 451 passing, 0 failing
  • Leak stend on the customer model, 40 rounds: RSS plateaus at ~10 rounds (was +58 MB/round before the fix). Matches the no-Tesseract baseline shape — a fixed ~2.4 MB overhead instead of linear growth.

Every buildSqlAndParams built a per-query object graph hanging off
Rc<QueryTools>, and QueryTools owned the join-tree cache. The cached
JoinTree's BaseCube held a strong Rc<QueryTools> back, so
QueryTools -> join_tree_cache -> JoinTree -> BaseCube -> QueryTools
formed a cycle: QueryTools never dropped and the whole graph (Compiler,
symbols, ~350 compiled minijinja SQL templates) leaked on every query.
In the refresh worker, which generates huge volumes of pre-aggregation
SQL, this grew unbounded and OOMed (Tesseract only; the JS planner was
unaffected).

Split the per-query state: introduce `State { query_tools, compiler,
join_tree_cache }` (Deref<Target = QueryTools>) that owns the mutable /
caching pieces, and make QueryTools a cache-free leaf. Transient planners
hold Rc<State>; cached/long-lived values (BaseCube, TypedFilter, symbols)
and the render layer keep Rc<QueryTools>, so no cached value can pin the
hub. The Compiler moved into State too (its symbol cache was a latent
host for the same class of cycle).

Filter building needs the Compiler only to (re)compile a to_date
rolling-window granularity, so BaseFilter/TypedFilterBuilder take an
optional &mut Compiler; a member-only rewrite carries the precomputed
FilterOp instead, avoiding the dependency. These compiler-threading
points are marked FIXME for a future early-compilation pass.

Adds a pure-Rust regression test (Weak-after-drop on QueryTools) that
fails on the cycle and passes once it is broken.

Note: member_mask_filters still lives in QueryTools and keeps a strong
back-reference via TypedFilter, so the cycle remains for queries with
masked members; relocating that field into State (a follow-up) closes it.
Member `sql` functions were compiled by building recording proxies in
Rust via neon and invoking the JS function through them. Every property
access created neon `JsFunction`s (the get-trap and to-string closures);
their backing napi function objects were never reclaimed even after the
JS side was GC'd, so the refresh worker leaked unboundedly while
generating pre-aggregation SQL (Tesseract only).

Move the recording to a stateless JS module (MemberSqlTemplateCompiler):
it builds the `{CUBE}`, FILTER_PARAMS, FILTER_GROUP, SECURITY_CONTEXT and
SQL_UTILS proxies in JS — where V8 garbage-collects them normally — runs
the member function, and returns the produced template plus the recorded
dependencies as plain data. BaseQuery exposes it via `compileMemberSql`,
reached from Rust through a new `BaseTools::compile_member_sql`; the
planner consumes the result exactly as before. The proxy machinery,
`ProxyState`, and `MemberSql::compile_template_sql` are removed from the
native side.

The only JS reference that still crosses back into Rust is a
`FILTER_PARAMS.x.filter(fn)` column callback, which is deferred (invoked
at render time with the resolved column). It is an existing JS function
held by a `Root` — not a Rust-created `JsFunction` — and is released with
the query graph, so it is bounded per query and does not reintroduce the
leak.

The recording logic is now unit-testable in isolation
(member-sql-template-compiler.test.ts), which was impractical while it
lived behind the Rust↔JS bridge.
@waralexrom waralexrom requested a review from a team as a code owner June 18, 2026 13:53
@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Claude finished @waralexrom's task in 4m 38s —— View job


Code review: memory-leak fixes in the native planner — verdict: ship after one follow-up

1 medium, 4 low. Both fixes look correct and well-documented. The Rc-cycle split and the JS-side recorder cleanly remove the two leak sources. Worth tracking one acknowledged residual cycle as a follow-up.

Details

Tasks

  • Read PR description and changed files
  • Review MemberSqlTemplateCompiler.js
  • Review BaseQuery.js integration
  • Review state.rs / query_tools.rs (Rc cycle break)
  • Review member_sql.rs / base_tools.rs / sql_call_builder.rs
  • Review tests (no_query_tools_leak.rs, member-sql-template-compiler.test.ts)
  • Spot-check call-site refactors

What the fix does — and what it leaves behind

The split is sound: State { query_tools, compiler, join_tree_cache } holds everything that can leak, and QueryTools becomes a cache-free leaf that cached values (BaseCube, TypedFilter, symbols) can safely hold an Rc<…> of. Combined with Deref<Target = QueryTools> (rust/cube/cubesqlplanner/cubesqlplanner/src/planner/state.rs:131-138), call sites stay readable.

The JS-side recorder (packages/cubejs-schema-compiler/src/adapter/MemberSqlTemplateCompiler.js) faithfully reproduces the previous Rust proxy semantics (FILTER_PARAMS deferred column callbacks; SECURITY_CONTEXT filter/requiredFilter/unsafeValue, including the empty-array → 1 = 0 and none1 = 1 cases). 17 unit tests cover the main paths.

Findings

[medium] Documented Rc cycle on masked members has no regression coveragerust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_tools.rs:33-45

The comment is honest:

QueryTools → member_mask_filters → … → QueryTools never drops

member_mask_filters was kept on QueryTools rather than moved into State, so the FilterItem → BaseFilter → TypedFilter → Rc<QueryTools> chain still leaks QueryTools (and the whole graph hanging off it) whenever masked members are present. The new no_query_tools_leak test only exercises the join-cache cycle. A second test that supplies masked_members in try_new would catch a regression if/when this is closed; without it the "leak fixed" guarantee is conditional.

Recommend either (a) moving member_mask_filters onto State in this PR (the comment notes nothing cached holds an Rc<State>, so it should just work), or (b) opening a follow-up issue and adding an #[ignore]'d test now so it's not lost.

[low] Compiled BaseFilters also reach QueryTools from other live cachesrust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/typed_filter.rs:53-54

TypedFilter holds Rc<QueryTools>. As long as no long-lived cache reachable from QueryTools holds a TypedFilter, the strong-count goes to zero — the new test covers this for joins. But anything new that caches a BaseFilter/TypedFilter under QueryTools will silently reintroduce the cycle. A short comment on TypedFilter calling this out (mirroring the one on State) would make the invariant maintainable.

[low] parseTemplateResult is O(n) strict-coerce, but path dedup is O(n²·k) JSONpackages/cubejs-schema-compiler/src/adapter/MemberSqlTemplateCompiler.js:40-49

uniqueInsertPath does JSON.stringify(path) and a linear scan per insert. Fine for typical member SQL (handful of symbols), but a Map<string,number> keyed on the stringified path would make this O(n) with no churn. Worth doing if any single member ever references dozens of symbols.

[low] coerceFilterValue falsy-handling parity is implicitpackages/cubejs-schema-compiler/src/adapter/MemberSqlTemplateCompiler.js:139-154

The recorder treats '', 0, NaN, and false as none (→ 1 = 1 / required-filter error), matching the old Rust proxy. Worth confirming this also matches the non-Tesseract contextSymbolsProxy path so behavior doesn't quietly diverge when CUBEJS_TESSERACT_SQL_PLANNER is toggled per query. Could be a one-liner doc comment pointing to the parity check.

[low] Dead-but-kept SqlTemplateArgs::clone_to_contextrust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/member_sql.rs:262-288

PR description says it's "intentionally kept for future compile-model-once reuse." That's a defensible choice, but a #[allow(dead_code)] plus a one-line rationale on the function itself would prevent the next janitor from removing it.

Nits

  • BaseQuery.compileMemberSql arg order is (sqlFn, securityContext, argNames) from the bridge but the JS impl reorders to (sqlFn, argNames, securityContext, sqlUtils) — small, but worth a comment on the wrapper noting the deliberate reorder so the call site reads correctly.
  • MemberSqlTemplateCompiler test file lacks a case for a deeply nested SECURITY_CONTEXT path (SECURITY_CONTEXT.a.b.c.filter(...)) — the old Rust had a dedicated security_context_leaf_proxy for this; the new recursion appears to handle it via the same proxy but it isn't pinned by a test.

Praise

  • The assert_released helper + Weak probe pattern is a clean, reusable harness for catching Rc cycles. Worth promoting to a shared test util.
  • The "FIXME: late compilation" comments on TypedFilterBuilder::build, BaseFilter::try_new, change_operator, change_granularity, and query_properties.rs:1069 clearly mark the remaining Compiler threading as a transitional state — that's exactly the kind of code archaeology future contributors need.
  • The split of recording state into state (plain object passed in MemberSqlTemplateCompiler) avoids the old Rc<RefCell<ProxyState>> / Weak dance entirely. Much easier to reason about.
· Branch: `tesseract-memory-investigation`

@github-actions github-actions Bot added rust Pull requests that update Rust code javascript Pull requests that update Javascript code labels Jun 18, 2026
@waralexrom waralexrom changed the title fix(tesseract): stop refresh-worker memory leaks in the native planner fix(tesseract): memory leaks in the native planner Jun 18, 2026
@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Claude finished @waralexrom's task in 1m 4s —— View job


Code review: memory-leak fixes in the native planner — verdict: ship after one follow-up

1 medium, 4 low. Re-checked after 8c58794 (lint-only: letconst in member-sql-template-compiler.test.ts). No change to the prior verdict — fixes still look correct, findings still stand.

Full review

Tasks

  • Read PR description and changed files
  • Review MemberSqlTemplateCompiler.js
  • Review BaseQuery.js integration
  • Review state.rs / query_tools.rs (Rc cycle break)
  • Review member_sql.rs / base_tools.rs / sql_call_builder.rs
  • Review tests (no_query_tools_leak.rs, member-sql-template-compiler.test.ts)
  • Spot-check call-site refactors
  • Re-check after lint-only sync commit 8c58794

What the fix does — and what it leaves behind

The split is sound: State { query_tools, compiler, join_tree_cache } holds everything that can leak, and QueryTools becomes a cache-free leaf that cached values (BaseCube, TypedFilter, symbols) can safely hold an Rc<…> of. Combined with Deref<Target = QueryTools> (rust/cube/cubesqlplanner/cubesqlplanner/src/planner/state.rs:131-138), call sites stay readable.

The JS-side recorder (packages/cubejs-schema-compiler/src/adapter/MemberSqlTemplateCompiler.js) faithfully reproduces the previous Rust proxy semantics (FILTER_PARAMS deferred column callbacks; SECURITY_CONTEXT filter/requiredFilter/unsafeValue, including the empty-array → 1 = 0 and none1 = 1 cases). 17 unit tests cover the main paths.

Findings

[medium] Documented Rc cycle on masked members has no regression coveragerust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_tools.rs:33-45

The comment is honest:

QueryTools → member_mask_filters → … → QueryTools never drops

member_mask_filters was kept on QueryTools rather than moved into State, so the FilterItem → BaseFilter → TypedFilter → Rc<QueryTools> chain still leaks QueryTools (and the whole graph hanging off it) whenever masked members are present. The new no_query_tools_leak test only exercises the join-cache cycle. A second test that supplies masked_members in try_new would catch a regression if/when this is closed; without it the "leak fixed" guarantee is conditional.

Recommend either (a) moving member_mask_filters onto State in this PR (the comment notes nothing cached holds an Rc<State>, so it should just work), or (b) opening a follow-up issue and adding an #[ignore]'d test now so it's not lost.

[low] Compiled BaseFilters also reach QueryTools from other live cachesrust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/typed_filter.rs:53-54

TypedFilter holds Rc<QueryTools>. As long as no long-lived cache reachable from QueryTools holds a TypedFilter, the strong-count goes to zero — the new test covers this for joins. But anything new that caches a BaseFilter/TypedFilter under QueryTools will silently reintroduce the cycle. A short comment on TypedFilter calling this out (mirroring the one on State) would make the invariant maintainable.

[low] parseTemplateResult is O(n) strict-coerce, but path dedup is O(n²·k) JSONpackages/cubejs-schema-compiler/src/adapter/MemberSqlTemplateCompiler.js:40-49

uniqueInsertPath does JSON.stringify(path) and a linear scan per insert. Fine for typical member SQL (handful of symbols), but a Map<string,number> keyed on the stringified path would make this O(n) with no churn. Worth doing if any single member ever references dozens of symbols.

[low] coerceFilterValue falsy-handling parity is implicitpackages/cubejs-schema-compiler/src/adapter/MemberSqlTemplateCompiler.js:139-154

The recorder treats '', 0, NaN, and false as none (→ 1 = 1 / required-filter error), matching the old Rust proxy. Worth confirming this also matches the non-Tesseract contextSymbolsProxy path so behavior doesn't quietly diverge when CUBEJS_TESSERACT_SQL_PLANNER is toggled per query. Could be a one-liner doc comment pointing to the parity check.

[low] Dead-but-kept SqlTemplateArgs::clone_to_contextrust/cube/cubesqlplanner/cubesqlplanner/src/cube_bridge/member_sql.rs:262-288

PR description says it's "intentionally kept for future compile-model-once reuse." That's a defensible choice, but a #[allow(dead_code)] plus a one-line rationale on the function itself would prevent the next janitor from removing it.

Nits

  • BaseQuery.compileMemberSql arg order is (sqlFn, securityContext, argNames) from the bridge but the JS impl reorders to (sqlFn, argNames, securityContext, sqlUtils) — small, but worth a comment on the wrapper noting the deliberate reorder so the call site reads correctly.
  • MemberSqlTemplateCompiler test file lacks a case for a deeply nested SECURITY_CONTEXT path (SECURITY_CONTEXT.a.b.c.filter(...)) — the old Rust had a dedicated security_context_leaf_proxy for this; the new recursion appears to handle it via the same proxy but it isn't pinned by a test.

Praise

  • The assert_released helper + Weak probe pattern is a clean, reusable harness for catching Rc cycles. Worth promoting to a shared test util.
  • The "FIXME: late compilation" comments on TypedFilterBuilder::build, BaseFilter::try_new, change_operator, change_granularity, and query_properties.rs:1069 clearly mark the remaining Compiler threading as a transitional state — that's exactly the kind of code archaeology future contributors need.
  • The split of recording state into state (plain object passed in MemberSqlTemplateCompiler) avoids the old Rc<RefCell<ProxyState>> / Weak dance entirely. Much easier to reason about.

· Branch: tesseract-memory-investigation ·

@codecov

codecov Bot commented Jun 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.63%. Comparing base (73b7805) to head (3cf8670).
⚠️ Report is 5 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #11120      +/-   ##
==========================================
+ Coverage   79.12%   83.63%   +4.50%     
==========================================
  Files         472      256     -216     
  Lines       96294    79019   -17275     
  Branches     3524        0    -3524     
==========================================
- Hits        76193    66085   -10108     
+ Misses      19586    12934    -6652     
+ Partials      515        0     -515     
Flag Coverage Δ
cube-backend ?
cubesql 83.63% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@waralexrom waralexrom requested a review from a team as a code owner June 18, 2026 14:44
@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Claude finished @waralexrom's task in 1m 8s —— View job


Code review: memory-leak fixes in the native planner — verdict: ship after one follow-up

1 medium remains. Re-reviewed after 98b3aae (null/undefined → empty string in parseTemplateResult) and 48f83e3 (broader JS test coverage; obsolete Rust bridge tests removed). Two prior low/nit items closed — see below.

Full review

Tasks

  • Re-check delta 8c58794..48f83e3
  • Verify which prior findings are now addressed
  • Re-verify the remaining medium against current query_tools.rs
  • Check existing review threads to avoid duplicates

What changed since the last review

  • 98b3aaeparseTemplateResult now coerces null/undefined member-sql results to ''. Matches what a permissive ${x} interpolation would have done in the old Rust proxy path and avoids "null"/"undefined" showing up in generated SQL.

  • 48f83e3 — adds ~237 lines of JS tests covering:

    • String() / valueOf / direct-return reference capture
    • distinct paths after .sql() vs. plain member ref
    • distinct leaves under a shared prefix
    • FILTER_PARAMS dedup behaviour (intentionally non-deduped)
    • empty FILTER_GROUP() → empty group
    • FILTER_GROUP('x') → throws on non-FILTER_PARAMS arg
    • FILTER_PARAMS and FILTER_GROUP co-existing in one template
    • numeric array → IN (...)
    • deeply nested SECURITY_CONTEXT.a.b.filter('c') (the gap from my earlier nit)
    • missing-deep-leaf path doesn't crash

    Drops filter-group.test.ts, filter-params.test.ts, multi-arg.test.ts, result-shape.test.ts, security-context.test.ts, symbol-paths.test.ts from cubejs-backend-native — those exercised the now-removed Rust proxy bridge, so their removal is correct.

Prior findings — current status

# Finding Status
medium member_mask_filters left on QueryTools, no regression test for masked-member cycle (rust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_tools.rs:33-46) Still open. Field + acknowledging comment remain on QueryTools; no_query_tools_leak.rs still only covers the join-cache cycle. Recommend moving member_mask_filters onto State in this PR (the structure already supports it) or filing a tracked follow-up with an #[ignore]'d regression test.
low TypedFilter/BaseFilter reach QueryTools from other caches — invariant comment recommended Still open (doc-only).
low parseTemplateResult strict-coerce vs. O(n²·k) JSON.stringify dedup in uniqueInsertPath Still open (perf, low-priority).
low coerceFilterValue falsy parity with contextSymbolsProxy is implicit Still open (doc-only).
low SqlTemplateArgs::clone_to_context dead-but-kept Still open — #[allow(dead_code)] + one-line rationale would prevent accidental removal.
nit Deeply nested SECURITY_CONTEXT path not pinned by a test Closed by 48f83e3 (navigates nested struct values through the recursive proxy, does not crash on a missing deep leaf path).
nit BaseQuery.compileMemberSql reorders args vs. bridge signature Still open (doc-only).

The fixes still look correct overall. The only thing I'd actively push on before merge is the open medium — everything else is doc/perf polish that can land separately.

· Branch: tesseract-memory-investigation ·

@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Claude finished @waralexrom's task in 35s —— View job


Code review: memory-leak fixes in the native planner — verdict: ship

1 medium remains. Re-checked after 1e3244e (drop QueryTools ref from TypedFilter to remove the latent Rc cycle). One low finding from the prior review is now closed.

Full review

Tasks

  • Re-check delta 48f83e3..1e3244e
  • Verify which prior findings are now addressed
  • Re-verify the remaining medium against current query_tools.rs

What changed since the last review

  • 1e3244eTypedFilter no longer carries Rc<QueryTools>. The reference is now threaded in at use-sites (base_filter.rs, query_properties.rs) rather than cached on the filter itself. This closes the latent-cycle risk flagged in the prior review: anything caching a TypedFilter/BaseFilter under QueryTools can no longer silently re-create the chain.

Prior findings — current status

# Finding Status
medium member_mask_filters left on QueryTools, no regression test for masked-member cycle (rust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_tools.rs:33-46) Still open. Recommend moving onto State here or filing a tracked follow-up with an #[ignore]'d regression test.
low TypedFilter holds Rc<QueryTools> — invariant comment recommended Closed by 1e3244e — the Rc is gone entirely, so the invariant is enforced structurally.
low parseTemplateResult O(n²·k) JSON.stringify dedup in uniqueInsertPath Still open (perf, low-priority).
low coerceFilterValue falsy parity with contextSymbolsProxy is implicit Still open (doc-only).
low SqlTemplateArgs::clone_to_context dead-but-kept Still open — #[allow(dead_code)] + one-line rationale would prevent accidental removal.
nit BaseQuery.compileMemberSql reorders args vs. bridge signature Still open (doc-only).

The PR already has paveltiunov's approval. Only the open medium would be worth pushing on pre-merge; everything else is doc/perf polish that can land separately.

· Branch: tesseract-memory-investigation ·

@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Claude finished @waralexrom's task in 1m 5s —— View job


Code review: memory-leak fixes in the native planner — verdict: ship

0 blocking. Re-checked after 3cf8670 (docs-only: rewrites the member_mask_filters comment in query_tools.rs to reflect that the cycle was closed by 1e3244e). The medium finding from the prior review is now structurally resolved.

Full review

Tasks

  • Re-check delta 1e3244e..3cf8670
  • Verify the new comment's claim against current TypedFilter/BaseFilter
  • Re-verify which prior findings remain

What changed since the last review

  • 3cf8670 — Docs-only. The acknowledgement comment on QueryTools::member_mask_filters (rust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_tools.rs:33-45) is rewritten from "KNOWN REMAINING CYCLE" to: "Nothing reachable from a stored FilterItem holds an Rc, so this cache forms no reference cycle. It stays here only until the early-compilation refactor resolves mask filters up front."

Verification

Confirmed against current source:

  • planner/filter/typed_filter.rs:53-60TypedFilter struct stores only member_evaluator, filter_type, operator, values, use_raw_values, op. No Rc<QueryTools>.
  • planner/filter/typed_filter.rs:103, 117Rc<QueryTools> lives on TypedFilterBuilder only; consumed by build() for transient compile work (the to_date rolling-window granularity branch), never moved into the built filter. Confirmed by FIXME: late compilation note at typed_filter.rs:171-175.
  • planner/filter/base_filter.rs:21-23BaseFilter is { typed_filter: TypedFilter }. No Rc<QueryTools> reachable from a stored FilterItem.

The doc update is accurate; the medium finding from the prior review is closed.

Prior findings — current status

# Finding Status
medium member_mask_filters cycle (query_tools.rs:33-45) Closed by 1e3244e + 3cf8670TypedFilter no longer carries Rc<QueryTools>, so the cached FilterItem chain terminates at the symbol/evaluator level. Comment now reflects this.
low TypedFilter invariant comment Closed (in prior review).
low parseTemplateResult JSON.stringify dedup is O(n²·k) Still open (perf, low-priority).
low coerceFilterValue falsy parity with contextSymbolsProxy is implicit Still open (doc-only).
low SqlTemplateArgs::clone_to_context dead-but-kept Still open — #[allow(dead_code)] + rationale would prevent accidental removal.
nit BaseQuery.compileMemberSql arg reorder vs. bridge signature Still open (doc-only).

Nothing remaining is blocking. PR has paveltiunov's approval; the three low items and one nit are all doc/perf polish that can land separately.

· Branch: tesseract-memory-investigation ·

@waralexrom waralexrom merged commit 6eb7afa into master Jun 18, 2026
198 of 203 checks passed
@waralexrom waralexrom deleted the tesseract-memory-investigation branch June 18, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

javascript Pull requests that update Javascript code rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants