Skip to content

fix(ecql): match keywords case-insensitively - #184

Open
epifanio wants to merge 1 commit into
geopython:mainfrom
epifanio:ecql-case-insensitive-keywords
Open

epifanio wants to merge 1 commit into
geopython:mainfrom
epifanio:ecql-case-insensitive-keywords

Conversation

@epifanio

@epifanio epifanio commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Refs #121.

Problem

The ECQL grammar only matches keywords in upper case, so these fail with UnexpectedToken:

from pygeofilter.parsers.ecql import parse
parse("attr like 'x%'")
parse("a = 1 and b = 2")
parse("intersects(geometry, POINT(1 1))")

ECQL keywords are case-insensitive, and the CQL2-text grammar here already matches them that way ("AND"i, …). As noted in #121, the ECQL grammar was left case-sensitive. That affects servers that parse user CQL with the ECQL parser. For example, pycsw parses CSW CQL_TEXT constraints with it for repositories that don't use a database. Long-standing CSW clients send apiso:Type like 'series', which pycsw's own cql2fes path accepts but this parser rejects.

Change

  • ecql/grammar.lark: every keyword literal is now case-insensitive ("LIKE" → "LIKE"i, etc.), including the spatial and temporal predicates, ENVELOPE, and DOES-NOT-EXIST. Distance units and the shared WKT grammar are unchanged.
  • ecql/parser.py: upper-case the spatial predicate name before the SPATIAL_PREDICATES_MAP lookup and the DWITHIN check. Those rules keep the matched token, so intersects(...) would otherwise raise a KeyError. This matches what the CQL2-text parser does with op.lower().

Tests

  • test_keywords_case_insensitive: each keyword form (boolean, comparison, LIKE/ILIKE/IN/IS NULL, EXISTS/DOES-NOT-EXIST, INCLUDE/EXCLUDE, temporal, spatial, RELATE, DWITHIN/BEYOND, BBOX, ENVELOPE), in lower case and title case, must parse to the same AST as upper case. On main these 54 cases fail; with this change they pass.
  • test_attribute_names_starting_with_keywords: notes, index, orbit, likes, isbn, android and between_x still lex as attribute names. These pass both before and after the change.
  • tests/parsers: 369 passed. ruff check and ruff format --check with the pinned v0.11.13 are clean.

ECQL keywords are case-insensitive, and the CQL2-text grammar already
matches them that way ("AND"i, ...), but the ECQL grammar only accepted
upper case: `attr like 'x'` or `a = 1 and b = 2` failed with
UnexpectedToken. Clients built against case-insensitive CQL
implementations (e.g. pycsw's cql2fes for CSW CQL_TEXT) send lowercase
keywords, which break when pycsw parses CQL_TEXT with pygeofilter.

Mark every ECQL keyword literal case-insensitive and upper-case the
spatial predicate name before looking it up, as the CQL2-text parser
does. Attribute names that start with a keyword (`notes`, `index`,
`orbit`) still lex as names.

Refs geopython#121
@epifanio
epifanio force-pushed the ecql-case-insensitive-keywords branch from f181b00 to dde3644 Compare October 13, 2026 13:52
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