gws.lib.cql¶
CQL2 support.
Parses CQL2-Text filter expressions and turns them into database expressions.
- Reference:
Submodules:
parser: the CQL2-Text parser (parse,ParseError) and the constants for node types (Node) and keyword sets (C).builder: the generic tree walkerBuilder, the SQLAlchemy generatorSqlBuilderandBuildError.
The parser does not know about databases, and the builders do not parse text: the parse tree described below is the interface between them.
Usage:
cond = cql.SqlBuilder(table).build(cql.parse("a_int > 10 AND S_INTERSECTS(a_geom, POINT(1 1))"))
sel = sa.select(table).where(cond)
Parse trees¶
parse returns a tree of plain lists, where the first element is the node type
and the rest are arguments:
a_int > 10 ['>', ['NAME', 'a_int'], ['INT', 10]]
a_int IS NULL ['IS_NULL', ['NAME', 'a_int']]
a IN (1, 2) ['IN', ['NAME', 'a'], ['INT', 1], ['INT', 2]]
Node types are listed in Node, operators and other keyword sets in C.
Literal nodes carry a python value: ['INT', 10], ['DATE', datetime.date(...)].
A NAME node carries the dot-separated parts of a property name: a.b is
['NAME', 'a', 'b']. The operators <> and != are both emitted as <>.
Function calls come in two flavours. Names the standard knows about (C.FUNCTIONS)
are checked for arity and emitted lowercased as FUNCTION, everything else is
emitted verbatim as USER_FUNCTION:
S_Intersects(g, h) ['FUNCTION', 's_intersects', ['NAME', 'g'], ['NAME', 'h']]
myschema.fn(1) ['USER_FUNCTION', 'myschema.fn', ['INT', 1]]
Builders¶
Builder walks a tree and dispatches on the node type to a build_<type> method,
and on the function name to a func_<name> method. Operators without a build_<type>
method go to build_operator. Missing methods raise BuildError,
so a subclass supports exactly what it implements.
SqlBuilder generates SQLAlchemy expressions for a postgis table and implements all
standard functions. Subclasses customize single node types, e.g. a model that stores
geometries in a projected crs only overrides the geometry literals:
class MyBuilder(cql.SqlBuilder):
def build_wkt(self, args):
return sa.func.ST_Transform(super().build_wkt(args), 3857)
Backend specific functions are handled by build_user_function, which receives the
name as written, followed by the argument nodes.
Notes¶
SqlBuilderrequires postgis, and theunaccentextension for theACCENTIfunction (CREATE EXTENSION unaccent).SqlBuilderresolves aNAMEby its first part only, as a column of the table.Geometry literals and
BBOXare in WGS84 (EPSG:4326).Temporal predicates compare
tstzrangevalues, an instant being a degenerate range. Bounds are inclusive, so intervals that only touch do intersect. Timestamps without a zone are read as UTC.Array literals are accepted both in the standard form
('a', 'b')and as['a', 'b']. Arrays are compared as sets, in particularA_EQUALSignores order and duplicates.BBOXis limited to the 2d form with four arguments.
Source code: gws.lib.cql