Repository navigation
fix: the filter parser refuses what PostgreSQL refuses, and bounds its nesting - #19
Merged
Merged
Conversation
…s nesting An expression the parser accepts is spliced into a WHERE clause, so it must not be one PostgreSQL calls a syntax error. Eight shapes were accepted and are one there, or an operator that does not exist; one that runs was refused; and one input ended the process. Refused now: - `id DISTINCT FROM 1`, `id NOT DISTINCT FROM 1`: SQL writes `IS [NOT] DISTINCT FROM`, and IS is the only way in. - a hexadecimal float (`0x1p-2`): text/scanner reads Go numbers. A number is a decimal literal, for integers and floats alike (so `1_000.5`, which ran, is refused as `1_000` already was). - a word glued to a number (`id=1AND name='x'`): PostgreSQL's "trailing junk after numeric literal". The whole word is the illegal token. - `active IS YES`: YES and NO are boolean literals, not truth values of IS. - `name NOT ~~ 'x'`: NOT negates the keyword LIKE, not its symbol. - a keyword spelled with a non-ASCII letter (`lıke`, `Iſ`, `aſc`): strings.ToUpper maps 'ı' and 'ſ' to 'I' and 'S', PostgreSQL folds case in ASCII only. - a leading byte order mark, which text/scanner dropped without a token. - more than 100 levels of nesting. The parser is recursive and the input chose the depth: about 250 000 opening parentheses ended the process with a stack overflow, a fatal error no recover catches. Accepted now: a negative number (`id = -1`, `IN (-1, 2)`, `BETWEEN -5 AND 5`). A minus sign glued to a decimal literal is part of it; not after an operator written with `!` or `~`, which PostgreSQL reads as one longer operator (`!=-`). Every row of the new tests was run against PostgreSQL 18, and so was every expression the parser accepts out of nine million random token sequences: none is a syntax error. Reviewed by comparing this parser with the previous one on 43 million expressions; that found the underscore float, the two letters, the mark and the overflow. Eighteen mutants of the change, seventeen caught; the eighteenth is equivalent. Adds FuzzFilterParser: no panic, and a node or an error, never both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The coverage step installed the tool with "@latest". v2.20.0 requires Go 1.27, this module builds with the Go of its go.mod (1.26, GOTOOLCHAIN=local), and the job failed the day that version was published: on this pull request, and it would have failed the release workflow the same way. v2.19.0 is the last version whose go.mod says 1.26. Raise the pin together with the go directive. Checked with go1.26.8: the tool installs, the suite passes with the race detector, and the coverage thresholds are met (97.5%). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
An expression the filter parser accepts is spliced into a
WHEREclause by its callers, so it must not be one PostgreSQL calls a syntax error. This makes the parser refuse what PostgreSQL refuses, accept negative numbers, and bound how deep an expression may nest.id DISTINCT FROM 1,id NOT DISTINCT FROM 1IS [NOT] DISTINCT FROMprice = 0x1p-2(a hexadecimal float)id=1AND name='x'(a word glued to a number)active IS YES,active IS NOT NOname NOT ~~ 'x',name NOT ~~* 'x'NOT LIKEor!~~lıke,Iſ,aſcin a sort)price = 1_000.5(a float with_)id = -1,id IN (-1, 2),age BETWEEN -5 AND 5((((…,NOT NOT NOT …)The first three and the negative number are the gaps found from
svc-qu3ry-core.How
DISTINCT FROM: the two branches that took it withoutISare gone.ISis the only way in, as the docs already said.text/scannerreads Go numbers. A number is now a decimal literal, for integers and floats alike, and a word glued to it is one illegal token (1AND).'x'ANDstays accepted: a string ends at its quote, and PostgreSQL runs it.LiteralNode.Textis-1,Valueisint64(-1)). There is still no arithmetic. A sign directly after an operator written with!or~is refused, because PostgreSQL reads the longest operator and such an operator may end in-(!=-).strings.ToUppermaps the dotlessıand the longſtoIandS;asciiUpperdoes not. The sort parser's directions use it too.recovercatches. A long flat expression is not limited by this. Callers should still bound the length of what they hand toParse.How it was checked
SELECT … WHERE (<input>); the comment on each row is what it answered._, the two letters, the byte order mark and the stack overflow; all four are in this change.FuzzFilterParser(new, the first fuzz target here): no panic, and a node or an error, never both.go test -race -tags=unit ./...passes, 97.4% of statements.For the release
_. I wrote the migration section as v1.0.2 → v1.0.3; rename it if you tag otherwise.0x10and1_000changes frominvalid integertoillegal token(both were refused before and are still).Lexerusers:-followed by a digit is now the start of anINTorFLOATtoken.YES/NOas boolean literals (active = yesis accepted and PostgreSQL readsyesas a column name; the docs now say so), lowercaseis(refused, by an older special case in the lexer), andLIKEwith a number as its pattern.🤖 Generated with Claude Code