1# ------------------------------------------------------------------------------ 2# 3# Project: pycql <https://github.com/geopython/pycql> 4# Authors: Fabian Schindler <fabian.schindler@eox.at> 5# 6# ------------------------------------------------------------------------------ 7# Copyright (C) 2019 EOX IT Services GmbH 8# 9# Permission is hereby granted, free of charge, to any person obtaining a copy 10# of this software and associated documentation files (the "Software"), to deal 11# in the Software without restriction, including without limitation the rights 12# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13# copies of the Software, and to permit persons to whom the Software is 14# furnished to do so, subject to the following conditions: 15# 16# The above copyright notice and this permission notice shall be included in all 17# copies of this Software or works derived from this Software. 18# 19# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25# THE SOFTWARE. 26# ------------------------------------------------------------------------------ 27 28""" 29""" 30 31 32class Node: 33 """ The base class for all other nodes to display the AST of CQL. 34 """ 35 inline = False 36 37 def get_sub_nodes(self): 38 """ Get a list of sub-node of this node. 39 40 :return: a list of all sub-nodes 41 :rtype: list[Node] 42 """ 43 raise NotImplementedError 44 45 def get_template(self): 46 """ Get a template string (using the ``%`` operator) 47 to represent the current node and sub-nodes. The template string 48 must provide a template replacement for each sub-node reported by 49 :func:`~pycql.ast.Node.get_sub_nodes`. 50 51 :return: the template to render 52 """ 53 raise NotImplementedError 54 55 def __eq__(self, other): 56 if type(self) != type(other): 57 return False 58 59 return self.__dict__ == other.__dict__ 60 61 62class ConditionNode(Node): 63 """ The base class for all nodes representing a condition 64 """ 65 pass 66 67 68class NotConditionNode(ConditionNode): 69 """ 70 Node class to represent a negation condition. 71 72 :ivar sub_node: the condition node to be negated 73 :type sub_node: Node 74 """ 75 76 def __init__(self, sub_node): 77 self.sub_node = sub_node 78 79 def get_sub_nodes(self): 80 """ Returns the sub-node for the negated condition. """ 81 return [self.sub_node] 82 83 def get_template(self): 84 return "NOT %s" 85 86 87class CombinationConditionNode(ConditionNode): 88 """ Node class to represent a condition to combine two other conditions 89 using either AND or OR. 90 91 :ivar lhs: the left hand side node of this combination 92 :type lhs: Node 93 :ivar rhs: the right hand side node of this combination 94 :type rhs: Node 95 :ivar op: the combination type. Either ``"AND"`` or ``"OR"`` 96 :type op: str 97 """ 98 def __init__(self, lhs, rhs, op): 99 self.lhs = lhs 100 self.rhs = rhs 101 self.op = op 102 103 def get_sub_nodes(self): 104 return [self.lhs, self.rhs] 105 106 def get_template(self): 107 return "%%s %s %%s" % self.op 108 109 110class PredicateNode(Node): 111 """ The base class for all nodes representing a predicate 112 """ 113 pass 114 115 116class ComparisonPredicateNode(PredicateNode): 117 """ Node class to represent a comparison predicate: to compare two 118 expressions using a comparison operation. 119 120 :ivar lhs: the left hand side node of this comparison 121 :type lhs: Node 122 :ivar rhs: the right hand side node of this comparison 123 :type rhs: Node 124 :ivar op: the comparison type. One of ``"="``, ``"<>"``, ``"<"``, 125 ``">"``, ``"<="``, ``">="`` 126 :type op: str 127 """ 128 def __init__(self, lhs, rhs, op): 129 self.lhs = lhs 130 self.rhs = rhs 131 self.op = op 132 133 def get_sub_nodes(self): 134 return [self.lhs, self.rhs] 135 136 def get_template(self): 137 return "%%s %s %%s" % self.op 138 139 140class BetweenPredicateNode(PredicateNode): 141 """ Node class to represent a BETWEEN predicate: to check whether an 142 expression value within a range. 143 144 :ivar lhs: the left hand side node of this comparison 145 :type lhs: Node 146 :ivar low: the lower bound of the clause 147 :type low: Node 148 :ivar high: the upper bound of the clause 149 :type high: Node 150 :ivar not_: whether the predicate shall be negated 151 :type not_: bool 152 """ 153 def __init__(self, lhs, low, high, not_): 154 self.lhs = lhs 155 self.low = low 156 self.high = high 157 self.not_ = not_ 158 159 def get_sub_nodes(self): 160 return [self.lhs, self.low, self.high] 161 162 def get_template(self): 163 return "%%s %sBETWEEN %%s AND %%s" % ("NOT " if self.not_ else "") 164 165 166class LikePredicateNode(PredicateNode): 167 """ Node class to represent a wildcard sting matching predicate. 168 169 :ivar lhs: the left hand side node of this predicate 170 :type lhs: Node 171 :ivar rhs: the right hand side node of this predicate 172 :type rhs: Node 173 :ivar case: whether the comparison shall be case sensitive 174 :type case: bool 175 :ivar not_: whether the predicate shall be negated 176 :type not_: bool 177 """ 178 def __init__(self, lhs, rhs, case, not_): 179 self.lhs = lhs 180 self.rhs = rhs 181 self.case = case 182 self.not_ = not_ 183 184 def get_sub_nodes(self): 185 return [self.lhs, self.rhs] 186 187 def get_template(self): 188 return "%%s %s%sLIKE %%s" % ( 189 "NOT " if self.not_ else "", 190 "I" if self.case else "" 191 ) 192 193 194class InPredicateNode(PredicateNode): 195 """ Node class to represent list checking predicate. 196 197 :ivar lhs: the left hand side node of this predicate 198 :type lhs: Node 199 :ivar sub_nodes: the list of sub nodes to check the inclusion 200 against 201 :type sub_nodes: list[Node] 202 :ivar not_: whether the predicate shall be negated 203 :type not_: bool 204 """ 205 def __init__(self, lhs, sub_nodes, not_): 206 self.lhs = lhs 207 self.sub_nodes = sub_nodes 208 self.not_ = not_ 209 210 def get_sub_nodes(self): 211 return [self.lhs] + list(self.sub_nodes) 212 213 def get_template(self): 214 return "%%s %sIN (%s)" % ( 215 "NOT " if self.not_ else "", 216 ", ".join(["%s"] * len(self.sub_nodes)) 217 ) 218 219 220class NullPredicateNode(PredicateNode): 221 """ Node class to represent null check predicate. 222 223 :ivar lhs: the left hand side node of this predicate 224 :type lhs: Node 225 :ivar not_: whether the predicate shall be negated 226 :type not_: bool 227 """ 228 def __init__(self, lhs, not_): 229 self.lhs = lhs 230 self.not_ = not_ 231 232 def get_sub_nodes(self): 233 return [self.lhs] 234 235 def get_template(self): 236 return "%%s IS %sNULL" % ("NOT " if self.not_ else "") 237 238 239# class ExistsPredicateNode(PredicateNode): 240# pass 241 242 243class TemporalPredicateNode(PredicateNode): 244 """ Node class to represent temporal predicate. 245 246 :ivar lhs: the left hand side node of this comparison 247 :type lhs: Node 248 :ivar rhs: the right hand side node of this comparison 249 :type rhs: Node 250 :ivar op: the comparison type. One of ``"BEFORE"``, 251 ``"BEFORE OR DURING"``, ``"DURING"``, 252 ``"DURING OR AFTER"``, ``"AFTER"`` 253 :type op: str 254 """ 255 def __init__(self, lhs, rhs, op): 256 self.lhs = lhs 257 self.rhs = rhs 258 self.op = op 259 260 def get_sub_nodes(self): 261 return [self.lhs, self.rhs] 262 263 def get_template(self): 264 return "%%s %s %%s" % self.op 265 266 267class SpatialPredicateNode(PredicateNode): 268 """ Node class to represent spatial relation predicate. 269 270 :ivar lhs: the left hand side node of this comparison 271 :type lhs: Node 272 :ivar rhs: the right hand side node of this comparison 273 :type rhs: Node 274 :ivar op: the comparison type. One of ``"INTERSECTS"``, 275 ``"DISJOINT"``, ``"CONTAINS"``, ``"WITHIN"``, 276 ``"TOUCHES"``, ``"CROSSES"``, ``"OVERLAPS"``, 277 ``"EQUALS"``, ``"RELATE"``, ``"DWITHIN"``, ``"BEYOND"`` 278 :type op: str 279 :ivar pattern: the relationship patter for the ``"RELATE"`` operation 280 :type pattern: str or None 281 :ivar distance: the distance for distance related operations 282 :type distance: Node or None 283 :ivar units: the units for distance related operations 284 :type units: str or None 285 """ 286 def __init__(self, lhs, rhs, op, pattern=None, distance=None, units=None): 287 self.lhs = lhs 288 self.rhs = rhs 289 self.op = op 290 self.pattern = pattern 291 self.distance = distance 292 self.units = units 293 294 def get_sub_nodes(self): 295 return [self.lhs, self.rhs] 296 297 def get_template(self): 298 if self.pattern: 299 return "%s(%%s, %%s, %r)" % (self.op, self.pattern) 300 elif self.distance or self.units: 301 return "%s(%%s, %%s, %r, %r)" % (self.op, self.distance, self.units) 302 else: 303 return "%s(%%s, %%s)" % (self.op) 304 305 306class BBoxPredicateNode(PredicateNode): 307 """ Node class to represent a bounding box predicate. 308 309 :ivar lhs: the left hand side node of this predicate 310 :type lhs: Node 311 :ivar minx: the minimum X value of the bounding box 312 :type minx: float 313 :ivar miny: the minimum Y value of the bounding box 314 :type miny: float 315 :ivar maxx: the maximum X value of the bounding box 316 :type maxx: float 317 :ivar maxx: the maximum Y value of the bounding box 318 :type maxx: float 319 :ivar crs: the coordinate reference system identifier 320 for the CRS the BBox is expressed in 321 :type crs: str 322 """ 323 def __init__(self, lhs, minx, miny, maxx, maxy, crs=None): 324 self.lhs = lhs 325 self.minx = minx 326 self.miny = miny 327 self.maxx = maxx 328 self.maxy = maxy 329 self.crs = crs 330 331 def get_sub_nodes(self): 332 return [self.lhs] 333 334 def get_template(self): 335 return "BBOX(%%s, %r, %r, %r, %r, %r)" % ( 336 self.minx, self.miny, self.maxx, self.maxy, self.crs 337 ) 338 339 340class ExpressionNode(Node): 341 """ The base class for all nodes representing expressions 342 """ 343 pass 344 345 346class AttributeExpression(ExpressionNode): 347 """ Node class to represent attribute lookup expressions 348 349 :ivar name: the name of the attribute to be accessed 350 :type name: str 351 """ 352 inline = True 353 354 def __init__(self, name): 355 self.name = name 356 357 def __repr__(self): 358 return "ATTRIBUTE %s" % self.name 359 360 361class LiteralExpression(ExpressionNode): 362 """ Node class to represent literal value expressions 363 364 :ivar value: the value of the literal 365 :type value: str, float, int, datetime, timedelta 366 """ 367 inline = True 368 369 def __init__(self, value): 370 self.value = value 371 372 def __repr__(self): 373 return "LITERAL %r" % self.value 374 375 376class ArithmeticExpressionNode(ExpressionNode): 377 """ Node class to represent arithmetic operation expressions with two 378 sub-expressions and an operator. 379 380 :ivar lhs: the left hand side node of this arithmetic expression 381 :type lhs: Node 382 :ivar rhs: the right hand side node of this arithmetic expression 383 :type rhs: Node 384 :ivar op: the comparison type. One of ``"+"``, ``"-"``, ``"*"``, ``"/"`` 385 :type op: str 386 """ 387 def __init__(self, lhs, rhs, op): 388 self.lhs = lhs 389 self.rhs = rhs 390 self.op = op 391 392 def get_sub_nodes(self): 393 return [self.lhs, self.rhs] 394 395 def get_template(self): 396 return "%%s %s %%s" % self.op 397 398 399def indent(text, amount, ch=' '): 400 padding = amount * ch 401 return ''.join(padding+line for line in text.splitlines(True)) 402 403 404def get_repr(node, indent_amount=0, indent_incr=4): 405 """ Get a debug representation of the given AST node. ``indent_amount`` 406 and ``indent_incr`` are for the recursive call and don't need to be 407 passed. 408 409 :param Node node: the node to get the representation for 410 :param int indent_amount: the current indentation level 411 :param int indent_incr: the indentation incrementation per level 412 :return: the represenation of the node 413 :rtype: str 414 """ 415 sub_nodes = node.get_sub_nodes() 416 template = node.get_template() 417 418 args = [] 419 for sub_node in sub_nodes: 420 if isinstance(sub_node, Node) and not sub_node.inline: 421 args.append("(\n%s\n)" % 422 indent( 423 get_repr(sub_node, indent_amount + indent_incr, indent_incr), 424 indent_amount + indent_incr 425 ) 426 ) 427 else: 428 args.append(repr(sub_node)) 429 430 return template % tuple(args) 431