1 """
2 Helpers for morphing modules
3 """
4
5
6
7
8
9
10
11 from gavo.adql import nodes
12
13
15 """is a scratchpad for morphers to communicate state among
16 themselves.
17
18 Append to warnings a necessary. Also, traverse keeps an attribute
19 nodeStack in here letting elements look up its parent chain.
20 """
22 self.warnings = []
23 self.nodeStack = []
24
25
26 _BOOLEANIZER_TABLE = {
27 ('=', '0'): "NOT ",
28 ('!=', '1'): "NOT ",
29 ('=', '1'): "",
30 ('!=', '0'): "",}
31
33 """prepends a NOT to expr if operator and operand suggest there should
34 be one for ADQL integerized boolean expressions.
35
36 The function will return None for unknown combinations of operator and
37 operand, and it will simply hand through Nones for expr, so calling
38 functions can just return addNotToBooleanized(...).
39 """
40 if expr is None:
41 return expr
42
43 prefix = _BOOLEANIZER_TABLE.get((operator, operand), None)
44 if prefix is None:
45
46 return None
47
48 return prefix+expr
49
50
51
52 _BOOLEANOID_FUNCTIONS = {}
53
55 """registers handler as a booleanizer for ADQL functions called
56 funcName.
57
58 funcName must be all-uppercase for this to work. handler(node,
59 operand, operator) is a function that receives a function node
60 and the operand and operator of the comparison and either returns
61 None to say it can't handle it, or something else; that something
62 else is what the entire comparison node is morphed into.
63
64 You can call multiple booleanizers for the same function; they will
65 be tried in sequence. Hence, make sure you get your import sequences
66 right if you do this.
67 """
68 _BOOLEANOID_FUNCTIONS.setdefault(funcName, []).append(handler)
69
70
72 """turns a comparison expression that's really a boolean
73 expression into a boolean expression.
74
75 New-style: handler functions of children of this just set an
76 OVERRIDE_RESULT attribute, and this just returns it. The DISTANCE
77 MORPHER already does this.
78
79 Old-style functionality (should be moved to new-style at some point):
80
81 This is for things like the geometry predicates (CONTAINS,
82 INTERSECTS) or stuff like ivo_hasword and such. Embedding these
83 as booleans helps the query planner a lot (though it might change
84 semantics slightly in the presence of NULLs).
85
86 The way this works is that morphing code can call
87 registerBooleanizer with the function name and callable
88 that receives the function node, the operator, and the operand.
89 If that function returns non-None, that result is used instead
90 of the current node.
91 """
92 if isinstance(node.op1, nodes.FunctionNode):
93 fCall, opd = node.op1, node.op2
94 elif isinstance(node.op2, nodes.FunctionNode):
95 fCall, opd = node.op2, node.op1
96 else:
97
98 return node
99
100 for morpher in _BOOLEANOID_FUNCTIONS.get(fCall.funName, []):
101 res = morpher(fCall, node.opr, nodes.flatten(opd))
102 if res is not None:
103 node = res
104 break
105 return node
106
107
109 """A class managing the process of morphing an ADQL expression.
110
111 It is constructed with a a dictionary of morphers; the keys are node
112 types, the values morphing functions.
113
114 Morphing functions have the signature m(node, state) -> node. They
115 should return the node if they do not with to change it.
116 state is a State instance.
117
118 The main entry point is morph(origTree) -> state, tree. origTree is not
119 modified, the return value can be flattened but can otherwise be severely
120 damaged.
121
122 For special effects, there's also earlyMorphers. These will be called
123 when traversal reaches the node for the first time. If these return
124 None, traversal continues as usual, if not, their result will be
125 added to the tree and *not* further traversed.
126 """
127 - def __init__(self, morphers, earlyMorphers={}):
128 self.morphers = morphers
129 self.earlyMorphers = earlyMorphers
130
142
144 newVal = self._traverse(value, state)
145 if not newVal is value:
146 return newVal
147
149 """iterates over key/value pairs changed by morphing value under
150 the key name.
151 """
152 if isinstance(value, (list, tuple)):
153 meth = self._getChangedForSeq
154 elif isinstance(value, nodes.ADQLNode):
155 meth = self._getChangedForNode
156 else:
157 return
158 newVal = meth(value, state)
159 if newVal is not None:
160 yield name, newVal
161
163 if node.type in self.earlyMorphers:
164 res = self.earlyMorphers[node.type](node, state)
165 if res is not None:
166 return res
167
168 state.nodeStack.append(node)
169 changes = []
170 for name, value in node.iterAttributes():
171 changes.extend(self._getChanges(name, value, state))
172
173 if changes:
174 newNode = node.change(**dict(changes))
175 newNode.original = node
176 else:
177 newNode = node
178
179
180
181
182 if hasattr(node, "OVERRIDE_RESULT"):
183 newNode = node.OVERRIDE_RESULT
184
185 popped = state.nodeStack.pop()
186 assert popped==node, "ADQL morphing node stack corruption"
187
188 curType = getattr(newNode, "type", None)
189 if curType in self.morphers:
190 handlerResult = self.morphers[curType](newNode, state)
191 assert handlerResult is not None, "ADQL morph handler returned None"
192 return handlerResult
193 return newNode
194
196 state = State()
197 res = self._traverse(tree, state)
198 return state, res
199