Coverage for gws-app/gws/lib/cql/__init__.py: 100%
3 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 12:46 +0200
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 12:46 +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
8Usage::
10 cond = cql.SqlBuilder(table).build(cql.parse("a_int > 10 AND S_INTERSECTS(a_geom, POINT(1 1))"))
11 sel = sa.select(table).where(cond)
13Parse trees
14-----------
16`parse` returns a tree of plain lists, where the first element is the node type
17and the rest are arguments::
19 a_int > 10 ['>', ['NAME', 'a_int'], ['INT', 10]]
20 a_int IS NULL ['IS_NULL', ['NAME', 'a_int']]
21 a IN (1, 2) ['IN', ['NAME', 'a'], ['INT', 1], ['INT', 2]]
23Node types are listed in `Node`, operators and other keyword sets in `C`.
24Literal nodes carry a python value: `['INT', 10]`, `['DATE', datetime.date(...)]`.
25A `NAME` node carries the dot-separated parts of a property name: `a.b` is
26`['NAME', 'a', 'b']`.
28Function calls come in two flavours. Names the standard knows about (`C.FUNCTIONS`)
29are checked for arity and emitted lowercased as `FUNCTION`, everything else is
30emitted verbatim as `USER_FUNCTION`::
32 S_Intersects(g, h) ['FUNCTION', 's_intersects', ['NAME', 'g'], ['NAME', 'h']]
33 myschema.fn(1) ['USER_FUNCTION', 'myschema.fn', ['INT', 1]]
35Builders
36--------
38`Builder` walks a tree and dispatches on the node type to a `build_<type>` method,
39and on the function name to a `func_<name>` method. Missing methods raise `BuildError`,
40so a subclass supports exactly what it implements.
42`SqlBuilder` generates SQLAlchemy expressions for a postgis table and implements all
43standard functions. Subclasses customize single node types, e.g. a model that stores
44geometries in a projected crs only overrides the geometry literals::
46 class MyBuilder(cql.SqlBuilder):
47 def build_wkt(self, args):
48 return sa.func.ST_Transform(super().build_wkt(args), 3857)
50Backend specific functions are handled by `build_user_function`, which receives the
51name as written, followed by the argument nodes.
53Notes
54-----
56- `SqlBuilder` requires postgis, and the `unaccent` extension for the `ACCENTI`
57 function (``CREATE EXTENSION unaccent``).
58- Temporal predicates compare `tstzrange` values, an instant being a degenerate
59 range. Bounds are inclusive, so intervals that only touch do intersect.
60 Timestamps without a zone are read as UTC.
61- Array literals are accepted both in the standard form ``('a', 'b')`` and as
62 ``['a', 'b']``. Arrays are compared as sets, in particular `A_EQUALS` ignores
63 order and duplicates.
64- `BBOX` is limited to the 2d form with four arguments.
65"""
67from .parser import parse, ParseError, Node, C
68from .builder import Builder, SqlBuilder, BuildError
70__all__ = [
71 'parse',
72 'ParseError',
73 'Node',
74 'C',
75 'Builder',
76 'SqlBuilder',
77 'BuildError',
78]