Skip to content

perf(linter): allocation-free L007 keyword-case scan [W6-03] - #896

Merged
ajitpratap0 merged 10 commits into
mainfrom
perf/w6-03-l007-alloc-free
Nov 3, 2026
Merged

ajitpratap0 merged 10 commits into
mainfrom
perf/w6-03-l007-alloc-free

Conversation

@ajitpratap0

@ajitpratap0 ajitpratap0 commented Oct 31, 2026 •

Copy link
Copy Markdown
Owner

Summary

Wave-1 task W6-03 from the 2026-11 performance audit (LINT-2). The prototype was confirmed by an independent verifier; Codex ported it, and Opus reviewed the result and ran the full gate.

L007 (keyword case consistency) built a []wordToken per line, used a strings.Builder per word, and called strings.ToUpper on every word before the keyword lookup. Now:

  • scanWords passes each word to the rule as a substring of the line, with its 1-based byte column. It allocates nothing.
  • isSQLKeyword upper-cases ASCII words into a 16-byte stack buffer before the map lookup.
    • Words with non-ASCII bytes keep the strings.ToUpper path, so Unicode case folding (e.g. ſ → S) behaves as before.
    • TestKeywordLengthBound fails if any keyword ever exceeds the buffer.
  • Violation messages, positions, suggestions and Fix output are unchanged.

Merged origin/main (wave 0: goldens, benchmark matrix, perf gate in shadow mode) into the branch without conflicts. The W0-02 goldens pass (task check); no golden changed. The private benchmark file was removed; measurements use the canonical W0-06 benchmarks on main (./pkg/linter ^BenchmarkLintRule$/^L007$, ^BenchmarkLintRW$).

Test Plan

Differential test (TestKeywordCaseDifferential). It compares against the previous implementation, kept in keyword_case_oracle_test.go as the oracle, and requires identical violations: rule, line, column, message and suggestion. Inputs are every statement and every whole file in testdata/realworld/* and testdata/corpus/* (3,002 statements), plus mixed case, Unicode identifiers, quoted identifiers, comments, string literals and invalid UTF-8.

Allocation tests. TestScanWordsNoAllocations checks that scanWords allocates nothing. TestKeywordCaseCleanASCIIHasNoAllocations checks that an ASCII line with no violations makes zero allocations.

Gate, run outside the Codex sandbox:

Check Result
go run honnef.co/go/tools/cmd/staticcheck@latest ./pkg/linter/... ✅
go test -race -count=1 ./pkg/linter/... ✅
task check ✅
task test:cbinding ✅
go test -count=1 ./pkg/compatibility/... ✅

Local measurement, canonical benchmarks (Apple M4, shared machine, indicative only): BenchmarkLintRule/L007 72565 -> 1194 allocs/op (-98.4%), 3085041 -> 95208 B/op (-96.9%); time -55% to -83% across 4 interleaved -cpu 1 rounds (noisy). BenchmarkLintRW allocs/op: postgresql 60916 -> 53220 (-12.6%), mysql 73216 -> 64136 (-12.4%). Plan targets (L007 <= -45% time, end-to-end allocs <= -10%) met locally.

CI perf gate, layout-randomized (amd64 Xeon 6975P-C, head 9659ccb merged into base 6399dc0; 20 randomized layouts per arm, per-layout Mann-Whitney U, Benjamini-Hochberg, A/A' control): PASS. 11 benchmarks faster by 3% or more, no gated time regression, allocs/op not up, B/op not up. A/A' control clean (0 of 52 moved >3%, suite geomean +0.05%, p 0.862).

benchmark time p A/A' spread B/op allocs/op status
LintRule/L007 (target) -66.40% <0.001 -0.19% 0.63% -96.91% 72565 -> 1194 faster, -allocs
LintRule/L010 +0.22% 0.369 +0.08% 1.04% +0.00% 0 -> 0 ok
suite geomean -4.25% <0.001 +0.05%

The earlier LintRule/L010 +11.9% flag did not reproduce under the layout-randomized gate (L010 is untouched by this PR; it was a code-layout artifact). Low-power section lists 2 benchmarks (see the CI comment); neither is L007 or L010.

Breaking Changes

None. The exported API and the rule's behaviour are unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2

Review follow-up (fixer)

  • Added differential edge cases for a keyword directly followed by a quote (select'x'from"t"where"a"='b', SELECT"col"FROM t, select`a`from`b` ); the previously surviving mutation (dropping the emit before an opening quote) now fails the suite.
  • Documented the unreachable >16-byte non-ASCII slow path in isSQLKeyword as defensive.
  • AC-STD re-run at the new head: task check, task test:cbinding, go test -count=1 ./pkg/compatibility/..., staticcheck on ./pkg/linter/... all pass.
  • Deleted keyword_case_bench_test.go (duplicated main's canonical benchmarks). Still outstanding (orchestrator action): authoritative amd64 A/B of BenchmarkLintRule/L007 and BenchmarkLintRW (dispatch Perf Gate with a scratch head adding those two lines to gate-suite.txt); paste the table here. Do not merge before this verdict is pasted here and the pending Race Detector / Test (ubuntu) checks are green.
  • Benchmark placement (pkg/linter/rules/keywords vs PLAN 2.2 linter package) to be reconciled when the W0 lint matrix lands.

L007 (keyword case consistency) built a []wordToken per line with a
strings.Builder per word and upper-cased every word with strings.ToUpper
before the keyword map lookup. It now scans words as substrings of the line
(scanWords) and upper-cases ASCII words into a 16-byte stack buffer for the
lookup; words with non-ASCII bytes keep the strings.ToUpper path so Unicode
case folding is unchanged. Violations, positions and fixes are identical.

Tests: a differential test against the previous implementation (kept in a
test file as the oracle) over every statement and file in the real-world
corpora plus Unicode, quoting, comment and invalid UTF-8 cases; allocation
tests; a guard that every keyword fits the stack buffer.

M4, GOGC=off -cpu 1, 10 interleaved rounds: L007 -48.8% to -53.5% per
dialect (geomean -51.5%), allocs -88% to -94%; LintWithDialect allocs
-12.1% to -15.4%, B/op -7% to -9%.

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

vercel Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated
gosqlx-website Ignored Ignored Preview Nov 3, 2026 6:09pm UTC

@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

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


Review of PR #896: allocation-free L007 keyword-case scan

I read the diff of keyword_case.go. I could not run the tests here because command approval was denied. My comments are from reading the code, and the results in the PR description are not re-verified.

  • Read the diff
  • Post review

Overall: This looks correct and well scoped. The oracle-based differential test over 3,002 statements plus edge cases is a strong safety net for a refactor like this.

What I checked

  • scanWords: It emits line[wordStart:i] where the old code rebuilt the word with WriteRune. The two are equivalent for valid UTF-8. For invalid UTF-8, range yields U+FFFD with width 1. That rune isn't a letter or digit, so it ends a word. It never ends up inside an emitted word, so the old and new text agree.
  • isSQLKeyword: The fast path upper-cases ASCII into a [16]byte stack buffer. The sqlKeywords[string(buf[:n])] lookup is the pattern the compiler optimizes into a no-allocation map lookup. Words over 16 bytes can only match if they are non-ASCII, so returning false for all-ASCII long words is sound as long as every keyword is ≤16 bytes. TestKeywordLengthBound guards that.
  • Behavior parity: The Check messages, columns (1-based byte offsets) and suggestions are unchanged.

Suggestions (non-blocking)

  1. Slow-path cost: A non-ASCII word still calls strings.ToUpper, which allocates if the word changes. That is fine because it is rare, but the comment could say so.
  2. Redundant case conversion: On a violation, strings.ToUpper and strings.ToLower are each called once more. That only happens on actual violations, so it is acceptable. You could skip the text != expected comparison and use strings.EqualFold as a cheap pre-check, but the gain is marginal.
  3. Closure capture: scanWords takes a closure that captures violations, r and lineNum. This is fine only because the closure doesn't escape. Escape analysis should keep it on the stack, and TestScanWordsNoAllocations covers it. If someone later stores the closure, that test will catch it. A short comment noting this would help.
  4. Keyword bound: maxKeywordLen = 16 is coupled to the sqlKeywords table only through the test. Consider a note next to the table, since adding a keyword such as CURRENT_TIMESTAMP (17 chars) would fail the test. The test failure message should say to raise the constant.
  5. Doc comment: The scanWords comment says "letters, digits and underscores, not starting with a digit". I couldn't confirm the "not starting with a digit" rule is enforced in the elided part of the loop. Please check the comment matches the code.

Performance

The reported −50% on L007 and −12–15% end-to-end allocs are plausible for removing the per-line slice, per-word builder and ToUpper. The amd64 confirmation from the perf gate is still outstanding, as the PR notes.

Security

No concerns. There is no unsafe code and no unbounded buffers. The stack buffer is length-checked before indexing.

Tests

Coverage is good: differential, allocation, bound and Unicode cases. It would be worth adding a fuzz test (FuzzKeywordCaseDifferential) against the oracle for arbitrary byte strings, since it is cheap given the oracle already exists.

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

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


Review of PR #896: allocation-free L007 keyword-case scan

I read the full diff of keyword_case.go. I could not run the tests in this environment because command approval was denied, so the results in the PR description are not re-verified here.

Verdict: This looks correct and well scoped. I found no blocking issues.

Correctness

  • scanWords: Slicing line[wordStart:i] matches what the old WriteRune builder produced. For invalid UTF-8, range yields U+FFFD with width 1. That rune is not a letter or digit, so it ends a word and never lands inside an emitted word.
  • Digit rule: The doc comment says "not starting with a digit". The loop enforces this with (wordStart >= 0 && unicode.IsDigit(ch)), so the comment is accurate. This resolves the open question from the earlier review.
  • isSQLKeyword: The ASCII fast path upper-cases into a [16]byte stack buffer. The sqlKeywords[string(buf[:n])] lookup is compiler-optimized to avoid allocating. Non-ASCII words go through strings.ToUpper, so Unicode folding such as ſ → S is preserved.
  • Long ASCII words: Returning false for an all-ASCII word over 16 bytes is only sound while every keyword is ≤16 bytes. TestKeywordLengthBound guards this.
  • Output parity: Messages, 1-based byte columns and suggestions are unchanged.

Suggestions (non-blocking)

  1. Comment the closure contract: scanWords relies on emit not escaping so the closure stays on the stack. TestScanWordsNoAllocations would catch a regression, but a one-line comment would help.
  2. Keyword bound: maxKeywordLen is tied to the sqlKeywords table only through the test. Add a note next to the table. Make sure the test failure message says to raise the constant, since a keyword like CURRENT_TIMESTAMP (17 chars) would trip it.
  3. Slow path: A short comment that non-ASCII words are rare would explain why the allocating path is acceptable.
  4. Fuzz test: Since the oracle already exists, a FuzzKeywordCaseDifferential over arbitrary byte strings would be cheap and would add coverage beyond the corpus.
  5. Violation path: strings.ToUpper and strings.ToLower still allocate when the word changes. This only happens on actual violations, so it is fine for now.

Performance

Removing the per-line slice, the per-word builder and the ToUpper on every word accounts for the reported −50% on L007 and −12–15% end-to-end allocations. The amd64 confirmation from the W0-09 perf gate is still outstanding, as the PR notes.

Security

No concerns. There is no unsafe code, and the stack buffer is length-checked before indexing.

Tests

Coverage is strong. It includes a differential test against the old implementation over 3,002 statements plus edge cases, allocation tests, and the keyword length-bound check.

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

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

Base a72ec74daf9a, head a71d8d64b303. 20 interleaved rounds, 20 randomized code layouts per arm (-randlayout seeds 416910951412..416910951431; A/A' 416910951432..416910951451), benchtime 100ms, -cpu 1, GOMEMLIMIT 64MiB, pinned to CPU 3 (SMT siblings 2-3) with taskset.

Runner CPU: AMD EPYC 7763 64-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.

  • 11 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.19% 0.705 +0.37% 1.37% +0.00% 5 → 5 ok
ParseE2E/select_where -0.23% 0.607 -0.01% 1.31% +0.00% 18 → 18 ok
ParseE2E/join -0.15% 0.695 -0.24% 0.87% +0.00% 17 → 17 ok
ParseE2E/aggregate +0.41% 0.636 -0.20% 1.97% +0.00% 18 → 18 ok
ParseE2E/subquery +0.78% 0.148 +1.13% 1.08% +0.00% 14 → 14 ok
ParseE2E/cte -0.31% 0.725 -0.37% 1.28% +0.00% 21 → 21 ok
ParseE2E/window -0.45% 0.365 +0.12% 1.22% +0.00% 11 → 11 ok
ParseE2E/complex -0.51% 0.409 -0.90% 2.57% +0.00% 49 → 49 ok
ParseE2E/insert -0.89% 0.133 -0.65% 0.92% +0.00% 17 → 17 ok
ParseE2E/update +0.10% 0.839 -0.16% 0.96% +0.00% 13 → 13 ok
ParseE2E/delete +0.24% 0.735 +0.72% 1.08% +0.00% 5 → 5 ok
ParseRW/postgresql/medium -0.32% 0.698 -0.83% 1.10% +0.00% 646 → 646 ok
ParseRW/mysql/medium -0.23% 0.547 -0.42% 0.94% +0.00% 368 → 368 ok
ParseRW/mariadb/medium -0.04% 0.925 -0.26% 1.24% -0.00% 528 → 528 ok
ParseRW/sqlserver/medium +0.17% 0.799 -0.08% 1.44% +0.00% 927 → 927 ok
ParseRW/oracle/medium -0.37% 0.369 -0.74% 1.00% +0.00% 1090 → 1090 ok
ParseRW/sqlite/medium +0.45% 0.547 +0.89% 1.91% +0.00% 347 → 347 ok
ParseRW/snowflake/medium -0.50% 0.114 -0.48% 1.16% +0.00% 776 → 776 ok
ParseRW/clickhouse/medium +0.32% 0.718 +1.46% 4.41% +0.00% 383 → 383 ok
ParseFresh/simple_select +0.14% 0.766 +0.08% 0.93% +0.00% 6 → 6 ok
ParseFresh/join +0.05% 0.882 -0.05% 0.47% +0.00% 18 → 18 ok
ParseFresh/complex -0.56% 0.211 -0.02% 1.32% +0.00% 50 → 50 ok
PoolGetPut/serial -0.07% 0.766 -0.25% 1.13% +0.00% 0 → 0 ok
Tokenize/scratch/join -0.13% 0.808 -0.59% 1.80% +0.00% 0 → 0 ok
Tokenize/scratch/complex -0.40% 0.579 -1.27% 1.52% +0.00% 0 → 0 ok
Tokenize/scratch/insert -0.12% 0.882 -1.28% 3.59% +0.00% 0 → 0 ok
Normalize/simple_select +0.34% 0.365 +0.32% 0.61% +0.00% 7 → 7 ok
Normalize/join +0.20% 0.561 -0.03% 1.20% +0.00% 23 → 23 ok
Normalize/complex -0.13% 0.797 -0.15% 1.68% +0.00% 68 → 68 ok
TransformParse/simple_select +0.20% 0.449 +0.72% 0.78% +0.00% 5 → 5 ok
TransformParse/join +0.09% 0.829 +0.06% 1.32% +0.00% 17 → 17 ok
TransformParse/complex +0.92% 0.208 +0.26% 1.77% +0.00% 49 → 49 ok
LintParallel/rules -18.50% <0.001 +0.14% 0.61% -26.01% 229686.5 → 158313 faster, -allocs
LintParallel/lint -6.98% <0.001 +0.78% 1.03% -9.22% 308264 → 236892.5 faster, -allocs
LintRW/postgresql -9.69% <0.001 -0.33% 2.21% -9.20% 34189.5 → 26500 faster, -allocs
LintRW/mysql -9.46% <0.001 +0.03% 1.64% -8.58% 41490 → 32413 faster, -allocs
LintRW/mariadb -9.58% <0.001 -0.41% 0.84% -9.03% 38235 → 29610 faster, -allocs
LintRW/sqlserver -10.24% <0.001 -0.32% 0.79% -10.45% 34620 → 25622 faster, -allocs
LintRW/oracle -10.75% <0.001 -0.13% 1.97% -10.00% 42243 → 31614 faster, -allocs
LintRW/sqlite -9.51% <0.001 +0.29% 2.46% -8.44% 37576 → 29427 faster, -allocs
LintRW/snowflake -9.52% <0.001 -0.83% 1.68% -9.63% 37559 → 28806 faster, -allocs
LintRW/clickhouse -9.93% <0.001 -0.13% 2.02% -8.55% 42343 → 32891 faster, -allocs
LintRule/L007 -65.63% <0.001 -0.50% 3.09% -96.91% 72565 → 1194 faster, -allocs
LintRule/L010 -0.23% 0.495 +0.04% 0.44% +0.00% 0 → 0 ok
ParseError/syntax_mid/parse +0.25% 0.386 -0.33% 0.64% +0.00% 12 → 12 ok
ParseError/syntax_mid/validate +0.28% 0.534 -0.32% 1.19% +0.00% 12 → 12 ok
ParseError/syntax_end/parse +1.44% 0.148 +0.16% 4.83% +0.00% 16 → 16 ok
ParseError/syntax_end/validate -0.88% 0.365 -0.32% 1.99% +0.00% 16 → 16 ok
ParseError/tokenize/parse +0.93% 0.289 -0.58% 2.09% +0.00% 4 → 4 ok
ParseError/tokenize/validate -0.21% 0.626 -0.21% 1.38% +0.00% 21 → 21 ok
ParseError/long_prefix/parse -1.44% 0.079 -1.31% 2.68% -0.01% 219 → 219 ok
ParseError/long_prefix/validate -1.75% 0.108 -0.90% 3.87% +0.00% 219 → 219 ok
geomean -4.14% <0.001 -0.16%

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.16%, p 0.640.

Low power

4 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
ParseError/syntax_end/parse 4.83% 27% +7.4%
ParseRW/clickhouse/medium 4.41% 38% +6.7%
ParseError/long_prefix/validate 3.87% 57% +5.9%
Tokenize/scratch/insert 3.59% 68% +5.5%

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.03% ok
e2e/complex -0.04% ok
e2e/cte -0.02% ok
e2e/delete +0.16% ok
e2e/insert +0.00% ok
e2e/join -0.04% ok
e2e/select_where -0.05% ok
e2e/simple_select +0.11% ok
e2e/subquery +0.01% ok
e2e/update +0.00% ok
e2e/window +0.08% ok
rw/postgresql/medium +0.06% ok
Secondary metric: default GC, GOMAXPROCS=1 (informational)
benchmark time p
ParseE2E/simple_select -1.50% 0.096
ParseE2E/select_where -0.95% 0.257
ParseE2E/join -2.43% 0.089
ParseE2E/aggregate -1.07% 0.218
ParseE2E/subquery -2.54% 0.105
ParseE2E/cte -1.22% 0.165
ParseE2E/window -1.62% 0.105
ParseE2E/complex -0.15% 0.684
ParseE2E/insert -0.84% 0.393
ParseE2E/update -0.81% 0.307
ParseE2E/delete +0.66% 0.545
ParseRW/postgresql/medium -1.08% 0.165
ParseRW/mysql/medium -1.16% 0.190
ParseRW/mariadb/medium -0.73% 0.684
ParseRW/sqlserver/medium -0.40% 0.631
ParseRW/oracle/medium -1.27% 0.165
ParseRW/sqlite/medium -1.56% 0.043
ParseRW/snowflake/medium -1.15% 0.075
ParseRW/clickhouse/medium -1.12% 0.315
ParseFresh/simple_select +0.24% 0.684
ParseFresh/join +0.06% 0.971
ParseFresh/complex -0.98% 0.241
PoolGetPut/serial -1.06% 0.029
Tokenize/scratch/join +0.07% 0.910
Tokenize/scratch/complex -0.42% 0.623
Tokenize/scratch/insert -0.63% 0.734
Normalize/simple_select +0.47% 0.570
Normalize/join -1.11% 0.089
Normalize/complex -0.75% 0.063
TransformParse/simple_select -0.52% 0.427
TransformParse/join -1.14% 0.121
TransformParse/complex -0.23% 0.571
LintParallel/rules -16.01% <0.001
LintParallel/lint -10.11% <0.001
LintRW/postgresql -10.99% <0.001
LintRW/mysql -10.23% <0.001
LintRW/mariadb -10.65% <0.001
LintRW/sqlserver -11.97% <0.001
LintRW/oracle -10.29% <0.001
LintRW/sqlite -10.93% <0.001
LintRW/snowflake -10.66% <0.001
LintRW/clickhouse -10.31% <0.001
LintRule/L007 -70.24% <0.001
LintRule/L010 +0.05% 0.912
ParseError/syntax_mid/parse -1.56% 0.247
ParseError/syntax_mid/validate +0.02% 0.970
ParseError/syntax_end/parse -0.97% 0.173
ParseError/syntax_end/validate -0.34% 0.520
ParseError/tokenize/parse -1.06% 0.436
ParseError/tokenize/validate +1.11% 0.212
ParseError/long_prefix/parse -0.15% 0.912
ParseError/long_prefix/validate -0.38% 0.684
geomean -5.07%
Environment
date:        2026-11-03T18:30:55Z
base:        a72ec74daf9a3bd7ca53e7642ff407795df5525d
head:        working tree of a71d8d64b303893f66edb5b6bd94080d157d7457
cpu model:   AMD EPYC 7763 64-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 416910951412..416910951431 (base, head), 416910951432..416910951451 (A/A')
pinning:     pinned to CPU 3 (SMT siblings 2-3) with taskset
gcoff env:   GOGC=off GOMEMLIMIT=64MiB
clocksource: tsc
load:        6.28, 2.36, 0.86
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 7763 64-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:                                1 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 7763 64-Core Processor                
                        │    base     │                head                │
                        │   sec/op    │   sec/op     vs base               │
Normalize/simple_select   2.324µ ± 1%   2.338µ ± 1%       ~ (p=0.365 n=20)
Normalize/join            7.124µ ± 1%   7.159µ ± 1%       ~ (p=0.565 n=20)
Normalize/complex         19.64µ ± 1%   19.67µ ± 2%       ~ (p=0.794 n=20)
geomean                   6.877µ        6.905µ       +0.41%

                        │     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.627µ ± 1%   1.629µ ± 1%       ~ (p=0.703 n=20)
ParseE2E/select_where             3.453µ ± 1%   3.442µ ± 1%       ~ (p=0.606 n=20)
ParseE2E/join                     4.621µ ± 1%   4.608µ ± 1%       ~ (p=0.693 n=20)
ParseE2E/aggregate                3.465µ ± 1%   3.475µ ± 2%       ~ (p=0.635 n=20)
ParseE2E/subquery                 3.100µ ± 1%   3.123µ ± 2%       ~ (p=0.147 n=20)
ParseE2E/cte                      4.461µ ± 1%   4.444µ ± 1%       ~ (p=0.723 n=20)
ParseE2E/window                   2.912µ ± 1%   2.898µ ± 1%       ~ (p=0.365 n=20)
ParseE2E/complex                  12.29µ ± 2%   12.23µ ± 2%       ~ (p=0.410 n=20)
ParseE2E/insert                   2.178µ ± 1%   2.153µ ± 1%       ~ (p=0.132 n=20)
ParseE2E/update                   1.928µ ± 1%   1.926µ ± 1%       ~ (p=0.836 n=20)
ParseE2E/delete                   1.232µ ± 1%   1.239µ ± 2%       ~ (p=0.733 n=20)
ParseRW/postgresql/medium         169.0µ ± 1%   168.3µ ± 1%       ~ (p=0.698 n=20)
ParseRW/mysql/medium              96.84µ ± 1%   96.66µ ± 1%       ~ (p=0.547 n=20)
ParseRW/mariadb/medium            137.6µ ± 1%   137.3µ ± 1%       ~ (p=0.925 n=20)
ParseRW/sqlserver/medium          274.7µ ± 1%   275.5µ ± 1%       ~ (p=0.799 n=20)
ParseRW/oracle/medium             327.2µ ± 1%   325.4µ ± 1%       ~ (p=0.369 n=20)
ParseRW/sqlite/medium             73.20µ ± 1%   73.54µ ± 2%       ~ (p=0.547 n=20)
ParseRW/snowflake/medium          190.4µ ± 1%   189.4µ ± 1%       ~ (p=0.114 n=20)
ParseRW/clickhouse/medium         89.43µ ± 3%   90.16µ ± 3%       ~ (p=0.718 n=20)
ParseFresh/simple_select          7.084µ ± 1%   7.077µ ± 1%       ~ (p=0.763 n=20)
ParseFresh/join                   11.45µ ± 0%   11.46µ ± 1%       ~ (p=0.878 n=20)
ParseFresh/complex                20.61µ ± 1%   20.48µ ± 2%       ~ (p=0.211 n=20)
PoolGetPut/serial                 67.03n ± 2%   66.99n ± 1%       ~ (p=0.763 n=20)
ParseError/syntax_mid/parse       2.422µ ± 1%   2.429µ ± 1%       ~ (p=0.387 n=20)
ParseError/syntax_mid/validate    2.178µ ± 1%   2.184µ ± 1%       ~ (p=0.533 n=20)
ParseError/syntax_end/parse       2.764µ ± 4%   2.796µ ± 1%       ~ (p=0.147 n=20)
ParseError/syntax_end/validate    2.639µ ± 2%   2.639µ ± 2%       ~ (p=0.365 n=20)
ParseError/tokenize/parse         913.1n ± 2%   916.6n ± 1%       ~ (p=0.289 n=20)
ParseError/tokenize/validate      2.837µ ± 1%   2.841µ ± 2%       ~ (p=0.625 n=20)
ParseError/long_prefix/parse      60.91µ ± 2%   59.65µ ± 1%       ~ (p=0.077 n=20)
ParseError/long_prefix/validate   57.83µ ± 3%   56.34µ ± 2%       ~ (p=0.108 n=20)
geomean                           9.534µ        9.519µ       -0.16%

                                │     base     │                head                 │
                                │     B/s      │     B/s       vs base               │
ParseE2E/simple_select            15.24Mi ± 1%   15.23Mi ± 1%       ~ (p=0.693 n=20)
ParseE2E/select_where             28.45Mi ± 1%   28.54Mi ± 1%       ~ (p=0.606 n=20)
ParseE2E/join                     26.01Mi ± 1%   26.08Mi ± 1%       ~ (p=0.683 n=20)
ParseE2E/aggregate                23.12Mi ± 1%   23.06Mi ± 2%       ~ (p=0.625 n=20)
ParseE2E/subquery                 25.23Mi ± 1%   25.04Mi ± 2%       ~ (p=0.147 n=20)
ParseE2E/cte                      26.51Mi ± 1%   26.61Mi ± 1%       ~ (p=0.723 n=20)
ParseE2E/window                   30.79Mi ± 1%   30.94Mi ± 1%       ~ (p=0.358 n=20)
ParseE2E/complex                  29.34Mi ± 2%   29.48Mi ± 2%       ~ (p=0.417 n=20)
ParseE2E/insert                   33.27Mi ± 1%   33.66Mi ± 1%       ~ (p=0.133 n=20)
ParseE2E/update                   27.21Mi ± 1%   27.23Mi ± 1%       ~ (p=0.857 n=20)
ParseE2E/delete                   40.28Mi ± 1%   40.03Mi ± 2%       ~ (p=0.753 n=20)
ParseRW/postgresql/medium         21.22Mi ± 1%   21.32Mi ± 1%       ~ (p=0.683 n=20)
ParseRW/mysql/medium              27.95Mi ± 0%   28.00Mi ± 1%       ~ (p=0.551 n=20)
ParseRW/mariadb/medium            25.76Mi ± 1%   25.83Mi ± 1%       ~ (p=0.878 n=20)
ParseRW/sqlserver/medium          24.22Mi ± 1%   24.16Mi ± 1%       ~ (p=0.784 n=20)
ParseRW/oracle/medium             25.62Mi ± 1%   25.76Mi ± 1%       ~ (p=0.387 n=20)
ParseRW/sqlite/medium             28.22Mi ± 1%   28.09Mi ± 2%       ~ (p=0.533 n=20)
ParseRW/snowflake/medium          26.70Mi ± 1%   26.84Mi ± 1%       ~ (p=0.113 n=20)
ParseRW/clickhouse/medium         25.20Mi ± 3%   25.00Mi ± 3%       ~ (p=0.713 n=20)
ParseError/syntax_mid/parse       18.90Mi ± 1%   18.85Mi ± 1%       ~ (p=0.379 n=20)
ParseError/syntax_mid/validate    21.02Mi ± 1%   20.96Mi ± 1%       ~ (p=0.525 n=20)
ParseError/syntax_end/parse       13.46Mi ± 4%   13.30Mi ± 1%       ~ (p=0.151 n=20)
ParseError/syntax_end/validate    14.09Mi ± 2%   14.10Mi ± 2%       ~ (p=0.365 n=20)
ParseError/tokenize/parse         48.05Mi ± 2%   47.86Mi ± 1%       ~ (p=0.286 n=20)
ParseError/tokenize/validate      15.46Mi ± 1%   15.44Mi ± 2%       ~ (p=0.616 n=20)
ParseError/long_prefix/parse      37.92Mi ± 2%   38.72Mi ± 1%       ~ (p=0.077 n=20)
ParseError/long_prefix/validate   39.94Mi ± 3%   40.99Mi ± 2%       ~ (p=0.108 n=20)
geomean                           25.43Mi        25.47Mi       +0.16%

                                │      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=1.000 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.91Ki ± 0%     65.91Ki ± 0%       ~ (p=0.652 n=20)
ParseRW/mysql/medium              42.37Ki ± 0%     42.37Ki ± 0%       ~ (p=1.000 n=20)
ParseRW/mariadb/medium            60.61Ki ± 0%     60.61Ki ± 0%       ~ (p=0.267 n=20)
ParseRW/sqlserver/medium          83.68Ki ± 0%     83.68Ki ± 0%       ~ (p=0.887 n=20)
ParseRW/oracle/medium             104.8Ki ± 0%     104.8Ki ± 0%       ~ (p=0.862 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.070 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   14.09µ ± 1%   14.02µ ± 1%       ~ (p=0.693 n=20)
ParseRW/mysql/medium        12.11µ ± 1%   12.08µ ± 1%       ~ (p=0.542 n=20)
ParseRW/mariadb/medium      15.29µ ± 1%   15.25µ ± 1%       ~ (p=0.904 n=20)
ParseRW/sqlserver/medium    13.08µ ± 1%   13.12µ ± 1%       ~ (p=0.799 n=20)
ParseRW/oracle/medium       13.63µ ± 1%   13.56µ ± 1%       ~ (p=0.365 n=20)
ParseRW/sqlite/medium       12.20µ ± 1%   12.26µ ± 2%       ~ (p=0.547 n=20)
ParseRW/snowflake/medium    14.64µ ± 1%   14.57µ ± 1%       ~ (p=0.116 n=20)
ParseRW/clickhouse/medium   12.78µ ± 3%   12.88µ ± 3%       ~ (p=0.703 n=20)
geomean                     13.43µ        13.43µ       -0.05%

pkg: github.com/ajitpratap0/GoSQLX/pkg/linter
                   │     base     │                 head                 │
                   │    sec/op    │    sec/op     vs base                │
LintParallel/rules   35.80m ±  1%   29.20m ±  3%  -18.42% (p=0.000 n=20)
LintParallel/lint    55.39m ±  1%   51.65m ±  1%   -6.75% (p=0.000 n=20)
LintRW/postgresql    7.209m ±  2%   6.538m ±  1%   -9.31% (p=0.000 n=20)
LintRW/mysql         7.489m ±  1%   6.763m ±  1%   -9.69% (p=0.000 n=20)
LintRW/mariadb       6.914m ±  0%   6.259m ±  1%   -9.48% (p=0.000 n=20)
LintRW/sqlserver     6.462m ±  0%   5.799m ±  1%  -10.26% (p=0.000 n=20)
LintRW/oracle        7.792m ±  1%   6.935m ±  0%  -11.01% (p=0.000 n=20)
LintRW/sqlite        6.512m ±  3%   5.886m ±  3%   -9.61% (p=0.000 n=20)
LintRW/snowflake     6.717m ±  3%   6.038m ±  1%  -10.11% (p=0.000 n=20)
LintRW/clickhouse    7.510m ±  1%   6.800m ±  2%   -9.47% (p=0.000 n=20)
LintRule/L007        8.035m ±  4%   2.749m ±  1%  -65.79% (p=0.000 n=20)
LintRule/L010        700.5µ ± 27%   703.5µ ± 26%        ~ (p=0.495 n=20)
geomean              8.002m         6.676m        -16.56%

                  │     base     │                 head                 │
                  │     B/s      │     B/s       vs base                │
LintRW/postgresql   3.929Mi ± 2%   4.334Mi ± 1%  +10.32% (p=0.000 n=20)
LintRW/mysql        4.368Mi ± 1%   4.835Mi ± 1%  +10.70% (p=0.000 n=20)
LintRW/mariadb      4.411Mi ± 1%   4.873Mi ± 1%  +10.49% (p=0.000 n=20)
LintRW/sqlserver    4.797Mi ± 0%   5.341Mi ± 1%  +11.33% (p=0.000 n=20)
LintRW/oracle       5.097Mi ± 1%   5.727Mi ± 0%  +12.35% (p=0.000 n=20)
LintRW/sqlite       4.406Mi ± 4%   4.878Mi ± 3%  +10.71% (p=0.000 n=20)
LintRW/snowflake    4.644Mi ± 3%   5.164Mi ± 1%  +11.19% (p=0.000 n=20)
LintRW/clickhouse   4.325Mi ± 1%   4.773Mi ± 2%  +10.36% (p=0.000 n=20)
geomean             4.485Mi        4.976Mi       +10.93%

                   │       base       │                  head                  │
                   │       B/op       │     B/op      vs base                  │
LintParallel/rules    10.963Mi ± 0%     8.111Mi ± 0%  -26.01% (p=0.000 n=20)
LintParallel/lint      30.93Mi ± 0%     28.08Mi ± 0%   -9.22% (p=0.000 n=20)
LintRW/postgresql      3.402Mi ± 0%     3.089Mi ± 0%   -9.20% (p=0.000 n=20)
LintRW/mysql           4.243Mi ± 0%     3.880Mi ± 0%   -8.58% (p=0.000 n=20)
LintRW/mariadb         3.924Mi ± 0%     3.570Mi ± 0%   -9.03% (p=0.000 n=20)
LintRW/sqlserver       3.488Mi ± 0%     3.124Mi ± 0%  -10.45% (p=0.000 n=20)
LintRW/oracle          4.242Mi ± 0%     3.818Mi ± 0%  -10.00% (p=0.000 n=20)
LintRW/sqlite          3.697Mi ± 0%     3.385Mi ± 0%   -8.44% (p=0.000 n=20)
LintRW/snowflake       3.745Mi ± 0%     3.384Mi ± 0%   -9.63% (p=0.000 n=20)
LintRW/clickhouse      4.190Mi ± 0%     3.832Mi ± 0%   -8.55% (p=0.000 n=20)
LintRule/L007        3012.73Ki ± 0%     92.98Ki ± 0%  -96.91% (p=0.000 n=20)
LintRule/L010            0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=20) ¹
geomean                             ²                 -32.13%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                   │      base      │                 head                  │
                   │   allocs/op    │  allocs/op   vs base                  │
LintParallel/rules    229.7k ± 0%     158.3k ± 0%  -31.07% (p=0.000 n=20)
LintParallel/lint     308.3k ± 0%     236.9k ± 0%  -23.15% (p=0.000 n=20)
LintRW/postgresql     34.19k ± 0%     26.50k ± 0%  -22.49% (p=0.000 n=20)
LintRW/mysql          41.49k ± 0%     32.41k ± 0%  -21.88% (p=0.000 n=20)
LintRW/mariadb        38.23k ± 0%     29.61k ± 0%  -22.56% (p=0.000 n=20)
LintRW/sqlserver      34.62k ± 0%     25.62k ± 0%  -25.99% (p=0.000 n=20)
LintRW/oracle         42.24k ± 0%     31.61k ± 0%  -25.16% (p=0.000 n=20)
LintRW/sqlite         37.58k ± 0%     29.43k ± 0%  -21.69% (p=0.000 n=20)
LintRW/snowflake      37.56k ± 0%     28.81k ± 0%  -23.30% (p=0.000 n=20)
LintRW/clickhouse     42.34k ± 0%     32.89k ± 0%  -22.32% (p=0.000 n=20)
LintRule/L007        72.565k ± 0%     1.194k ± 0%  -98.35% (p=0.000 n=20)
LintRule/L010          0.000 ± 0%      0.000 ± 0%        ~ (p=1.000 n=20) ¹
geomean                           ²                -43.51%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                  │     base      │                 head                 │
                  │   sec/stmt    │   sec/stmt    vs base                │
LintRW/postgresql    23.64µ ±  2%   21.44µ ±  1%   -9.31% (p=0.000 n=20)
LintRW/mysql         18.09µ ±  1%   16.34µ ±  1%   -9.69% (p=0.000 n=20)
LintRW/mariadb       18.20µ ±  0%   16.47µ ±  1%   -9.47% (p=0.000 n=20)
LintRW/sqlserver     23.33µ ±  0%   20.93µ ±  1%  -10.26% (p=0.000 n=20)
LintRW/oracle        21.17µ ±  1%   18.84µ ±  0%  -11.01% (p=0.000 n=20)
LintRW/sqlite        18.71µ ±  3%   16.91µ ±  3%   -9.61% (p=0.000 n=20)
LintRW/snowflake     20.54µ ±  3%   18.46µ ±  1%  -10.11% (p=0.000 n=20)
LintRW/clickhouse    19.06µ ±  1%   17.26µ ±  2%   -9.46% (p=0.000 n=20)
LintRule/L007       2856.5n ±  4%   977.1n ±  1%  -65.79% (p=0.000 n=20)
LintRule/L010        249.1n ± 27%   250.1n ± 26%        ~ (p=0.482 n=20)
geomean              10.72µ         8.865µ        -17.30%

pkg: github.com/ajitpratap0/GoSQLX/pkg/sql/tokenizer
                         │    base     │                head                │
                         │   sec/op    │   sec/op     vs base               │
Tokenize/scratch/join      1.481µ ± 2%   1.480µ ± 1%       ~ (p=0.804 n=20)
Tokenize/scratch/complex   3.948µ ± 1%   3.939µ ± 2%       ~ (p=0.578 n=20)
Tokenize/scratch/insert    746.8n ± 3%   748.9n ± 2%       ~ (p=0.877 n=20)
geomean                    1.634µ        1.634µ       +0.01%

                         │     base     │                head                 │
                         │     B/s      │     B/s       vs base               │
Tokenize/scratch/join      81.16Mi ± 1%   81.18Mi ± 1%       ~ (p=0.805 n=20)
Tokenize/scratch/complex   91.31Mi ± 1%   91.52Mi ± 2%       ~ (p=0.569 n=20)
Tokenize/scratch/insert    97.06Mi ± 2%   96.78Mi ± 3%       ~ (p=0.877 n=20)
geomean                    89.60Mi        89.59Mi       -0.01%

                         │     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.529µ ± 1%   1.531µ ± 1%       ~ (p=0.449 n=20)
TransformParse/join            5.003µ ± 1%   4.996µ ± 1%       ~ (p=0.825 n=20)
TransformParse/complex         13.02µ ± 2%   13.13µ ± 2%       ~ (p=0.208 n=20)
geomean                        4.635µ        4.648µ       +0.28%

                             │     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 added the documentation Improvements or additions to documentation label Nov 1, 2026
@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

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


Review of PR #896: allocation-free L007 keyword-case scan

I read the diff of keyword_case.go against origin/main. I did not run the tests, so the results in the PR description are not re-verified here.

Verdict: No blocking issues. The change is correct and well scoped.

Correctness

  • scanWords: line[wordStart:i] is the same text the old WriteRune builder produced. For invalid UTF-8, range yields U+FFFD with width 1. That rune is not a letter or digit, so it ends a word and never lands inside an emitted word. Columns are still 1-based byte offsets.
  • isSQLKeyword fast path: ASCII words up to 16 bytes are upper-cased into a stack buffer. The sqlKeywords[string(buf[:n])] lookup is compiler-optimized and does not allocate. Any byte >= 0x80 falls back to strings.ToUpper, so Unicode folding such as ſ → S is kept.
  • Long-word branch: The comment on the unreachable branch is useful. All-ASCII words over 16 bytes return false, which is sound while every keyword is at most 16 bytes. TestKeywordLengthBound guards that.
  • Violation path: Check computes strings.ToUpper or strings.ToLower once per keyword and compares it to the original. The messages and suggestions match the old output.

Suggestions (non-blocking)

  1. Comment the closure contract. scanWords stays allocation-free only if emit doesn't escape. TestScanWordsNoAllocations would catch a regression, but a one-line comment on scanWords would help the next editor.
  2. Cross-reference the keyword bound. Add a note next to the sqlKeywords table that maxKeywordLen must cover every entry. Make sure the TestKeywordLengthBound failure message says to raise the constant.
  3. Avoid a second conversion on clean keywords. In Check, a keyword that is already in the preferred case still goes through ToUpper or ToLower. For ASCII input these return the original string without allocating when nothing changes. The cost is a scan, not an allocation. This is fine as it is.
  4. Add a fuzz test. The oracle already exists, so a FuzzKeywordCaseDifferential over arbitrary byte strings would be cheap. It would go beyond the 3,002-statement corpus.

Performance

The deterministic numbers are the trustworthy ones: -81% to -90% B/op and -88% to -94% allocs/op on L007, and -12% to -15% end-to-end allocs. The time claim comes only from the earlier quiet run. The CI perf gate shows no parser regression, but its suite has no lint benchmarks, so it does not measure the L007 gain. Adding BenchmarkLintRule to the gated suite would confirm the amd64 time gain.

Security

No concerns. There is no unsafe code, and the stack buffer is length-checked before indexing.

Tests

Coverage is strong: a differential test against the previous implementation (kept as an oracle), allocation tests for scanWords and the clean-ASCII case, and the keyword length-bound check.

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

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


Review of PR #896: allocation-free L007 keyword-case scan

I read the keyword_case.go diff against origin/main. I did not run the tests, so the results in the PR description are not re-verified here.

Verdict: No blocking issues. The change is correct and well scoped.

Correctness

  • scanWords: line[wordStart:i] is the same text the old WriteRune builder produced. For invalid UTF-8, range yields U+FFFD with width 1. That rune is not a letter or digit, so it ends a word and never lands inside one. Columns are still 1-based byte offsets.
  • isSQLKeyword:
    • Short ASCII words are upper-cased into a [16]byte stack buffer. The sqlKeywords[string(buf[:n])] lookup is optimized by the compiler and does not allocate.
    • Any byte >= 0x80 goes through strings.ToUpper, so Unicode folding such as ſ → S is kept.
    • The long-word branch is unreachable today. The comment explains why, and TestKeywordLengthBound guards the bound.
  • Check: Messages, suggestions and columns match the old output.

Suggestions (non-blocking)

  1. Add a one-line comment on scanWords saying emit must not escape, since the zero-allocation property depends on it. TestScanWordsNoAllocations would catch a regression.
  2. Add a note next to the sqlKeywords table pointing at maxKeywordLen. Make sure the TestKeywordLengthBound failure message says to raise the constant.
  3. Add a FuzzKeywordCaseDifferential against the oracle. It is cheap now that the oracle exists.
  4. strings.ToUpper and strings.ToLower in Check run on every keyword. They don't allocate when the word is already in the target case, so this is fine.

Performance

The deterministic numbers hold up: -81% to -90% B/op and -88% to -94% allocs/op on L007, and -12% to -15% end-to-end allocs.

The amd64 time gain is still unconfirmed. The shadow perf gate passed, but its suite has no lint benchmarks. It shows the parser path doesn't regress and says nothing about L007. Per the PR notes, add BenchmarkLintRule to scripts/perf/gate-suite.txt or run the gate with a base that includes the bench files, and paste the result before merging.

Security

No concerns. There is no unsafe code, and the stack buffer is length-checked before indexing.

Tests

Coverage is strong:

  • a differential test against the old implementation (kept as an oracle) over 3,002 statements plus edge cases, including keyword-before-quote
  • allocation tests for scanWords and the clean-ASCII case
  • the keyword length-bound check

ajitpratap0 added a commit that referenced this pull request Nov 2, 2026
#896 (L007), #902 (L010) and #905 (lazy error wrap) target benchmarks the
PR-run gate never measured, so none of them could be accepted or closed
on an amd64 verdict. long_prefix is included so #905's expected miss is
still checked for regressions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2
ajitpratap0 added a commit that referenced this pull request Nov 3, 2026
…w suite (#927)

* ci(perf): add BenchmarkLintRW to the shadow gate suite

PR #906's gate run moved 0 of 26 benchmarks because no linter benchmark was
gated. Add it so W6-05b, W6-06 and W6-07 are measured by the PR-run gate.

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

* ci(perf): gate LintRule L007/L010 and ParseError in the shadow suite

#896 (L007), #902 (L010) and #905 (lazy error wrap) target benchmarks the
PR-run gate never measured, so none of them could be accepted or closed
on an amd64 verdict. long_prefix is included so #905's expected miss is
still checked for regressions.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Resolve CHANGELOG (and gate-suite) conflicts by placing this PR's entries in main's Unreleased sections.

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 #896: allocation-free L007 keyword-case scan

I read the diff of keyword_case.go and CHANGELOG.md against origin/main. I did not run the tests, so the results in the PR description are not re-verified here.

Verdict: No blocking issues. The change is correct and well scoped.

Correctness

  • scanWords: line[wordStart:i] is the same text the old WriteRune builder produced. Invalid UTF-8 yields U+FFFD with width 1. That rune is not a letter or digit, so it ends a word and is never part of an emitted word. Columns are still 1-based byte offsets.
  • isSQLKeyword:
    • ASCII words of up to 16 bytes are upper-cased into a stack buffer. The sqlKeywords[string(buf[:n])] lookup does not allocate.
    • Words with a byte >= 0x80 go through strings.ToUpper, so Unicode folding such as ſ → S is kept.
    • The fallback branch for words over 16 bytes is documented as defensive. An all-ASCII word over 16 bytes returns false. That is sound while every keyword is at most 16 bytes, and TestKeywordLengthBound guards it.
  • Check: Messages, suggestions and columns are unchanged.

Suggestions (non-blocking)

  1. Add a one-line comment on scanWords saying emit must not escape, because the zero-allocation property depends on it. TestScanWordsNoAllocations would catch a regression.
  2. Add a note next to the sqlKeywords table pointing at maxKeywordLen. Make sure the TestKeywordLengthBound failure message says to raise the constant.
  3. Add a FuzzKeywordCaseDifferential against the oracle. The oracle already exists, so this is cheap.
  4. In Check, the ToUpper and ToLower calls do not allocate when the word is already in the target case. The allocation happens only on actual violations, which is acceptable.

Performance

The deterministic numbers hold up: allocs/op on L007 drops from 72565 to 1194, and end-to-end allocs drop about 12%. The amd64 time gain is still unconfirmed. The shadow perf gate passed, but its suite has no lint benchmarks, so it shows no parser regression and says nothing about L007. As the PR notes, run the gate with BenchmarkLintRule/L007 and BenchmarkLintRW added to the suite and paste the table before merging.

Security

No concerns. There is no unsafe code, and the stack buffer is length-checked before indexing.

Tests

Coverage is strong:

  • a differential test against the previous implementation (kept as an oracle) over 3,002 statements plus edge cases, including keyword-before-quote
  • allocation tests for scanWords and clean ASCII lines
  • the keyword length-bound check

@ajitpratap0

Copy link
Copy Markdown
Owner Author

Holding for re-gate under the layout-randomized perf gate (same policy as #931). The REGRESSION verdict is LintRule/L010 +11.9% on the pinned leg, but this PR only changes L007 (keyword_case.go): L010 allocs and B/op are unchanged, and the default-GC leg shows L010 +1.0% (p=0.105). The L007 result itself is strong: -64.8% time, 72565 -> 1194 allocs/op; LintRW -6.6% to -10.4% across all 8 dialects.

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 16s —— View job


Review of PR #896: allocation-free L007 keyword-case scan

I read the diff of keyword_case.go and CHANGELOG.md against origin/main. I did not run the tests, so the results in the PR description are not re-verified here.

Verdict: No blocking issues. The change is correct and well scoped.

Correctness

  • scanWords: line[wordStart:i] is the same text the old WriteRune builder produced. Invalid UTF-8 yields U+FFFD with width 1. That rune is not a letter or digit, so it ends a word and is never part of an emitted word. Columns are still 1-based byte offsets.
  • isSQLKeyword:
    • ASCII words of up to 16 bytes are upper-cased into a stack buffer. The sqlKeywords[string(buf[:n])] lookup does not allocate.
    • Words with a byte >= 0x80 go through strings.ToUpper, so Unicode folding such as ſ → S is kept.
    • The branch for words over 16 bytes is documented as defensive. It is sound while every keyword is at most 16 bytes, and TestKeywordLengthBound guards that.
  • Check: Messages, suggestions and columns are unchanged.

Perf gate note

The LintRule/L010 +11.9% on the pinned leg looks unrelated to this PR. keyword_case.go is the only non-test file changed, L010 allocs and B/op are identical, and the default-GC leg shows +1.0% (p=0.105). That fits your layout-noise reading, so re-running under the layout-randomized gate is the right call.

The L007 result is strong in the same run: -64.8% time, 72565 → 1194 allocs/op, and LintRW -6.6% to -10.4% across all 8 dialects. This also covers the amd64 time confirmation that was outstanding. Once the re-gate passes, that item can be closed.

Suggestions (non-blocking)

  1. Add a one-line comment on scanWords saying emit must not escape, because the zero-allocation property depends on it. TestScanWordsNoAllocations would catch a regression.
  2. Add a note next to the sqlKeywords table pointing at maxKeywordLen. Make sure the TestKeywordLengthBound failure message says to raise the constant.
  3. Add a FuzzKeywordCaseDifferential against the oracle. The oracle already exists, so this is cheap.
  4. In Check, ToUpper and ToLower do not allocate when the word is already in the target case. They allocate only on actual violations, which is acceptable.

Security

No concerns. There is no unsafe code, and the stack buffer is length-checked before indexing.

Tests

Coverage is strong:

  • a differential test against the previous implementation (kept as an oracle) over 3,002 statements plus edge cases, including keyword-before-quote
  • allocation tests for scanWords and clean ASCII lines
  • the keyword length-bound check
    · branch perf/w6-03-l007-alloc-free

@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 #896: allocation-free L007 keyword-case scan

I read the diff of keyword_case.go and CHANGELOG.md against origin/main. I did not run the tests, so the results in the PR description are not re-verified here.

Verdict: No blocking issues. The change is correct and well scoped.

Correctness

  • scanWords: line[wordStart:i] is the same text the old WriteRune builder produced. Invalid UTF-8 yields U+FFFD with width 1, which is not a letter or digit, so it ends a word and is never part of an emitted word. Columns are still 1-based byte offsets.
  • isSQLKeyword:
    • ASCII words of up to 16 bytes are upper-cased into a stack buffer. The sqlKeywords[string(buf[:n])] lookup does not allocate.
    • Words with a byte >= 0x80 go through strings.ToUpper, so Unicode folding such as ſ → S is kept.
    • The branch for words over 16 bytes is documented as defensive. An all-ASCII word over 16 bytes returns false, which is sound while every keyword is at most 16 bytes. TestKeywordLengthBound guards that.
  • Check: Messages, suggestions and columns are unchanged. The hasViolation branches are now identical in form (text != expectedCase), so the if/else could be collapsed into one comparison after the switch. That is cosmetic only.

Suggestions (non-blocking)

  1. Add a one-line comment on scanWords saying emit must not escape, because the zero-allocation property depends on it. TestScanWordsNoAllocations would catch a regression.
  2. Add a note next to the sqlKeywords table pointing at maxKeywordLen. Make sure the TestKeywordLengthBound failure message says to raise the constant.
  3. Add a FuzzKeywordCaseDifferential against the oracle. The oracle already exists, so this is cheap.
  4. In Check, ToUpper and ToLower do not allocate when the word is already in the target case. They allocate only on actual violations, which is acceptable.

Performance

The latest perf gate run (base 8c98501, head e298a1d, amd64, layout-randomized) reports REGRESSION in shadow mode. The only flagged benchmark is LintRule/L010 at +20.2% time, with B/op and allocs/op unchanged at 0.

  • This PR does not touch L010 code. The L010 change in this branch's diff is none, so the likely cause is code-layout or alignment effects. The default-GC leg shows the same +19.8%, so it is not GC-related. That is a larger and more consistent shift than your earlier +11.9% / +1.0% reading, so please don't treat it as settled noise yet.
  • Before merging, confirm it is not real. Run -run xxx -bench 'LintRule/L010' -count 10 on base and head separately on amd64 to see whether the gap follows the layout, or check whether e298a1d includes any change that L010 shares (for example a shared helper or the Context type).
  • The L007 result is strong and now confirmed on amd64: -68.6% time, 72565 → 1194 allocs/op, -96.9% B/op. LintRW improves 8.6% to 10.6% across all 8 dialects, and the gated parser, tokenizer and transform benchmarks are flat (geomean differences are driven by the lint benchmarks added to the suite).
  • That confirms the amd64 time gain that was outstanding, so that item can be closed.

Security

No concerns. There is no unsafe code, and the stack buffer is length-checked before indexing.

Tests

Coverage is strong:

  • a differential test against the previous implementation (kept as an oracle) over 3,002 statements plus edge cases, including keyword-before-quote
  • allocation tests for scanWords and clean ASCII lines
  • the keyword length-bound check
    · branch perf/w6-03-l007-alloc-free

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

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


Review of PR #896: allocation-free L007 keyword-case scan

I read the diff of keyword_case.go and CHANGELOG.md against origin/main. I did not run the tests, so the results in the PR description are not re-verified here.

Verdict: No blocking issues. The change is correct and well scoped.

Correctness

  • scanWords: line[wordStart:i] is the same text the old WriteRune builder produced. Invalid UTF-8 yields U+FFFD with width 1, which is not a letter or digit. It ends a word and never lands inside an emitted word. Columns are still 1-based byte offsets.
  • isSQLKeyword:
    • ASCII words of up to 16 bytes are upper-cased into a stack buffer. The sqlKeywords[string(buf[:n])] lookup does not allocate.
    • Words with a byte >= 0x80 go through strings.ToUpper, so Unicode folding such as ſ → S is kept.
    • The branch for words over 16 bytes is documented as defensive. An all-ASCII word over 16 bytes returns false, which is sound while every keyword is at most 16 bytes. TestKeywordLengthBound guards that.
  • Check: Messages, suggestions and columns are unchanged. Both hasViolation branches now have the same form (text != expectedCase). You could collapse them into one comparison after the style switch. That is cosmetic.

Suggestions (non-blocking)

  1. Add a one-line comment on scanWords saying emit must not escape, because the zero-allocation property depends on it. TestScanWordsNoAllocations would catch a regression.
  2. Add a note next to the sqlKeywords table pointing at maxKeywordLen. Make sure the TestKeywordLengthBound failure message says to raise the constant.
  3. Add a FuzzKeywordCaseDifferential against the oracle. The oracle already exists, so this is cheap.

Performance

The latest layout-randomized amd64 gate (head 9659ccb) reports PASS:

  • LintRule/L007: -66.4% time, 72565 → 1194 allocs/op, -96.9% B/op.
  • LintRW: 8.6% to 11.4% faster across all 8 dialects.
  • LintRule/L010: +0.22% (p=0.369), with the A/A' control clean. The earlier L010 flags were layout artifacts, consistent with L010 being untouched.
  • The parser, tokenizer, transform and normalize benchmarks are flat.

This closes the outstanding amd64 time confirmation. An earlier comment on this thread reported a later L010 +20.2% regression at head e298a1d. That is not in the data above, and I could not see that run here. If that run is still current, re-check it before merging. Otherwise the PASS above supersedes it.

Security

No concerns. There is no unsafe code, and the stack buffer is length-checked before indexing.

Tests

Coverage is strong:

  • A differential test against the previous implementation (kept as an oracle) over 3,002 statements plus edge cases, including a keyword directly before a quote.
  • Allocation tests for scanWords and clean ASCII lines.
  • The keyword length-bound check.
    · branch perf/w6-03-l007-alloc-free

@ajitpratap0
ajitpratap0 merged commit 6d29d68 into main Nov 3, 2026
52 checks passed
@ajitpratap0
ajitpratap0 deleted the perf/w6-03-l007-alloc-free branch November 3, 2026 19:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation linter performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant