Skip to content

perf(ast): ASCII fast path in identifierNeedsQuoting, hash-free isReservedName [W5-04] - #945

Open
ajitpratap0 wants to merge 5 commits into
mainfrom
perf/w5-04-quote-ascii-fastpath
Open

ajitpratap0 wants to merge 5 commits into
mainfrom
perf/w5-04-quote-ascii-fastpath

Conversation

@ajitpratap0

@ajitpratap0 ajitpratap0 commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • identifierNeedsQuoting: 256-entry byte-class table for ASCII names; the first non-ASCII byte falls back to the unchanged rune loop (identifierNeedsQuotingRunes).
  • isReservedName: first-letter/length index of [16]byte entries derived at init from reservedNameWords (which stays the single word list), array-compare scan, zero allocations. This deviates from the PLAN label "switch-based": a generated index keeps one source of truth at the same speed.
  • No API or behaviour change; AST.SQL() and formatter output byte-identical.

Test Plan

  • New pkg/sql/ast/quote_fastpath_test.go: frozen reference copies, exhaustive 0-2 byte and piece-combination equivalence, reserved-name case/affix/exhaustive 3-byte equivalence, index invariants, FuzzQuoteFastPath (60s, 944k execs, clean), zero-alloc pins, micro benchmarks.
  • Mutation proof (each reverted): M1-M3 TestIdentifierNeedsQuoting_MatchesReference; M4 TestIdentifierByteClass_NonASCII; M5, M7 TestIsReservedName_MatchesReference; M6 TestReservedNameIndex_Invariants + TestIsReservedName_MatchesReference.
  • Byte identity: SQL dump of all 5,422 corpus renderings identical to origin/main (git-archive base). task goldens clean, no golden changes.
  • task check (after merging origin/main), task test:cbinding, go test ./pkg/compatibility/..., staticcheck on pkg/sql/ast/..., targeted -race run: all pass.
  • Local A/B (task bench:ab, Apple M4, shared machine, indicative only; spread up to +/-223%): BenchmarkRender geomean -9.78%; sql/select_where -17%, sql/join -15%, sql/cte -21%, readable/delete -9.7%; sql/complex and sql/insert no change.
  • CI perf gate on 8996df9 (comment 6612262939; layout-randomized, 20 layouts/arm, runner CPU Intel(R) Xeon(R) 6975P-C): PASS. No time regression; allocs/op and B/op unchanged on every gated benchmark; instructions/op between -7.34% (e2e/cte) and +0.06% (e2e/simple_select), e2e/complex -4.32%; A/A' control clean (0 of 52 benchmarks moved more than 3%, suite geomean -0.08%, p 0.369). Low power list (9): ParseError/long_prefix/validate, ParseE2E/delete, LintRule/L010, ParseE2E/simple_select, ParseRW/clickhouse/medium, ParseRW/snowflake/medium, Tokenize/scratch/insert, Tokenize/scratch/complex, LintRW/oracle; none is in this change's blast radius, and allocs/op, B/op and instructions/op guard them.
  • The one significant gated improvement is the guard PLAN section 5 names, BenchmarkNormalize (renders through the same helpers): simple_select -2.85%, join -2.35% (both p<0.001, status ok), complex -4.94% (p<0.001, flagged 'faster'); allocs/op and B/op unchanged. Under PLAN 5.3 rule 5 this is the amd64 evidence of a real gain, so the PR is not closed.
  • Refreshed PR gate after merging origin/main (base 6e86a6b, head abde69d, AMD EPYC 9V74): PASS, all checks green. Normalize again significant and faster: simple_select -1.82%, join -2.27%, complex -3.32% (p<0.001, A/A' -0.85%..-0.47%), allocs/op and B/op unchanged.
  • Target benchmark pkg/formatter BenchmarkRender (not in gate-suite.txt), via the layout-randomized matrix route (perf-gate.yml -f mode=matrix -f rounds=20 -f layouts=20, run 41706707217, AMD EPYC 9V74, GOGC=off, pinned CPU). The job hit the 230-minute limit before the A/A' stage and the report step, so its report is ERROR; I ran scripts/perf/gate on the 16 completed layouts per arm of the raw gcoff files instead (no A/A' control for this run). The base of that run (6d29d68) carried the L007 keyword_case change that this branch lacked, so ignore its Lint rows; Render rows are unaffected. Render/sql: simple_select -10.96%, select_where -17.56%, join -14.77%, aggregate -14.35%, subquery -15.16%, cte -19.89%, window -18.57%, complex -17.31%, insert -10.70%, update -13.08%, delete -12.78% (all p<0.001, allocs 1 -> 1, B/op +0.00%). Render/readable: simple_select -7.65%, select_where -10.98%, join -8.23%, aggregate -9.80%, subquery -8.63%, cte -9.17%, window -8.14%, complex -9.01%, insert -7.95%, update -5.72%, delete -7.57% (all p<0.001, allocs/op and B/op unchanged). Targets met: render/sql <= -7% (worst row -10.70%) and render/readable <= -3% (worst row -5.72%).
  • PLAN deviation: a generated first-letter/length index instead of a switch-based isReservedName (one word list stays the source of truth).

Breaking Changes

None.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2

Test and others added 2 commits November 3, 2026 22:46
…ervedName [W5-04]

Byte-class table for ASCII names (rune loop only for non-ASCII), and a
first-letter/length index derived from reservedNameWords replaces the map
lookup. SQL() output is byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2
@vercel

vercel Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
gosqlx-website Ready Ready Preview Nov 4, 2026 12:03am UTC

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Claude finished @ajitpratap0's task in 22s —— View job


Review of PR #945 (ASCII fast path for identifier quoting)

I read the diff and traced the logic by hand. I did not run the tests or benchmarks. The sandbox blocked the go test command, so the check below is by reading only.

Verdict: Looks correct. I found no blocking issues.

identifierNeedsQuoting (pkg/sql/ast/sql.go)

  • The byte-class state machine matches the rune loop for ASCII input.
    • Digit-led parts: a word byte (including *) in a digit-led part returns true, as the rune loop does. Digits keep the part bare, which is what ClickHouse d.1 needs.
    • $: it is rejected at the start of a part or in a digit-led part, and allowed later, as in the rune loop.
    • Everything else: other ASCII bytes return true.
  • Falling back to identifierNeedsQuotingRunes(name) on the first byte ≥ 0x80 is safe. The fallback restarts from the beginning, so no state carries over. Invalid UTF-8 also goes down the rune path, which is the conservative choice.
  • The empty-name check is duplicated in both functions. That is harmless.

isReservedName (pkg/sql/ast/quote_names.go)

  • Deriving the index at init from reservedNameWords keeps one source of truth.
  • The init-time validation panics on bad words, so a future typo in the list fails loudly instead of silently missing the index.
  • uint16 and uint8 are comfortably wide enough for the entry count and bucket size.
  • The [16]byte array compare requires up to be zero-padded beyond len(name). up is a fresh zero value, so that holds.

Minor nits (non-blocking)

  1. reservedNameEntry and reservedNameIndex have no doc comments, unlike the code around them. A short comment on the bucket layout ([firstLetter][length] → start, n) would help. The anonymous struct type is repeated twice in the initializer, so a named reservedBucket type would be tidier.
  2. The CHANGELOG entry says "measured numbers await the CI verdict". Fill in the real numbers once the perf gate reports, as the repo's "measure, don't assume" rule expects.
  3. In the changelog line, "Identifier quoting [W5-04]" puts the ticket tag in a different spot than the neighbouring entries, which put it at the end. This is cosmetic.

Tests

  • Test coverage is strong:
    • frozen reference copies,
    • exhaustive short-input equivalence,
    • a fuzz test,
    • zero-allocation pins,
    • mutation proofs.
  • The FuzzQuoteFastPath run (944k execs) and the byte-identical corpus dump give good confidence that behaviour is unchanged.

Performance and security

  • No allocations and no map hashing remain on the hot path.
  • There is no new injection surface. Names that need quoting still go through writeQuotedIdentifier, and the fast path only returns "bare" for the same inputs as before.

@github-actions github-actions Bot added documentation Improvements or additions to documentation ast AST node changes labels Nov 3, 2026
…ut [W5-04]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Perf gate: PASS (shadow mode: report only, this check never fails)

Base 6e86a6b8b9b1, head abde69dc9aa9. 20 interleaved rounds, 20 randomized code layouts per arm (-randlayout seeds 417437552412..417437552431; A/A' 417437552432..417437552451), benchtime 100ms, -cpu 1, GOMEMLIMIT 64MiB, pinned to CPU 3 (SMT siblings 2-3) with taskset.

Runner CPU: AMD EPYC 9V74 80-Core Processor (x86_64). Base and head ran interleaved in this job, so the tables show their ratios only; absolute timings (raw files in the artifact) hold for this CPU and this job alone and are never compared across jobs.

  • 2 benchmark(s) faster by 3% or more

Rules: each arm runs 20 randomized code layouts (-ldflags=-randlayout); time regresses at >= +3% when significant at a false discovery rate of 0.01 over the suite (Benjamini-Hochberg on exact Mann-Whitney U p-values of the per-layout medians, so a regression must hold across layouts, not within one binary), or a suite geomean >= +1.5% (per-layout suite geomeans, same test); allocs/op must not increase; B/op must not rise more than 2%; instructions/op must not rise more than 1.5%. The A/A' control runs the base revision under a disjoint set of layout seeds; when more than 10% of benchmarks move over 3% in it, the run is noisy: re-run once, then inconclusive (neutral).

Gated suite (GOGC=off, GOMAXPROCS=1)

benchmark time p A/A' spread B/op allocs/op status
ParseE2E/simple_select -0.12% 0.850 -1.13% 1.78% +0.00% 5 → 5 ok
ParseE2E/select_where +0.26% 0.490 +0.09% 1.04% +0.00% 18 → 18 ok
ParseE2E/join -0.14% 0.808 +0.12% 1.59% +0.00% 17 → 17 ok
ParseE2E/aggregate +0.12% 0.914 -0.28% 3.97% +0.00% 18 → 18 ok
ParseE2E/subquery +0.90% 0.086 -0.06% 1.18% +0.00% 14 → 14 ok
ParseE2E/cte +0.24% 0.525 +0.37% 1.21% +0.00% 21 → 21 ok
ParseE2E/window -0.34% 0.330 -0.31% 0.97% +0.00% 11 → 11 ok
ParseE2E/complex +0.28% 0.561 -0.23% 1.55% +0.00% 49 → 49 ok
ParseE2E/insert -0.05% 0.968 -0.66% 1.63% +0.00% 17 → 17 ok
ParseE2E/update -0.05% 0.882 -0.43% 1.73% +0.00% 13 → 13 ok
ParseE2E/delete -0.26% 0.705 -0.64% 1.57% +0.00% 5 → 5 ok
ParseRW/postgresql/medium +0.13% 0.925 -1.33% 2.53% +0.00% 646 → 646 ok
ParseRW/mysql/medium +1.36% 0.091 -0.11% 2.58% +0.00% 368 → 368 ok
ParseRW/mariadb/medium +0.38% 0.512 -0.39% 1.28% +0.00% 528 → 528 ok
ParseRW/sqlserver/medium +0.63% 0.102 +0.24% 0.44% +0.00% 927 → 927 ok
ParseRW/oracle/medium +0.56% 0.072 -0.13% 0.72% +0.00% 1090 → 1090 ok
ParseRW/sqlite/medium +0.48% 0.341 -0.25% 1.22% +0.00% 347 → 347 ok
ParseRW/snowflake/medium +0.95% 0.076 +0.29% 1.61% +0.00% 776 → 776 ok
ParseRW/clickhouse/medium +0.59% 0.327 +0.06% 1.17% +0.00% 383 → 383 ok
ParseFresh/simple_select -0.08% 0.818 +0.16% 1.15% +0.00% 6 → 6 ok
ParseFresh/join +0.22% 0.507 +0.49% 1.17% +0.00% 18 → 18 ok
ParseFresh/complex +0.32% 0.394 +0.20% 1.42% +0.00% 50 → 50 ok
PoolGetPut/serial -3.53% <0.001 +0.17% 0.30% +0.00% 0 → 0 faster
Tokenize/scratch/join -0.04% 0.989 -0.71% 1.38% +0.00% 0 → 0 ok
Tokenize/scratch/complex +0.24% 0.766 -0.48% 1.47% +0.00% 0 → 0 ok
Tokenize/scratch/insert +0.53% 0.665 +0.11% 3.15% +0.00% 0 → 0 ok
Normalize/simple_select -1.82% <0.001 -0.85% 1.32% +0.00% 7 → 7 ok
Normalize/join -2.27% <0.001 -0.82% 0.76% +0.00% 23 → 23 ok
Normalize/complex -3.32% <0.001 -0.47% 0.75% +0.00% 68 → 68 faster
TransformParse/simple_select +0.12% 0.892 +0.00% 0.86% +0.00% 5 → 5 ok
TransformParse/join -0.08% 0.946 +0.38% 1.05% +0.00% 17 → 17 ok
TransformParse/complex +0.48% 0.561 +0.10% 2.31% +0.00% 49 → 49 ok
LintParallel/rules +0.86% 0.414 +0.94% 3.03% +0.00% 68929.5 → 68929 ok
LintParallel/lint +0.36% 0.698 -0.53% 1.16% -0.00% 147508 → 147508 ok
LintRW/postgresql +0.36% 0.429 +0.01% 1.05% +0.00% 16338 → 16338 ok
LintRW/mysql -0.03% 0.968 +0.51% 1.72% -0.00% 20352.5 → 20352 ok
LintRW/mariadb -0.66% 0.289 +0.23% 1.67% +0.00% 18484 → 18484 ok
LintRW/sqlserver +0.57% 0.253 +0.80% 1.59% +0.00% 15940 → 15940 ok
LintRW/oracle -0.02% 0.989 +0.47% 2.62% +0.00% 19674 → 19674 ok
LintRW/sqlite -0.57% 0.277 -0.71% 1.41% +0.00% 18302 → 18302 ok
LintRW/snowflake +0.11% 0.779 -0.09% 1.40% +0.00% 17765 → 17765 ok
LintRW/clickhouse +0.72% 0.265 +0.94% 2.30% +0.00% 20645 → 20645 ok
LintRule/L007 -0.32% 0.698 -0.29% 1.96% +0.00% 1194 → 1194 ok
LintRule/L010 -0.01% 0.989 +0.15% 1.54% +0.00% 0 → 0 ok
ParseError/syntax_mid/parse -0.32% 0.441 -0.24% 1.59% +0.00% 12 → 12 ok
ParseError/syntax_mid/validate -0.18% 0.871 -0.33% 1.77% +0.00% 12 → 12 ok
ParseError/syntax_end/parse +0.18% 0.787 -0.56% 2.06% +0.00% 16 → 16 ok
ParseError/syntax_end/validate -0.28% 0.655 -0.93% 2.16% +0.00% 16 → 16 ok
ParseError/tokenize/parse -1.36% 0.204 -0.60% 2.76% +0.00% 4 → 4 ok
ParseError/tokenize/validate -1.45% 0.126 +0.02% 1.95% +0.00% 21 → 21 ok
ParseError/long_prefix/parse -0.05% 0.904 -0.29% 1.22% +0.00% 219 → 219 ok
ParseError/long_prefix/validate +0.44% 0.114 +0.27% 0.68% +0.00% 219 → 219 ok
geomean -0.10% 0.398 -0.13%

time and A/A' are Hodges-Lehmann shifts over the per-layout medians. spread: the base arm's robust standard deviation across its layouts (1.4826 x MAD, relative), i.e. how much this benchmark moves between builds that differ only in function placement, plus run-to-run noise; a change much smaller than it cannot be resolved in one run.

A/A' control (base revision, disjoint layout seeds): 0 of 52 benchmarks moved more than 3%; 0 would have been flagged by the time rule; suite geomean -0.13%, p 0.758.

Low power

1 benchmark(s) have under 80% estimated power to be flagged on their own for a +5% slowdown (their spread across layouts is too wide for 20 layouts at the Benjamini-Hochberg level alpha/m = 0.000192). A slowdown below the last column usually goes unflagged there; instructions/op, allocs/op and the suite geomean still guard them.

benchmark spread power at +5% slowdown caught 80% of the time
ParseE2E/aggregate 3.97% 53% +6.0%

Power is estimated with the normal approximation for the U test (Noether): power = Phi(ln(1+shift)/s * sqrt(K/2) * sqrt(3/pi) - z(1 - alpha/(2m))), with s the spread and K the layouts per arm.

Instructions per operation (cmd/perfdriver)

case instructions/op status
e2e/aggregate +0.04% ok
e2e/complex +0.00% ok
e2e/cte -0.02% ok
e2e/delete -0.04% ok
e2e/insert -0.08% ok
e2e/join -0.04% ok
e2e/select_where -0.01% ok
e2e/simple_select +0.02% ok
e2e/subquery +0.06% ok
e2e/update -0.01% ok
e2e/window +0.00% ok
rw/postgresql/medium +0.06% ok
Secondary metric: default GC, GOMAXPROCS=1 (informational)
benchmark time p
ParseE2E/simple_select -0.52% 0.427
ParseE2E/select_where +0.08% 0.970
ParseE2E/join -0.01% 1.000
ParseE2E/aggregate -1.30% 0.199
ParseE2E/subquery +0.09% 0.971
ParseE2E/cte -0.24% 0.684
ParseE2E/window -0.47% 0.579
ParseE2E/complex -0.19% 0.796
ParseE2E/insert -0.07% 0.910
ParseE2E/update +0.68% 0.353
ParseE2E/delete +0.48% 0.596
ParseRW/postgresql/medium +0.71% 0.579
ParseRW/mysql/medium +1.80% 0.123
ParseRW/mariadb/medium +1.21% 0.190
ParseRW/sqlserver/medium +0.79% 0.481
ParseRW/oracle/medium +1.95% 0.023
ParseRW/sqlite/medium +1.33% 0.123
ParseRW/snowflake/medium +1.08% 0.280
ParseRW/clickhouse/medium +0.33% 0.739
ParseFresh/simple_select -0.70% 0.105
ParseFresh/join +1.06% 0.165
ParseFresh/complex +0.48% 0.631
PoolGetPut/serial -3.57% <0.001
Tokenize/scratch/join +0.04% 0.970
Tokenize/scratch/complex -0.01% 1.000
Tokenize/scratch/insert -0.97% 0.436
Normalize/simple_select -1.07% 0.218
Normalize/join -2.26% 0.063
Normalize/complex -1.95% 0.019
TransformParse/simple_select -0.18% 0.791
TransformParse/join +0.02% 1.000
TransformParse/complex +0.04% 0.971
LintParallel/rules +0.20% 0.971
LintParallel/lint -0.20% 0.971
LintRW/postgresql +0.70% 0.739
LintRW/mysql -0.06% 0.971
LintRW/mariadb +0.65% 0.247
LintRW/sqlserver +0.28% 0.853
LintRW/oracle +0.86% 0.579
LintRW/sqlite -0.11% 0.912
LintRW/snowflake +1.06% 0.280
LintRW/clickhouse -0.62% 0.481
LintRule/L007 -1.08% 0.315
LintRule/L010 +0.73% 0.481
ParseError/syntax_mid/parse +0.50% 0.450
ParseError/syntax_mid/validate +0.39% 0.256
ParseError/syntax_end/parse +1.60% 0.041
ParseError/syntax_end/validate +1.14% 0.112
ParseError/tokenize/parse -0.73% 0.739
ParseError/tokenize/validate +1.73% 0.212
ParseError/long_prefix/parse +1.29% 0.315
ParseError/long_prefix/validate +0.38% 0.631
geomean +0.14%
Environment
date:        2026-11-04T00:06:06Z
base:        6e86a6b8b9b1e7dbb91268edd0972fbe357e773b
head:        working tree of abde69dc9aa994949468d18ec83579d4eed1870f
cpu model:   AMD EPYC 9V74 80-Core Processor (x86_64)
go:          go version go1.26.9 linux/amd64
kernel:      Linux 7.0.0-1017-azure x86_64
layouts:     20 per arm, -ldflags=-randlayout seeds 417437552412..417437552431 (base, head), 417437552432..417437552451 (A/A')
pinning:     pinned to CPU 3 (SMT siblings 2-3) with taskset
gcoff env:   GOGC=off GOMEMLIMIT=64MiB
clocksource: tsc
load:        7.33, 2.55, 0.92
instr count: cachegrind
perf probe:  Error: No supported events found. The instructions:u event is not supported. 

Architecture:                            x86_64
CPU(s):                                  4
Model name:                              AMD EPYC 9V74 80-Core Processor
Thread(s) per core:                      2
Core(s) per socket:                      2
Socket(s):                               1
Hypervisor vendor:                       Microsoft
L1d cache:                               64 KiB (2 instances)
L1i cache:                               64 KiB (2 instances)
L2 cache:                                2 MiB (2 instances)
L3 cache:                                32 MiB (1 instance)
benchstat (primary metric, samples pooled across layouts: informational, the verdict uses the per-layout test)
goos: linux
goarch: amd64
pkg: github.com/ajitpratap0/GoSQLX/pkg/fingerprint
cpu: AMD EPYC 9V74 80-Core Processor                
                        │    base     │                head                │
                        │   sec/op    │   sec/op     vs base               │
Normalize/simple_select   2.366µ ± 1%   2.323µ ± 1%  -1.80% (p=0.000 n=20)
Normalize/join            6.924µ ± 0%   6.767µ ± 1%  -2.27% (p=0.000 n=20)
Normalize/complex         18.21µ ± 1%   17.58µ ± 1%  -3.41% (p=0.000 n=20)
geomean                   6.681µ        6.514µ       -2.50%

                        │     base     │                 head                  │
                        │     B/op     │     B/op      vs base                 │
Normalize/simple_select     896.0 ± 0%     896.0 ± 0%       ~ (p=1.000 n=20) ¹
Normalize/join            2.211Ki ± 0%   2.211Ki ± 0%       ~ (p=1.000 n=20) ¹
Normalize/complex         6.117Ki ± 0%   6.117Ki ± 0%       ~ (p=1.000 n=20) ¹
geomean                   2.279Ki        2.279Ki       +0.00%
¹ all samples are equal

                        │    base    │                head                 │
                        │ allocs/op  │ allocs/op   vs base                 │
Normalize/simple_select   7.000 ± 0%   7.000 ± 0%       ~ (p=1.000 n=20) ¹
Normalize/join            23.00 ± 0%   23.00 ± 0%       ~ (p=1.000 n=20) ¹
Normalize/complex         68.00 ± 0%   68.00 ± 0%       ~ (p=1.000 n=20) ¹
geomean                   22.20        22.20       +0.00%
¹ all samples are equal

pkg: github.com/ajitpratap0/GoSQLX/pkg/gosqlx
                                │    base     │                head                │
                                │   sec/op    │   sec/op     vs base               │
ParseE2E/simple_select            1.710µ ± 1%   1.714µ ± 1%       ~ (p=0.846 n=20)
ParseE2E/select_where             3.498µ ± 1%   3.510µ ± 0%       ~ (p=0.490 n=20)
ParseE2E/join                     4.609µ ± 1%   4.599µ ± 1%       ~ (p=0.805 n=20)
ParseE2E/aggregate                3.530µ ± 4%   3.539µ ± 2%       ~ (p=0.909 n=20)
ParseE2E/subquery                 3.150µ ± 1%   3.179µ ± 1%       ~ (p=0.085 n=20)
ParseE2E/cte                      4.412µ ± 1%   4.442µ ± 1%       ~ (p=0.525 n=20)
ParseE2E/window                   2.906µ ± 1%   2.895µ ± 1%       ~ (p=0.330 n=20)
ParseE2E/complex                  12.00µ ± 2%   11.99µ ± 1%       ~ (p=0.560 n=20)
ParseE2E/insert                   2.136µ ± 1%   2.134µ ± 1%       ~ (p=0.963 n=20)
ParseE2E/update                   1.889µ ± 1%   1.889µ ± 1%       ~ (p=0.878 n=20)
ParseE2E/delete                   1.180µ ± 2%   1.176µ ± 1%       ~ (p=0.703 n=20)
ParseRW/postgresql/medium         153.6µ ± 3%   154.3µ ± 4%       ~ (p=0.925 n=20)
ParseRW/mysql/medium              86.52µ ± 2%   88.07µ ± 3%       ~ (p=0.091 n=20)
ParseRW/mariadb/medium            119.3µ ± 1%   119.9µ ± 1%       ~ (p=0.512 n=20)
ParseRW/sqlserver/medium          222.1µ ± 0%   223.7µ ± 1%       ~ (p=0.102 n=20)
ParseRW/oracle/medium             271.4µ ± 0%   273.1µ ± 1%       ~ (p=0.072 n=20)
ParseRW/sqlite/medium             68.51µ ± 1%   68.90µ ± 1%       ~ (p=0.341 n=20)
ParseRW/snowflake/medium          174.9µ ± 1%   176.3µ ± 1%       ~ (p=0.076 n=20)
ParseRW/clickhouse/medium         82.75µ ± 1%   82.80µ ± 2%       ~ (p=0.327 n=20)
ParseFresh/simple_select          7.077µ ± 1%   7.084µ ± 1%       ~ (p=0.815 n=20)
ParseFresh/join                   11.26µ ± 1%   11.31µ ± 1%       ~ (p=0.507 n=20)
ParseFresh/complex                20.02µ ± 1%   20.05µ ± 1%       ~ (p=0.394 n=20)
PoolGetPut/serial                 76.01n ± 0%   73.35n ± 0%  -3.50% (p=0.000 n=20)
ParseError/syntax_mid/parse       2.469µ ± 1%   2.464µ ± 1%       ~ (p=0.441 n=20)
ParseError/syntax_mid/validate    2.178µ ± 2%   2.182µ ± 1%       ~ (p=0.867 n=20)
ParseError/syntax_end/parse       2.840µ ± 2%   2.849µ ± 1%       ~ (p=0.784 n=20)
ParseError/syntax_end/validate    2.639µ ± 3%   2.631µ ± 3%       ~ (p=0.654 n=20)
ParseError/tokenize/parse         873.0n ± 2%   858.7n ± 2%       ~ (p=0.204 n=20)
ParseError/tokenize/validate      2.773µ ± 1%   2.728µ ± 2%       ~ (p=0.126 n=20)
ParseError/long_prefix/parse      61.37µ ± 1%   61.35µ ± 1%       ~ (p=0.904 n=20)
ParseError/long_prefix/validate   57.90µ ± 1%   58.11µ ± 1%       ~ (p=0.114 n=20)
geomean                           9.251µ        9.253µ       +0.03%

                                │     base     │                head                 │
                                │     B/s      │     B/s       vs base               │
ParseE2E/simple_select            14.50Mi ± 1%   14.47Mi ± 1%       ~ (p=0.836 n=20)
ParseE2E/select_where             28.09Mi ± 1%   27.99Mi ± 0%       ~ (p=0.490 n=20)
ParseE2E/join                     26.07Mi ± 1%   26.13Mi ± 1%       ~ (p=0.825 n=20)
ParseE2E/aggregate                22.70Mi ± 4%   22.64Mi ± 2%       ~ (p=0.920 n=20)
ParseE2E/subquery                 24.82Mi ± 1%   24.60Mi ± 1%       ~ (p=0.087 n=20)
ParseE2E/cte                      26.80Mi ± 1%   26.62Mi ± 1%       ~ (p=0.516 n=20)
ParseE2E/window                   30.85Mi ± 1%   30.97Mi ± 1%       ~ (p=0.311 n=20)
ParseE2E/complex                  30.05Mi ± 2%   30.06Mi ± 1%       ~ (p=0.560 n=20)
ParseE2E/insert                   33.94Mi ± 1%   33.96Mi ± 1%       ~ (p=0.952 n=20)
ParseE2E/update                   27.76Mi ± 1%   27.78Mi ± 0%       ~ (p=0.836 n=20)
ParseE2E/delete                   42.04Mi ± 2%   42.16Mi ± 1%       ~ (p=0.723 n=20)
ParseRW/postgresql/medium         23.37Mi ± 4%   23.25Mi ± 4%       ~ (p=0.920 n=20)
ParseRW/mysql/medium              31.28Mi ± 2%   30.73Mi ± 3%       ~ (p=0.090 n=20)
ParseRW/mariadb/medium            29.72Mi ± 1%   29.57Mi ± 1%       ~ (p=0.516 n=20)
ParseRW/sqlserver/medium          29.95Mi ± 0%   29.75Mi ± 1%       ~ (p=0.109 n=20)
ParseRW/oracle/medium             30.88Mi ± 0%   30.69Mi ± 1%       ~ (p=0.073 n=20)
ParseRW/sqlite/medium             30.15Mi ± 1%   29.98Mi ± 1%       ~ (p=0.351 n=20)
ParseRW/snowflake/medium          29.07Mi ± 1%   28.84Mi ± 1%       ~ (p=0.080 n=20)
ParseRW/clickhouse/medium         27.23Mi ± 1%   27.22Mi ± 2%       ~ (p=0.317 n=20)
ParseError/syntax_mid/parse       18.54Mi ± 1%   18.58Mi ± 1%       ~ (p=0.425 n=20)
ParseError/syntax_mid/validate    21.01Mi ± 1%   20.99Mi ± 1%       ~ (p=0.846 n=20)
ParseError/syntax_end/parse       13.10Mi ± 2%   13.06Mi ± 1%       ~ (p=0.774 n=20)
ParseError/syntax_end/validate    14.09Mi ± 3%   14.14Mi ± 3%       ~ (p=0.654 n=20)
ParseError/tokenize/parse         50.25Mi ± 2%   51.09Mi ± 2%       ~ (p=0.204 n=20)
ParseError/tokenize/validate      15.82Mi ± 1%   16.08Mi ± 2%       ~ (p=0.119 n=20)
ParseError/long_prefix/parse      37.64Mi ± 1%   37.65Mi ± 1%       ~ (p=0.909 n=20)
ParseError/long_prefix/validate   39.89Mi ± 1%   39.75Mi ± 1%       ~ (p=0.116 n=20)
geomean                           26.40Mi        26.37Mi       -0.14%

                                │      base      │                 head                  │
                                │      B/op      │     B/op      vs base                 │
ParseE2E/simple_select              816.0 ± 0%       816.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/select_where             1.523Ki ± 0%     1.523Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/join                     1.836Ki ± 0%     1.836Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/aggregate                1.812Ki ± 0%     1.812Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/subquery                 1.828Ki ± 0%     1.828Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/cte                      2.336Ki ± 0%     2.336Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/window                   1.281Ki ± 0%     1.281Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/complex                  5.025Ki ± 0%     5.025Ki ± 0%       ~ (p=0.695 n=20)
ParseE2E/insert                     824.0 ± 0%       824.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/update                     736.0 ± 0%       736.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/delete                     400.0 ± 0%       400.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/postgresql/medium         65.90Ki ± 0%     65.90Ki ± 0%       ~ (p=0.638 n=20)
ParseRW/mysql/medium              42.37Ki ± 0%     42.37Ki ± 0%       ~ (p=0.768 n=20)
ParseRW/mariadb/medium            60.60Ki ± 0%     60.60Ki ± 0%       ~ (p=0.091 n=20)
ParseRW/sqlserver/medium          83.67Ki ± 0%     83.67Ki ± 0%       ~ (p=0.274 n=20)
ParseRW/oracle/medium             104.8Ki ± 0%     104.8Ki ± 0%       ~ (p=0.877 n=20)
ParseRW/sqlite/medium             38.12Ki ± 0%     38.12Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/snowflake/medium          77.63Ki ± 0%     77.63Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/clickhouse/medium         34.82Ki ± 0%     34.82Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/simple_select            896.0 ± 0%       896.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/join                   1.914Ki ± 0%     1.914Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/complex                5.102Ki ± 0%     5.102Ki ± 0%       ~ (p=1.000 n=20) ¹
PoolGetPut/serial                   0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/syntax_mid/parse         680.0 ± 0%       680.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/syntax_mid/validate      680.0 ± 0%       680.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/syntax_end/parse       1.430Ki ± 0%     1.430Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseError/syntax_end/validate    1.430Ki ± 0%     1.430Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseError/tokenize/parse           272.0 ± 0%       272.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/tokenize/validate      1.758Ki ± 0%     1.758Ki ± 0%       ~ (p=1.000 n=20) ¹
ParseError/long_prefix/parse      22.75Ki ± 0%     22.75Ki ± 0%       ~ (p=0.161 n=20)
ParseError/long_prefix/validate   22.68Ki ± 0%     22.68Ki ± 0%       ~ (p=1.000 n=20) ¹
geomean                                        ²                 +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                │     base      │                 head                 │
                                │   allocs/op   │  allocs/op   vs base                 │
ParseE2E/simple_select             5.000 ± 0%      5.000 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/select_where              18.00 ± 0%      18.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/join                      17.00 ± 0%      17.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/aggregate                 18.00 ± 0%      18.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/subquery                  14.00 ± 0%      14.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/cte                       21.00 ± 0%      21.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/window                    11.00 ± 0%      11.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/complex                   49.00 ± 0%      49.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/insert                    17.00 ± 0%      17.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/update                    13.00 ± 0%      13.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseE2E/delete                    5.000 ± 0%      5.000 ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/postgresql/medium          646.0 ± 0%      646.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/mysql/medium               368.0 ± 0%      368.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/mariadb/medium             528.0 ± 0%      528.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/sqlserver/medium           927.0 ± 0%      927.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/oracle/medium             1.090k ± 0%     1.090k ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/sqlite/medium              347.0 ± 0%      347.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/snowflake/medium           776.0 ± 0%      776.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseRW/clickhouse/medium          383.0 ± 0%      383.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/simple_select           6.000 ± 0%      6.000 ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/join                    18.00 ± 0%      18.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseFresh/complex                 50.00 ± 0%      50.00 ± 0%       ~ (p=1.000 n=20) ¹
PoolGetPut/serial                  0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/syntax_mid/parse        12.00 ± 0%      12.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/syntax_mid/validate     12.00 ± 0%      12.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/syntax_end/parse        16.00 ± 0%      16.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/syntax_end/validate     16.00 ± 0%      16.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/tokenize/parse          4.000 ± 0%      4.000 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/tokenize/validate       21.00 ± 0%      21.00 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/long_prefix/parse       219.0 ± 0%      219.0 ± 0%       ~ (p=1.000 n=20) ¹
ParseError/long_prefix/validate    219.0 ± 0%      219.0 ± 0%       ~ (p=1.000 n=20) ¹
geomean                                       ²                +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                          │    base     │                head                │
                          │  sec/stmt   │  sec/stmt    vs base               │
ParseRW/postgresql/medium   12.80µ ± 3%   12.86µ ± 4%       ~ (p=0.925 n=20)
ParseRW/mysql/medium        10.82µ ± 2%   11.01µ ± 3%       ~ (p=0.091 n=20)
ParseRW/mariadb/medium      13.26µ ± 1%   13.32µ ± 1%       ~ (p=0.512 n=20)
ParseRW/sqlserver/medium    10.58µ ± 0%   10.65µ ± 1%       ~ (p=0.101 n=20)
ParseRW/oracle/medium       11.31µ ± 0%   11.38µ ± 1%       ~ (p=0.072 n=20)
ParseRW/sqlite/medium       11.42µ ± 1%   11.48µ ± 1%       ~ (p=0.337 n=20)
ParseRW/snowflake/medium    13.45µ ± 1%   13.56µ ± 1%       ~ (p=0.075 n=20)
ParseRW/clickhouse/medium   11.82µ ± 1%   11.83µ ± 2%       ~ (p=0.324 n=20)
geomean                     11.89µ        11.97µ       +0.69%

pkg: github.com/ajitpratap0/GoSQLX/pkg/linter
                   │     base     │                head                 │
                   │    sec/op    │    sec/op     vs base               │
LintParallel/rules   20.51m ±  2%   20.79m ±  2%       ~ (p=0.414 n=20)
LintParallel/lint    40.08m ±  1%   40.37m ±  2%       ~ (p=0.698 n=20)
LintRW/postgresql    5.393m ±  1%   5.423m ±  1%       ~ (p=0.429 n=20)
LintRW/mysql         5.606m ±  1%   5.612m ±  1%       ~ (p=0.968 n=20)
LintRW/mariadb       5.046m ±  1%   5.025m ±  2%       ~ (p=0.289 n=20)
LintRW/sqlserver     4.778m ±  1%   4.809m ±  1%       ~ (p=0.253 n=20)
LintRW/oracle        5.667m ±  2%   5.658m ±  1%       ~ (p=0.989 n=20)
LintRW/sqlite        4.694m ±  1%   4.657m ±  1%       ~ (p=0.277 n=20)
LintRW/snowflake     4.819m ±  1%   4.818m ±  1%       ~ (p=0.779 n=20)
LintRW/clickhouse    5.380m ±  2%   5.426m ±  1%       ~ (p=0.265 n=20)
LintRule/L007        2.826m ±  1%   2.817m ±  2%       ~ (p=0.698 n=20)
LintRule/L010        974.0µ ± 16%   972.6µ ± 16%       ~ (p=0.989 n=20)
geomean              5.684m         5.695m        +0.20%

                  │     base     │                head                 │
                  │     B/s      │     B/s       vs base               │
LintRW/postgresql   5.255Mi ± 1%   5.221Mi ± 1%       ~ (p=0.409 n=20)
LintRW/mysql        5.836Mi ± 1%   5.832Mi ± 1%       ~ (p=1.000 n=20)
LintRW/mariadb      6.046Mi ± 1%   6.070Mi ± 2%       ~ (p=0.267 n=20)
LintRW/sqlserver    6.485Mi ± 1%   6.442Mi ± 1%       ~ (p=0.285 n=20)
LintRW/oracle       7.010Mi ± 2%   7.019Mi ± 1%       ~ (p=0.995 n=20)
LintRW/sqlite       6.113Mi ± 1%   6.161Mi ± 1%       ~ (p=0.256 n=20)
LintRW/snowflake    6.475Mi ± 1%   6.475Mi ± 1%       ~ (p=0.783 n=20)
LintRW/clickhouse   6.032Mi ± 2%   5.980Mi ± 1%       ~ (p=0.261 n=20)
geomean             6.137Mi        6.130Mi       -0.12%

                   │      base      │                 head                  │
                   │      B/op      │     B/op      vs base                 │
LintParallel/rules   5.559Mi ± 0%     5.559Mi ± 0%       ~ (p=0.888 n=20)
LintParallel/lint    25.53Mi ± 0%     25.53Mi ± 0%       ~ (p=0.984 n=20)
LintRW/postgresql    2.801Mi ± 0%     2.801Mi ± 0%       ~ (p=0.963 n=20)
LintRW/mysql         3.537Mi ± 0%     3.537Mi ± 0%       ~ (p=0.147 n=20)
LintRW/mariadb       3.252Mi ± 0%     3.252Mi ± 0%       ~ (p=0.394 n=20)
LintRW/sqlserver     2.838Mi ± 0%     2.838Mi ± 0%       ~ (p=0.100 n=20)
LintRW/oracle        3.477Mi ± 0%     3.477Mi ± 0%       ~ (p=0.560 n=20)
LintRW/sqlite        3.066Mi ± 0%     3.066Mi ± 0%       ~ (p=0.417 n=20)
LintRW/snowflake     3.083Mi ± 0%     3.083Mi ± 0%       ~ (p=0.176 n=20)
LintRW/clickhouse    3.474Mi ± 0%     3.474Mi ± 0%       ~ (p=0.878 n=20)
LintRule/L007        92.98Ki ± 0%     92.98Ki ± 0%       ~ (p=1.000 n=20) ¹
LintRule/L010          0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=20) ¹
geomean                           ²                 +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                   │     base      │                 head                 │
                   │   allocs/op   │  allocs/op   vs base                 │
LintParallel/rules   68.93k ± 0%     68.93k ± 0%       ~ (p=0.585 n=20)
LintParallel/lint    147.5k ± 0%     147.5k ± 0%       ~ (p=0.559 n=20)
LintRW/postgresql    16.34k ± 0%     16.34k ± 0%       ~ (p=0.342 n=20)
LintRW/mysql         20.35k ± 0%     20.35k ± 0%       ~ (p=0.751 n=20)
LintRW/mariadb       18.48k ± 0%     18.48k ± 0%       ~ (p=1.000 n=20)
LintRW/sqlserver     15.94k ± 0%     15.94k ± 0%       ~ (p=0.176 n=20)
LintRW/oracle        19.67k ± 0%     19.67k ± 0%       ~ (p=1.000 n=20)
LintRW/sqlite        18.30k ± 0%     18.30k ± 0%       ~ (p=0.320 n=20)
LintRW/snowflake     17.77k ± 0%     17.77k ± 0%       ~ (p=1.000 n=20) ¹
LintRW/clickhouse    20.64k ± 0%     20.64k ± 0%       ~ (p=1.000 n=20)
LintRule/L007        1.194k ± 0%     1.194k ± 0%       ~ (p=1.000 n=20) ¹
LintRule/L010         0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=20) ¹
geomean                          ²                -0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                  │     base     │                head                 │
                  │   sec/stmt   │   sec/stmt    vs base               │
LintRW/postgresql   17.68µ ±  1%   17.78µ ±  1%       ~ (p=0.425 n=20)
LintRW/mysql        13.54µ ±  1%   13.56µ ±  1%       ~ (p=0.962 n=20)
LintRW/mariadb      13.28µ ±  1%   13.22µ ±  2%       ~ (p=0.286 n=20)
LintRW/sqlserver    17.25µ ±  1%   17.36µ ±  1%       ~ (p=0.253 n=20)
LintRW/oracle       15.40µ ±  2%   15.38µ ±  1%       ~ (p=0.989 n=20)
LintRW/sqlite       13.49µ ±  1%   13.38µ ±  1%       ~ (p=0.274 n=20)
LintRW/snowflake    14.74µ ±  1%   14.73µ ±  1%       ~ (p=0.784 n=20)
LintRW/clickhouse   13.65µ ±  2%   13.77µ ±  1%       ~ (p=0.265 n=20)
LintRule/L007       1.005µ ±  1%   1.001µ ±  2%       ~ (p=0.683 n=20)
LintRule/L010       346.2n ± 16%   345.8n ± 16%       ~ (p=0.963 n=20)
geomean             7.766µ         7.767µ        +0.02%

pkg: github.com/ajitpratap0/GoSQLX/pkg/sql/tokenizer
                         │    base     │                head                │
                         │   sec/op    │   sec/op     vs base               │
Tokenize/scratch/join      1.397µ ± 1%   1.392µ ± 1%       ~ (p=0.984 n=20)
Tokenize/scratch/complex   3.675µ ± 1%   3.691µ ± 1%       ~ (p=0.763 n=20)
Tokenize/scratch/insert    694.6n ± 2%   701.8n ± 2%       ~ (p=0.664 n=20)
geomean                    1.528µ        1.533µ       +0.37%

                         │     base     │                head                 │
                         │     B/s      │     B/s       vs base               │
Tokenize/scratch/join      86.03Mi ± 1%   86.36Mi ± 1%       ~ (p=1.000 n=20)
Tokenize/scratch/complex   98.11Mi ± 1%   97.68Mi ± 1%       ~ (p=0.753 n=20)
Tokenize/scratch/insert    104.3Mi ± 2%   103.3Mi ± 2%       ~ (p=0.678 n=20)
geomean                    95.85Mi        95.51Mi       -0.36%

                         │     base     │                head                 │
                         │     B/op     │    B/op     vs base                 │
Tokenize/scratch/join      0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
Tokenize/scratch/complex   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
Tokenize/scratch/insert    0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
geomean                               ²               +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                         │     base     │                head                 │
                         │  allocs/op   │ allocs/op   vs base                 │
Tokenize/scratch/join      0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
Tokenize/scratch/complex   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
Tokenize/scratch/insert    0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=20) ¹
geomean                               ²               +0.00%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/ajitpratap0/GoSQLX/pkg/transform
                             │    base     │                head                │
                             │   sec/op    │   sec/op     vs base               │
TransformParse/simple_select   1.637µ ± 1%   1.637µ ± 1%       ~ (p=0.888 n=20)
TransformParse/join            5.083µ ± 1%   5.089µ ± 0%       ~ (p=0.941 n=20)
TransformParse/complex         13.26µ ± 1%   13.30µ ± 1%       ~ (p=0.560 n=20)
geomean                        4.797µ        4.802µ       +0.11%

                             │     base     │                 head                  │
                             │     B/op     │     B/op      vs base                 │
TransformParse/simple_select     816.0 ± 0%     816.0 ± 0%       ~ (p=1.000 n=20) ¹
TransformParse/join            1.836Ki ± 0%   1.836Ki ± 0%       ~ (p=1.000 n=20) ¹
TransformParse/complex         5.023Ki ± 0%   5.023Ki ± 0%       ~ (p=1.000 n=20) ¹
geomean                        1.944Ki        1.944Ki       +0.00%
¹ all samples are equal

                             │    base    │                head                 │
                             │ allocs/op  │ allocs/op   vs base                 │
TransformParse/simple_select   5.000 ± 0%   5.000 ± 0%       ~ (p=1.000 n=20) ¹
TransformParse/join            17.00 ± 0%   17.00 ± 0%       ~ (p=1.000 n=20) ¹
TransformParse/complex         49.00 ± 0%   49.00 ± 0%       ~ (p=1.000 n=20) ¹
geomean                        16.09        16.09       +0.00%
¹ all samples are equal

Run, raw data and benchstat

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Claude finished @ajitpratap0's task in 18s —— View job


Review of PR #945 (ASCII fast path for identifier quoting)

I read the diff against origin/main. I did not run tests or benchmarks. This review is from reading the code only.

Verdict: Looks correct. I found no blocking issues.

identifierNeedsQuoting (pkg/sql/ast/sql.go)

  • The byte-class state machine matches the rune loop for ASCII input.
    • Word bytes, including *, in a digit-led part return true.
    • Digits keep a part bare, which keeps ClickHouse d.1 working.
    • $ is rejected at the start of a part or inside a digit-led part, and allowed elsewhere.
    • Any other ASCII byte returns true.
  • The first byte ≥ 0x80 hands the whole name to identifierNeedsQuotingRunes. That restarts from the beginning, so no state carries over. Invalid UTF-8 also takes this path, which is the conservative choice.

isReservedName (pkg/sql/ast/quote_names.go)

  • Building the index at init from reservedNameWords keeps a single word list.
  • A bad word panics at init, so a typo in the list fails loudly.
  • Bucket indexing is safe. len(name) is capped at 16, so [maxReservedNameLen+1] is large enough.
  • The [16]byte compare works because up starts zeroed, which pads the unused bytes.
  • uint16 and uint8 are wide enough for the current list size.
  • The earlier nits about doc comments and a named bucket type are already addressed (reservedNameBucket).

Remaining nits (non-blocking)

  1. CHANGELOG.md still says "measured numbers await the CI verdict". Replace it with the real perf-gate numbers once they are in, per the repo's "measure, don't assume" rule.
  2. The ticket tag [W5-04] is missing from that entry. Neighbouring entries put it at the end.

Tests

  • Coverage is strong:
    • frozen reference copies,
    • exhaustive short-input equivalence,
    • a fuzz test,
    • zero-allocation pins,
    • mutation proofs.
  • The byte-identical corpus dump (5,422 renderings) gives good confidence that behaviour is unchanged.

Performance and security

  • The hot path has no allocations and no map hashing.
  • There is no new injection surface. Names that need quoting still go through the same quoting path, and the fast path returns "bare" only for the same inputs as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Claude finished @ajitpratap0's task in 15s —— View job


Review of PR #945 (ASCII fast path for identifier quoting)

I read the diff against origin/main. I did not run tests or benchmarks, so this is a review of the code only.

Verdict: Looks correct. I found no blocking issues.

identifierNeedsQuoting (pkg/sql/ast/sql.go)

  • The byte-class state machine matches the documented rules:
    • A word byte (letter, _ or *) in a digit-led part returns true.
    • Digits keep a part bare, which keeps ClickHouse d.1 working.
    • . resets the part state.
    • $ is rejected at the start of a part or inside a digit-led part, and allowed elsewhere.
    • Any other ASCII byte returns true.
  • The first byte ≥ 0x80 hands the whole name to identifierNeedsQuotingRunes. That restarts from the beginning, so no state carries over. Invalid UTF-8 also goes down the rune path.
  • The empty-name check appears in both functions. That is harmless.

isReservedName (pkg/sql/ast/quote_names.go)

  • The index is built at init from reservedNameWords, so the word list stays the single source of truth.
  • A bad word panics at init, so a typo in the list fails loudly.
  • Bucket indexing is safe: len(name) is capped at maxReservedNameLen, and the array has maxReservedNameLen+1 slots.
  • up is zero-initialised, so the [16]byte compare holds for names shorter than 16 bytes.
  • uint16 and uint8 are wide enough for the current list. The init-time check does not guard against overflow if the list ever grows past 255 words per bucket. That is not a realistic risk.
  • The earlier nit about doc comments and a named bucket type is addressed (reservedNameBucket).

Nits (non-blocking)

  1. The reservedNameEntries, reservedNameIndex declaration is a long single-line signature. Naming the index type (for example type reservedNameIndexTable [26][maxReservedNameLen + 1]reservedNameBucket) would read better.
  2. The [W5-04] tag sits at the end of the CHANGELOG entry, which matches the neighbouring entries. The "await CI verdict" wording is gone and real numbers are in.
  3. The CHANGELOG entry is honest that BenchmarkRender is outside the gated suite. Consider adding it to the perf-gate suite in a follow-up, so the PLAN targets can be confirmed on amd64. The CI gate here only shows no regression: Normalize/* is about 2–3% faster, and allocs/op and B/op are unchanged.

Tests

  • Coverage is strong:
    • frozen reference copies,
    • exhaustive short-input equivalence,
    • a fuzz test,
    • zero-allocation pins,
    • mutation proofs,
    • a byte-identical corpus dump (5,422 renderings).

Performance and security

  • The hot path has no allocations and no map hashing.
  • The fast path returns "bare" only for the same inputs as before, so there is no new injection surface.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Claude finished @ajitpratap0's task in 14s —— View job


Review of PR #945 (ASCII fast path for identifier quoting)

I read the diff against origin/main (sql.go, quote_names.go, CHANGELOG.md). I did not run tests or benchmarks.

Verdict: Looks correct. I found no blocking issues.

identifierNeedsQuoting (pkg/sql/ast/sql.go)

  • The byte-class state machine matches the documented rules:
    • A word byte (letter, _ or *) in a digit-led part returns true.
    • Digits keep a part bare, so ClickHouse d.1 still works.
    • . resets the part state.
    • $ is rejected at the start of a part or in a digit-led part, and allowed elsewhere.
    • Any other ASCII byte returns true.
  • The first byte ≥ 0x80 hands the whole name to identifierNeedsQuotingRunes. It restarts from the beginning, so no state carries over. Invalid UTF-8 takes the same path.

isReservedName (pkg/sql/ast/quote_names.go)

  • The index is built at init from reservedNameWords, so the word list stays the single source of truth.
  • A bad word panics at init, so a typo in the list fails loudly.
  • Indexing is safe. len(name) is capped at maxReservedNameLen, and first is checked to be a–z before it is used as an index.
  • up is zero-initialised, so the [16]byte compare holds for names shorter than 16 bytes.

Nits (non-blocking)

  1. The uint16 bucket start and uint8 count are not checked at init. Overflow is not realistic with the current list. A one-line panic guard would make a future list growth fail loudly.
  2. The reservedNameEntries, reservedNameIndex declaration has a long one-line signature. A named type for the index table would read better.
  3. The empty-name check appears in both identifierNeedsQuoting and the rune fallback. This is harmless.

CHANGELOG

  • The entry gives amd64 numbers for BenchmarkRender, so the PLAN targets are backed by data. It also states that run 41706707217 had no A/A' control. The PR body still says the gated suite has no BenchmarkRender, so consider updating it to match.
  • The PR gate shows no regression. Normalize/* is 2–5% faster, and allocs/op and B/op are unchanged.

Tests

  • Coverage is strong:
    • frozen reference copies,
    • exhaustive short-input equivalence,
    • a fuzz test,
    • zero-allocation pins,
    • mutation proofs,
    • a byte-identical corpus dump.

Performance and security

  • The hot path has no allocations and no map hashing.
  • The fast path returns "bare" only for the same inputs as before, so there is no new injection surface.

This branch was successfully deployed

1 active deployment
Preview — 8c8a9f88 Deployed Nov 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ast AST node changes documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant