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

1"""CQL2 support. 

2 

3Parses CQL2-Text filter expressions and turns them into database expressions. 

4 

5Reference: 

6 - https://docs.ogc.org/is/21-065r2/21-065r2.html 

7 

8Usage:: 

9 

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) 

12 

13Parse trees 

14----------- 

15 

16`parse` returns a tree of plain lists, where the first element is the node type 

17and the rest are arguments:: 

18 

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]] 

22 

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']`. 

27 

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`:: 

31 

32 S_Intersects(g, h) ['FUNCTION', 's_intersects', ['NAME', 'g'], ['NAME', 'h']] 

33 myschema.fn(1) ['USER_FUNCTION', 'myschema.fn', ['INT', 1]] 

34 

35Builders 

36-------- 

37 

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. 

41 

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:: 

45 

46 class MyBuilder(cql.SqlBuilder): 

47 def build_wkt(self, args): 

48 return sa.func.ST_Transform(super().build_wkt(args), 3857) 

49 

50Backend specific functions are handled by `build_user_function`, which receives the 

51name as written, followed by the argument nodes. 

52 

53Notes 

54----- 

55 

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""" 

66 

67from .parser import parse, ParseError, Node, C 

68from .builder import Builder, SqlBuilder, BuildError 

69 

70__all__ = [ 

71 'parse', 

72 'ParseError', 

73 'Node', 

74 'C', 

75 'Builder', 

76 'SqlBuilder', 

77 'BuildError', 

78]