Skip to content

perf: repo-wide optimization audit (2026-10-11) — tracking issue #566

Open

Description

@ajitpratap0

Summary

Tracking issue for the repo-wide code optimization audit run on 2026-10-11 against main at 31b5b1c (v1.14.0). Nine package-group finders (tokenizer, parser, AST and a pprof-driven pass on Opus; the rest on Sonnet) proposed 80 findings. A per-group adversarial verifier re-read every cited line, re-ran or wrote benchmarks, and refuted 24; 1 was already covered by #460. The 54 confirmed findings were clustered into the 22 issues below. Every issue cites current file:line references and measured numbers.

Headline measurements (Apple M4, prototypes in a scratch copy, not committed)

Path Today Prototype
Tokenize 1 MB input 1.74 s 23-29 ms (#544)
Tokenize 1k-element IN list 4.9 ms ~90 µs (#544)
Tokenize ComplexSQL 10.0 µs / 159 allocs ~6 µs / 13 allocs (#544 #545 #546 #549)
Pooled parse, realistic join 4.1 µs / 80 allocs / 11 KB 2.75 µs / 42 allocs / 3.5 KB (#551 #552 #553 #554)
Any dialect-aware call +20-25 µs, +43 KB overhead removed (#550)

Issues by area, in suggested landing order

Tokenizer (hot path)

Parser (hot path)

AST (pools and visitors)

Higher-level packages

Already tracked

Refuted or dropped (not filed, for the record)

  • LSP synchronous message loop: a design change, not a perf fix; risks ordering bugs vs didChange.
  • Missing ReleaseAST in cbinding, schema validators, advisor, linter.LintString: real, but measured gain is noise and some paths may retain node pointers.
  • LSP completion ToUpper/ToLower per item: no allocation, Go's ASCII fast path returns the original string.
  • Config cache os.Stat + Clone per lookup: deliberate invalidation and mutation protection, cold path.
  • WASM MarshalIndent and build flags: output contract and build-size items, not runtime hot path.
  • Linter per-rule AST walks and per-line re-tokenizing: plausible but unmeasured; would change public Rule/Context API.
  • Formatter nodeFormatter/Builder per nested call, transpiler per-rule passes, FromSQL re-parse: cold or dominated by parse cost.
  • TableReference field reordering, Fingerprint %x formatting, renderSelect shallow copies: negligible.
  • Validate's TrimSpace + []byte copy: the proposed unsafe view would break aliasing guarantees.
  • Global spanInfo map (ast/span.go:77): unsynchronized and unbounded, but no production caller writes to it.

Process

Each child issue has acceptance criteria: the named benchmarks must improve or not regress (benchstat, -count=6), task test:race passes, pooling invariants hold, and performance_baselines.json is updated when a baseline moves. Please benchmark before and after on the same machine and paste benchstat output in the PR.

Generated by an automated optimization audit (9 finders, 9 adversarial verifiers, 1 synthesizer) on 2026-10-11.

Activity

  1. ajitpratap0 commented on Oct 11, 2026

    @ajitpratap0
    OwnerAuthor

    Fix status (2026-10-11)

    Fixes were implemented by a multi-agent workflow (Fable plans and validates; Opus/Sonnet implement and review). Every PR below passed the full race gate (go test -race on all packages except cbinding), go vet, golangci-lint, an independent code review, and a benchstat comparison; branches deliberately do not touch CHANGELOG.md or performance_baselines.json so they merge without conflicts. One consolidated CHANGELOG/baselines PR should follow once these land.

    PR Issues Headline (allocs/op or time, measured)
    #577 #544 #545 #547 #549 tokenizer: 1 MB input 1.75 s -> 22 ms; -25% allocs; identifier scan -18% ns/op; -45% B/op on 10 KB+ inputs
    #578 #551 #553 #554 parser: ComplexSelect 46 -> 22 allocs; qualified refs -1 alloc each; paginated join 37 -> 26 allocs
    #581 #555 #556 ast: double-Put and stale-reset defects fixed; PutExpression after a 20k-element list 2995 ns -> 19 ns; ReleaseAST B/op -90%
    #575 #558 ast SQL(): 15-line query 122 -> 1 allocs, ~3.5x faster
    #569 #559 SplitStatements 855 -> 1 allocs
    #571 #560 release hygiene: balanced pool accounting in batch/transform/Transpile
    #572 #561 ExtractMetadata 114 -> 24 allocs, ~2.5x faster
    #568 #562 gosqlx.Lint -30 allocs/call; L015 96 KB copy removed
    #574 #563 security scanner: clean-query scan 28.6 us -> 7.0 us; extra Opus security review, zero finding differences over 400k inputs
    #573 #564 LSP: 10k-line diagnostics 789 MiB / 512 ms -> 1.2 MiB / 7 ms
    #570 #565 schema loaders: 3N+1 -> 4 queries per load; MaxOpenConns=1 deadlock and in-memory SQLite pitfall fixed

    Deferred by maintainer decision (not started): #546, #548, #550 (the tokenizer-2 chain, stacked on #577), #552, #557. Branch perf/tokenizer-zero-copy-and-dialect exists at the #577 head with no own commits.

    Note: external PR #576 (fold identifier case without per-word allocation) overlaps #545, which is included in #577; one of the two should be preferred.

  2. ajitpratap0 commented on Oct 13, 2026

    @ajitpratap0
    OwnerAuthor

    Status update 2026-10-13 (batch 2)

    Batch 1 (#544 #545 #547 #549 #551 #553 #554 #555 #556 #558 #559 #560 #561 #562 #563 #564 #565) is fully merged (PRs #568–#575, #577, #578, #581).

    Batch 2 PRs, each with an independent Opus review and validation (full race gate, vet, lint, benchstat A/B before/after, golden/differential tests where output equivalence matters):

    #595 is the largest: #520 (Oracle :1/:name binds), #546 (substring token values, slab Words, TokenizeString), #548 (zero-copy escape-free quoted literals, 400+ → 2 allocs on quote-heavy input), #550 (drop the never-read keyword table; dialect-aware entry points now cost the same as the default path, e.g. ParseBytesWithDialect 70 → 28 allocs). It also drops the token slice in Parser.Reset so a pooled parser no longer retains the previous caller's SQL string.

    Side findings while doing this work:

    Still owed: one consolidated PR updating CHANGELOG.md and performance_baselines.json for all merged perf PRs (branches deliberately did not touch either).

  3. ajitpratap0 commented on Oct 13, 2026

    @ajitpratap0
    OwnerAuthor

    Status update (2026-10-13): merge train complete

    All open PRs from this effort are merged. Each was rebased onto the current main, passed the full race test suite locally, went green on every CI check (including Vercel), and had its review comments answered. Each was then squash-merged.

    Merged in this train

    PR Change
    #594 .gitattributes with LF line endings, fixing Windows CRLF golden failures
    #588 Visitors get pointers into the AST (#557)
    #582 Schema foreign-key join keys (#579)
    #586 Parser preprocess buffer reuse (#552)
    #595 Tokenizer chain: zero-copy tokens, Oracle bind variables, keyword table (#520, #546, #548, #550)
    #585 MySQL three-part identifiers and parenthesized join groups (#521)
    #591 CLI NOT EXISTS nil dereference (contributor PR)
    #592 Numeric window-frame offsets (#589, contributor PR)
    #584 INSERT ... WITH (#537); gosqlx format keeps leading CTEs on INSERT, UPDATE and DELETE
    #583 ClickHouse parametric aggregate params in gosqlx format (#531)
    #597 Hardening for adversarial input (details below)

    #597 contains:

    • iterative comment skipping
    • comments counted toward MaxTokens
    • the E2007 depth limit extended to unary operators, NOT and derived tables
    • linear-time set-operation rendering
    • $name placeholders keeping their text

    Consolidated docs: #598 adds CHANGELOG [Unreleased] entries for everything since v1.14.0, starting with a Behavior changes section, and refreshes performance_baselines.json.

    Follow-up issues found during review were filed as #599–#620. Each was reproduced on main 61ca6c8, and none duplicates an existing issue.

    Also open from earlier: #596 (arithmetic on the right-hand side of a comparison).

  4. ajitpratap0 commented on Oct 14, 2026

    @ajitpratap0
    OwnerAuthor

    Batch 3 (#599–#620) is complete

    All 22 issues are fixed in 12 PRs, merged through one merge train. Every PR got an Opus adversarial and security review before merge, and the fix commits were independently validated by Opus.

    PR Issues Merge commit
    #621 #617 e1d54df
    #625 #618, #619 c35349f
    #626 #614, #615 c1f69d5
    #622 #620 41a5035
    #629 #608, #609 82982e5
    #623 #610 3baad9d
    #628 #605, #606, #607 1cd8f78
    #630 #603, #612 458b3c3
    #631 #602, #600 1be0808
    #627 #613, #616 95978a4
    #632 #601, #611, #604 728c544
    #624 #599 baf528e

    The CHANGELOG and refreshed baselines are in #598 (merged as d7338ce).

    Security: the review of #632 found render-time SQL injection, and main had it too. A quoted alias such as "a; DROP TABLE x; --" came back from gosqlx.Format and SQL() as two statements. #632 closes the whole class:

    • every quoted-name position is now quoted when needed;
    • the tokenizer treats smart quotes inside ASCII-quoted tokens as ordinary characters;
    • keyword fields are checked against exact allow-lists.

    It was validated with hostile-name fuzzing (549k cases) and AST-mutation fuzzing: 0 injected statements and 0 injectable paths.

    Follow-ups found during batch 3, not yet filed

    Per the earlier instruction not to add new issues for now, these are recorded here rather than filed. Each one is already on main.

    Rendering and round-trip, all fail-safe: the output does not re-parse, or the CLI refuses it

    • SQL Server CROSS APPLY / OUTER APPLY render as CROSS APPLY JOIN …, and TOP is lost in the derived table.
    • MySQL MATCH (…) AGAINST (…) renders as AGAINST AGAINST(…).
    • Oracle START WITH / CONNECT BY and QUALIFY are not rendered.
    • ALTER TABLE … ADD CONSTRAINT renders as an empty string from SQL() and as ALTER from the formatter. ALTER ROLE / POLICY / CONNECTOR are not rendered.
    • ALTER TABLE … ADD COLUMN IF NOT EXISTS / DROP COLUMN IF EXISTS are not parsed. The AST fields exist.
    • ClickHouse: SQL() drops ORDER BY after ENGINE = MergeTree(), the formatter drops ARRAY JOIN, and INTO OUTFILE after SETTINGS is not rendered.
    • CREATE TABLE t AS SELECT … renders as CREATE TABLE t (), and Snowflake CREATE SEQUENCE renders as an empty string. Snowflake CREATE STAGE RawSQL is lost.
    • fn()(x) rendering, JOIN-side table functions, and INSERT … SET t.a / = DEFAULT.
    • Dialect-specific INTERVAL rendering, and the MySQL-only SET form when transpiling.

    Quoting and dialects

    • Quoting is dialect-agnostic: ANSI "…" everywhere. MySQL output needs backticks unless ANSI_QUOTES is set. The CLI now refuses rather than emit wrong MySQL.
    • A name quoted only for its case ("MixedCase") renders bare, because the AST does not record quoting for aliases or column lists. Recording Quoted everywhere would let case-sensitive names round-trip.
    • In the default dialect, "#x" renders bare and does not re-parse.
    • Backslash escaping is not per dialect: 'a\b' becomes 'a\\b' in SQL Server, Oracle, SQLite, Snowflake and ClickHouse. The MySQL tokenizer does not handle \" inside double-quoted strings.
    • Lowercase any(...) turns into ALL.
    • Window frame-type casing and dialect gating.

    Security scanner

    • It does not descend into derived tables, CTEs or comma joins.
    • The NULL-column UNION check never fires.

    API, schema and linter

    CI

    • TestFormatAST_SetOperationChainLinearTime uses a 2 s wall-clock limit. It failed once on a starved windows-latest runner (4.6 s) alongside unrelated lsp/security timeouts. A rerun passed. It should use a relative or operation-count bound.
    • Dependabot [Parser] Add DISTINCT keyword support #142 (website sprintf-js) is untouched.
  5. ajitpratap0 commented on Oct 18, 2026

    @ajitpratap0
    OwnerAuthor

    Status update for the issues tracked under this one:

    Issue #566 stays open.

  6. ajitpratap0 commented on Oct 18, 2026

    @ajitpratap0
    OwnerAuthor

    Follow-up outcomes for #781-#784 (found while fixing #596/#590):

    All four issues are closed. No admin merges, force-pushes, or CHANGELOG edits were made, and no scratch worktrees remain from this work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions