Skip to content

Prune placeholders only when a null was inlined - #153

Merged
gmpassos merged 1 commit into
masterfrom
perf/prune-only-when-needed
Aug 12, 2026
Merged

Prune placeholders only when a null was inlined#153
gmpassos merged 1 commit into
masterfrom
perf/prune-only-when-needed

Conversation

@gmpassos

Copy link
Copy Markdown
Contributor

Follow-up to #152, which introduced a cost this removes.

The regression

1.15.1 rewrites field == ? bound to null into field IS NULL. That leaves the parameter resolved but unreferenced, and PostgreSQL rejects a statement carrying variables it does not use — so #152 added a prune that materialized the encoded output and scanned it for every placeholder:

var output = context.outputString;          // StringBuffer.toString(), every encode
parametersPlaceholders.removeWhere(
  (key, _) => !_isPlaceholderInOutput(output, parameterPlaceholder(key)));

Every query paid it, including the overwhelmingly common one that compares nothing against null — O(placeholders × output length), plus a '@$key' allocation per placeholder, plus the full toString().

Measured in isolation on a 3-placeholder condition of the shape entity queries produce:

per encoded condition
1.15.1 0.376 us
this PR 0.074 us
removed 0.302 us

For scale, the 1.15.0 dispatch work brought a whole logged route call to ~0.90us. This was adding roughly a third of that back, on every query.

The fix

Only a placeholder actually rewritten to IS NULL/IS NOT NULL can become unreferenced. EncodingContext now records those keys as they are written, so:

  • nothing compared against null → the prune returns immediately: no toString(), no scan, no allocation;
  • something was → only the recorded keys are checked, and the output is materialized lazily, since a recorded key may still be referenced by another operator (field > ? bound to null) and then there is nothing to look for.

The set is left null while empty — allocating one per encoded condition would give back part of the saving.

Verification

No behaviour change is intended: same statements, same bound parameters. Checked against the suites that caught the original bug, where an unreferenced parameter is a hard error (Contains superfluous variables):

  • PostgreSQL 65/65, MySQL 65/65 (Docker)
  • Full VM suite 915 passing, ensure_build included
  • dart analyze --fatal-infos --fatal-warnings and dart format --set-exit-if-changed clean

🤖 Generated with Claude Code

1.15.1 rewrites `field == ?` bound to null into `field IS NULL`, which
leaves that parameter unreferenced, and found it by materializing the
encoded output and scanning it for every placeholder. Every query paid
that, including the overwhelmingly common one comparing nothing against
null: ~0.30us per encoded condition (3-placeholder condition), against the
~0.90us of a whole logged route call after the 1.15.0 dispatch work.

Only a placeholder actually rewritten to `IS NULL`/`IS NOT NULL` can become
unreferenced, so `EncodingContext` now records those keys as they are
written, and the prune returns immediately when the set is empty — no
`toString()`, no scan. The output is materialized lazily even when it is
non-empty, since a recorded key may still be referenced by another operator
(`field > ?` bound to null), and then there is nothing to look for.

The set is left null while empty: allocating one per encoded condition
would give back part of what this saves.

No behaviour change: same statements, same bound parameters. Verified by
the suites that caught the original bug — PostgreSQL 65/65 and MySQL 65/65,
where an unreferenced parameter is a hard error ("Contains superfluous
variables").

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 68.24%. Comparing base (c05f56e) to head (339ee9d).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #153      +/-   ##
==========================================
- Coverage   68.24%   68.24%   -0.01%     
==========================================
  Files          66       66              
  Lines       22147    22160      +13     
==========================================
+ Hits        15114    15122       +8     
- Misses       7033     7038       +5     
Flag Coverage Δ
unittests 68.24% <100.00%> (-0.01%) ⬇️

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.

@gmpassos
gmpassos merged commit 4d34116 into master Aug 12, 2026
5 checks passed
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