Repository navigation
fix(parser): linear qualified-name joining and correct E2002 error context (#613, #616) - #627
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @ajitpratap0's task in 20s —— View job Review of PR #627Overall this looks good. The changes are small and targeted, and the tests are well considered. I read the diff of #613
#616 hint text vs SQL source
Performance and security
Tests
No blocking issues. The only change I'd ask for is the comment fix. |
#613) parseQualifiedName built dotted names with `name = name + "." + part`, so time and allocation grew quadratically with the number of parts: a ~100 KB `a.a.a...` chain in FROM, JOIN, INSERT, UPDATE, DELETE, CREATE/DROP/TRUNCATE, views or indexes cost hundreds of MB to GBs. The parts are now collected in a fixed stack array (it does not escape) and joined once with strings.Join, and names with more than maxQualifiedNameParts (5, the same limit as column references from #585) are rejected with E2004 "qualified name has more than 5 parts". Names with 1 to 5 parts produce identical strings and SQL() output. The FROM path surfaces the E2004 directly; the JOIN, DML and DDL callers keep discarding or re-wrapping the error as E2002 "expected table name", so those inputs are rejected fast but with the callers' existing message (callers intentionally untouched to keep the diff localized). Benchmarks (benchstat, Apple M4, machine heavily loaded by concurrent runs so ns/op is noisy; allocs and B/op are exact): ParserSimpleSelect/ComplexSelect/Insert/Update/Delete allocs and B/op unchanged QualifiedTableName/1part 8 allocs, 2.128Ki (unchanged) QualifiedTableName/3part 10 -> 9 allocs (-10%) QualifiedTableName/5part 12 -> 9 allocs (-25%) QualifiedTableName/chain4KB 2068 -> 30 allocs (-98.6%), 5032Ki -> 664Ki (-86.8%) Fixes #613 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve
…errors (#616) Several parser error builders were handed a human-readable sentence as their `sql` argument. That argument becomes Error.Context.SQL, so Error() printed the sentence as "line 1" of the query with a caret at the real column. The parser does not hold the source SQL, so these sites now pass "" (no snippet, as in #585) and carry the sentence, verbatim, as the hint. Code, message and location are unchanged. ExpectedTokenError (E2002): FROM, SAMPLE (x5), PREWHERE, WHERE and the SELECT column list guard. InvalidSyntaxError (E2004), same defect: CAST type params, INTERVAL, empty array subscript, ::TYPE(...) params, and the two strict-mode empty statement checks. Adds a table-driven regression test plus a go/ast guard that fails if a non-test parser file passes a non-empty string literal as a pkg/errors builder's `sql` parameter. Attaching the real source text to parser errors is left as a follow-up. Fixes #616 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve
627-1: the Snowflake permissive CREATE <kind> stub (STAGE, SEQUENCE, ...)
discarded the parseQualifiedName error, so a 6+ part name parsed
successfully with only its tail in RawSQL ("CREATE SEQUENCE f" for
"CREATE SEQUENCE a.b.c.d.e.f"). The stub now returns the E2004 part-limit
error; other name errors are still tolerated as before.
627-2: every non-FROM caller of parseQualifiedName (JOIN, INSERT, UPDATE,
DELETE, CREATE TABLE/VIEW/MATERIALIZED VIEW/INDEX, ClickHouse MV TO,
REFRESH MATERIALIZED VIEW, DROP, TRUNCATE, MySQL SHOW/DESCRIBE/REPLACE,
Snowflake USE) replaced the E2004 with "expected table name, got
IDENTIFIER", pointing at a valid identifier. The part-limit error now
carries an errQualifiedNameTooLong cause, and callers pass it through
unchanged via isQualifiedNameTooLong/qualifiedNameError. Tests now assert
the E2004 message on every path.
627-3: the BETWEEN lower/upper, LIKE, REGEXP and IN-subquery wrappers
passed the current token's text as the error's sql argument, printing a
fake "1 | )" source line. They now pass "" and attach the operand error
with WithCause. The #616 static guard now flags any sql argument other
than the literal "" (allowlisting validate.go's real-source
string(input)), not just non-empty string literals.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve
f5d80b8 to
a7120d6
Compare
|
Claude finished @ajitpratap0's task in 15s —— View job Review of PR #627 (re-review after the latest push)I read the diff of The head has moved on since the earlier review. JOIN and DDL callers now pass the E2004 through via Remaining nits
Performance and security
Tests
No blocking issues. The comment fix and the description update are the only changes I'd ask for. |
|
Merge train (batch 3): #627
|
Summary
#613 parser: parseQualifiedName concatenates dotted table names quadratically
parseQualifiedName (pkg/sql/parser/parser.go) used to build dotted object names with repeated string concatenation and no part cap, so cost grew quadratically: a 64 KB
a.a.a...chain in FROM or DROP TABLE took about 66 ms and 1.17 GB and parsed successfully. It now collects up to 5 parts in a stack array (verified not to escape) and joins them once. Names with more than 5 parts are rejected with E2004 'qualified name has more than 5 parts', the same limit #585 set for column references. The same 64 KB input now fails in about 8 ms with 10.5 MB. Names with 1 to 5 parts render byte-identically on every caller path (FROM, JOIN, DML, CREATE/DROP/TRUNCATE, views, indexes) compared with main 61ca6c8. Benchmarks: allocs and B/op unchanged on Parser{Simple,Complex}Select/Insert/Update/Delete; 3-part and 5-part names drop 1 and 3 allocs; chain4KB drops 98.6% of allocs and 86.8% of B/op. Known limitations and deferred items: only the FROM path surfaces the E2004. JOIN, DML and DDL callers keep their existing E2002 'expected table name' wrapping and were left untouched to avoid conflicts with the from-joins and insert chains. The Snowflake permissive CREATE path now keeps only the trailing part of an over-long name in RawSQL. The change was verified by new regression tests that fail on main, the full -race suite, go vet, golangci-lint, and interleaved benchstat runs.#616 parser: several E2002 errors pass hint text as the SQL source, printing a bogus context snippet
Fixes #616. Fifteen parser error sites passed an explanatory sentence as the builder's
sqlargument: 9 ExpectedTokenError (E2002) sites in FROM, SAMPLE x5, PREWHERE, WHERE and the SELECT column list, plus 6 InvalidSyntaxError (E2004) sites in CAST/::TYPE params, INTERVAL, the empty array subscript and both strict-mode empty-statement checks. Error() then printed that sentence as line 1 of the user's SQL, with a caret at the real column. Each site now passes "" and attaches the same sentence through .WithHint(...), so the code, message and line/column are unchanged; only the fake snippet is gone and the sentence now appears as the Hint. Verified with a table-driven regression test covering every scout reproduction, a go/ast guard that fails if any non-test parser file passes a non-empty literal as a pkg/errors builder's sql argument, and a byte-for-byte comparison of the error headers before and after. The race suite (excluding cbinding) passes. Out of scope, with follow-ups: showing the real SQL in parser error context, which means threading source text into Parser or attaching it in gosqlx/CLI/LSP and would change output at roughly 100 sites; ClickHouse parsingFROM t SAMPLE/SAMPLE xwith SAMPLE as a table alias; polishing the wording of the hints.Commits (2), one implementation commit per issue plus test-only follow-ups:
8 files changed, 499 insertions(+), 35 deletions(-)Test Plan
CGO_ENABLED=1 go test -race -timeout 300s $(go list ./... | grep -v /cbinding)),go vet,golangci-lint, re-ran the reproduction cases on HEAD, and confirmed the new tests fail on the parent commit.benchstat -count=6before/after on the same machine.#613 evidence (validator)
#613 race gate: PASS.
CGO_ENABLED=1 go test -race -timeout 300s $(go list ./... | grep -v /cbinding)on HEAD 05eee99 printed EXIT=0 with 44 packages ok and no FAIL lines. No re-runs were needed. Log: /private/tmp/claude-501/-Users-ajitpratapsingh-dev-GoSQLX/b99cd3cf-cb92-4294-a0db-d6a7c9a86253/scratchpad/b3/613/val/race.txt#616 evidence (validator)
#616 race gate: CGO_ENABLED=1 go test -race -timeout 300s $(go list ./... | grep -v /cbinding) on HEAD f5d80b8 exited 0. 44 packages reported ok, with 0 FAIL lines and 0 DATA RACE reports. A few packages, such as tokenizer, were served from the test cache, which is keyed on their content. The full log is at /private/tmp/claude-501/-Users-ajitpratapsingh-dev-GoSQLX/b99cd3cf-cb92-4294-a0db-d6a7c9a86253/scratchpad/b3/616-race.txt. Nothing flaked, so no re-run was needed.
Breaking Changes
Notes
gosqlx formatsilently corrupts many expressions; switch the CLI to pkg/formatter #599-schema/postgres: loader misreports index columns and key flags (column order, INCLUDE, expression and partitioned indexes, read-only roles) #620); later PRs in the train are rebased onto this one after it merges.main.Fixes #613
Fixes #616
Follow-up to #566
🤖 Generated with Claude Code
https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve