Coverage for gws-app/gws/lib/cql/__init__.py: 100%
3 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""CQL2 support.
3Parses CQL2-Text filter expressions and turns them into database expressions.
5Reference:
6 - https://docs.ogc.org/is/21-065r2/21-065r2.html
7 - https://docs.ogc.org/is/21-065r2/21-065r2.html#cql2-bnf
9Submodules:
11- ``parser``: the CQL2-Text parser (``parse``, ``ParseError``) and the
12 constants for node types (``Node``) and keyword sets (``C``).
13- ``builder``: the generic tree walker ``Builder``, the SQLAlchemy
14 generator ``SqlBuilder`` and ``BuildError``.
16The parser does not know about databases, and the builders do not parse
17text: the parse tree described below is the interface between them.
19Usage::
21 cond = cql.SqlBuilder(table).build(cql.parse("a_int > 10 AND S_INTERSECTS(a_geom, POINT(1 1))"))
22 sel = sa.select(table).where(cond)
24Parse trees
25-----------
27``parse`` returns a tree of plain lists, where the first element is the node type
28and the rest are arguments::
30 a_int > 10 ['>', ['NAME', 'a_int'], ['INT', 10]]
31 a_int IS NULL ['IS_NULL', ['NAME', 'a_int']]
32 a IN (1, 2) ['IN', ['NAME', 'a'], ['INT', 1], ['INT', 2]]
34Node types are listed in ``Node``, operators and other keyword sets in ``C``.
35Literal nodes carry a python value: ``['INT', 10]``, ``['DATE', datetime.date(...)]``.
36A ``NAME`` node carries the dot-separated parts of a property name: ``a.b`` is
37``['NAME', 'a', 'b']``. The operators ``<>`` and ``!=`` are both emitted as ``<>``.
39Function calls come in two flavours. Names the standard knows about (``C.FUNCTIONS``)
40are checked for arity and emitted lowercased as ``FUNCTION``, everything else is
41emitted verbatim as ``USER_FUNCTION``::
43 S_Intersects(g, h) ['FUNCTION', 's_intersects', ['NAME', 'g'], ['NAME', 'h']]
44 myschema.fn(1) ['USER_FUNCTION', 'myschema.fn', ['INT', 1]]
46Builders
47--------
49``Builder`` walks a tree and dispatches on the node type to a ``build_<type>`` method,
50and on the function name to a ``func_<name>`` method. Operators without a ``build_<type>``
51method go to ``build_operator``. Missing methods raise ``BuildError``,
52so a subclass supports exactly what it implements.
54``SqlBuilder`` generates SQLAlchemy expressions for a postgis table and implements all
55standard functions. Subclasses customize single node types, e.g. a model that stores
56geometries in a projected crs only overrides the geometry literals::
58 class MyBuilder(cql.SqlBuilder):
59 def build_wkt(self, args):
60 return sa.func.ST_Transform(super().build_wkt(args), 3857)
62Backend specific functions are handled by ``build_user_function``, which receives the
63name as written, followed by the argument nodes.
65Notes
66-----
68- ``SqlBuilder`` requires postgis, and the ``unaccent`` extension for the ``ACCENTI``
69 function (``CREATE EXTENSION unaccent``).
70- ``SqlBuilder`` resolves a ``NAME`` by its first part only, as a column of the table.
71- Geometry literals and ``BBOX`` are in WGS84 (EPSG:4326).
72- Temporal predicates compare ``tstzrange`` values, an instant being a degenerate
73 range. Bounds are inclusive, so intervals that only touch do intersect.
74 Timestamps without a zone are read as UTC.
75- Array literals are accepted both in the standard form ``('a', 'b')`` and as
76 ``['a', 'b']``. Arrays are compared as sets, in particular ``A_EQUALS`` ignores
77 order and duplicates.
78- ``BBOX`` is limited to the 2d form with four arguments.
79"""
81from .parser import parse, ParseError, Node, C
82from .builder import Builder, SqlBuilder, BuildError
84__all__ = [
85 'parse',
86 'ParseError',
87 'Node',
88 'C',
89 'Builder',
90 'SqlBuilder',
91 'BuildError',
92]