Skip to content

fix: a pattern is a string, IS is a keyword in any case, YES and NO are names - #21

Merged
christiangda merged 1 commit into
mainfrom
fix/a-pattern-is-a-string-and-is-in-any-case
Oct 12, 2026
Merged

christiangda merged 1 commit into
mainfrom
fix/a-pattern-is-a-string-and-is-in-any-case

Conversation

@christiangda

Copy link
Copy Markdown
Contributor

What

Three places where the filter parser and PostgreSQL disagreed about an input. What the pattern operators take is read from PostgreSQL's manual, 9.7. Pattern Matching:

string LIKE pattern [ESCAPE escape-character]
string SIMILAR TO pattern [ESCAPE escape-character]
text ~ text → boolean

and ~~, ~~*, !~~, !~~* are what PostgreSQL translates LIKE, ILIKE and their negations to.

Input Before Now PostgreSQL 18.6
a number or a boolean as a pattern: name LIKE 1, name NOT ILIKE -1, name ~~ 1, name SIMILAR TO 1, name LIKE TRUE accepted refused: expected string pattern for operator LIKE, got LITERAL operator does not exist: text ~~ integer, and the same for an integer, a numeric and a boolean column: no ~~ takes one
YES and NO as boolean literals: active = YES, active IN (yes, no) accepted, as true and false refused names: a comparison with a column called yes
IS in lower case: id is null, id is distinct from 1, active is true refused (Is, iS and IS were accepted) accepted runs
a field called yes or no, in the allow-list: yes = 1 refused accepted runs

Why each

  • A pattern is a string. The POSIX operators (~, ~*, !~, !~*) already refused anything else; LIKE, ILIKE, SIMILAR TO and the ~~ family took any literal. The documentation already said that the parser "checks that the right-hand side is a string literal" for all of them. It is not a matter of the column's type, which the parser does not know: a number is no pattern for any column.
  • is was kept back as an identifier by a special case in the lexer, written for a test that tokenized an English sentence. Every other keyword, and every other spelling of this one, was read in any case.
  • YES and NO are not SQL's. An expression that parsed with one could not be spliced into a WHERE clause as it was written: PostgreSQL looked for a column.

Not in it

The ESCAPE clause is not part of the grammar, before or now; name LIKE 'x!%' ESCAPE '!' is refused, and the filtering page says so. A pattern uses the default escape character, the backslash.

Compatibility

Two inputs that were accepted are refused, so this is for a release note: docs/migration.md has the section ("v1.0.3 → the next release"; the version is the maintainer's to name), with what changes for a caller that walks the tree (reflect.Bool literals come from TRUE/FALSE only; a LIKE node's right side and a SimilarToNode's Pattern are always a string literal) and for Lexer users.

Tests

filter_pg_patterns_test.go, each row run against PostgreSQL 18.6 with what it answered in the comment: 20 refused patterns (every operator, three column types, inside a group, under NOT, ESCAPE), the refusal's wording for the six operators, 14 accepted patterns whose node carries a string literal; IS in four spellings across its predicates, and is = 1 refused; YES/NO refused in eight positions, TRUE/FALSE in any case, a field called yes, and the string 'yes'. Three older tests pinned the old behaviour and are corrected.

go test -race -tags=unit ./... passes; coverage 97.5 %, the new function 100 %.

🤖 Generated with Claude Code

…re names

Three places where the filter parser and PostgreSQL disagreed.

LIKE, ILIKE, SIMILAR TO and the ~~ operators take a string pattern, as
the POSIX operators already did: PostgreSQL has no ~~ that takes a
number or a boolean, whatever the column's type is.

`is` in lower case is the IS keyword, like every other spelling of it;
the lexer kept it back as an identifier.

YES and NO are no boolean literals: SQL has TRUE and FALSE, and
PostgreSQL reads the other two as names. A field may be called yes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@christiangda christiangda self-assigned this Oct 12, 2026
@christiangda
christiangda merged commit 01746e7 into main Oct 12, 2026
1 check passed
@christiangda
christiangda deleted the fix/a-pattern-is-a-string-and-is-in-any-case branch October 12, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant