Coverage for gws-app/gws/lib/cql/__init__.py: 100%
3 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-23 20:06 +0200
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-23 20:06 +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 ``<>``.
38Unary minus is emitted as ``NEG``: ``-a`` is ``['NEG', ['NAME', 'a']]``.
40Function calls come in two flavours. Names the standard knows about (``C.FUNCTIONS``)
41are checked for arity and emitted lowercased as ``FUNCTION``, everything else is
42emitted verbatim as ``USER_FUNCTION``::
44 S_Intersects(g, h) ['FUNCTION', 's_intersects', ['NAME', 'g'], ['NAME', 'h']]
45 myschema.fn(1) ['USER_FUNCTION', 'myschema.fn', ['INT', 1]]
47Builders
48--------
50``Builder`` walks a tree and dispatches on the node type to a ``build_<type>`` method,
51and on the function name to a ``func_<name>`` method. Operators without a ``build_<type>``
52method go to ``build_operator``. Missing methods raise ``BuildError``,
53so a subclass supports exactly what it implements.
55``SqlBuilder`` generates SQLAlchemy expressions for a postgis table and implements all
56standard functions. Subclasses customize single node types, e.g. a model that stores
57geometries in a projected crs only overrides the geometry literals::
59 class MyBuilder(cql.SqlBuilder):
60 def build_wkt(self, args):
61 return sa.func.ST_Transform(super().build_wkt(args), 3857)
63Backend specific functions are handled by ``build_user_function``, which receives the
64name as written, followed by the argument nodes.
66Notes
67-----
69- ``SqlBuilder`` requires postgis, and the ``unaccent`` extension for the ``ACCENTI``
70 function (``CREATE EXTENSION unaccent``).
71- ``SqlBuilder`` resolves a ``NAME`` by its first part only, as a column of the table.
72- Geometry literals and ``BBOX`` are in WGS84 (EPSG:4326).
73- Temporal predicates compare ``tstzrange`` values, an instant being a degenerate
74 range. Bounds are inclusive, so intervals that only touch do intersect.
75 Timestamps without a zone are read as UTC.
76- Array literals are accepted both in the standard form ``('a', 'b')`` and as
77 ``['a', 'b']``. Arrays are compared as sets, in particular ``A_EQUALS`` ignores
78 order and duplicates.
79- ``BBOX`` is limited to the 2d form with four arguments.
80"""
82from .parser import parse, ParseError, Node, C
83from .builder import Builder, SqlBuilder, BuildError
85__all__ = [
86 'parse',
87 'ParseError',
88 'Node',
89 'C',
90 'Builder',
91 'SqlBuilder',
92 'BuildError',
93]