1 """
2 Adding field infos to columns and other objects in an ADQL parse tree.
3
4 When we want to generate VOTables from ADQL queries, we must know types,
5 units, ucds, and the like, and we need to know STC information for
6 all columns in a query.
7
8 To do that, we traverse the parse tree postorder looking for nodes that have
9 an addFieldInfos method (note the plural). These then get called,
10 which causes one of the classes in adql.fieldinfos to be constructed
11 and assigned to the node's fieldInfos attribute. The source for
12 these infos is either an AnnotationContext (and thus typically
13 the user-supplied retrieveFieldInfos function) or derived annotations.
14 These are computed by the nodes themselves, using their addFieldInfo
15 (singular!) method.
16 """
17
18
19
20
21
22
23
24 import contextlib
25
26 from gavo import utils
27 from gavo import stc
28
29
30
31 -class AnnotationContext(object):
32 """An context object for the annotation process.
33
34 It is constructed with a FieldInfoGetter implementation
35 and an equivalence policy for STC objects.
36
37 It has errors and warnings attributes consisting of user-exposable
38 error strings accrued during the annotation process.
39
40 The annotation context also manages the namespaces for column reference
41 resolution. It maintains a stack of getters; is is maintained
42 using the customResolver context manager.
43
44 withTables, if passed, must be a sequence of already annotated
45 WithQuery objects.
46
47 Finally, the annotation context provides a ancestors attribute that,
48 at any time, gives a list of the current node's ancestor nodes.
49 """
50 - def __init__(self,
51 retrieveFieldInfos,
52 equivalencePolicy=stc.defaultPolicy):
53 self.retrieveFieldInfos = retrieveFieldInfos
54 self.policy = equivalencePolicy
55 self.withTables = {}
56 self.colResolvers = []
57 self.errors, self.warnings = [], []
58 self.ancestors = []
59
60 @contextlib.contextmanager
61 - def customResolver(self, getter):
62 """a context manager temporarily installing a different field info
63 getter.
64 """
65 self.colResolvers.append(getter)
66 try:
67 yield
68 finally:
69 self.colResolvers.pop()
70
71 - def addWithTable(self, withTable):
72 """adds a nodes.WithQuery to use in column resolution.
73
74 withTable must already be annotated for this to work.
75 """
76 self.retrieveFieldInfos.addExtraFieldInfos(
77 withTable.name,
78 withTable.fieldInfos.seq)
79
80 - def getFieldInfo(self, colName, tableName):
81 """returns the (colName, fieldInfo) for colName within tableName
82 in the current context.
83 """
84 res = self.colResolvers[-1](colName, tableName)
85 if res is None:
86 raise utils.ReportableError("Internal Error: resolver returned NULL for"
87 " %s.%s. Please report this to the gavo@ari.uni-heidelberg.de"
88 " together with the failed query."%(tableName, colName))
89 return res
90
93 """does the real tree traversal for annotate.
94 """
95 context.ancestors.append(node)
96 for c in node.iterNodeChildren():
97 _annotateTraverse(c, context)
98 context.ancestors.pop()
99 if hasattr(node, "addFieldInfos"):
100 node.addFieldInfos(context)
101
104 """adds annotations to all nodes wanting some.
105
106 This is done by a postorder traversal of the tree, identifying all
107 annotable objects.
108
109 context can be an AnnotationContext instance. You can also just
110 pass in a adql.FieldInfoGetter instance. In that case, annotation
111 runs with the default stc equivalence policy.
112
113 The function returns the context used in any case.
114 """
115 if not isinstance(context, AnnotationContext):
116 context = AnnotationContext(context)
117
118 for wt in node.withTables:
119 _annotateTraverse(wt, context)
120 context.addWithTable(wt)
121
122 _annotateTraverse(node, context)
123 return context
124
127 """dumps an ADQL parse tree, giving the computed annotations.
128
129 For debugging.
130 """
131 import pprint
132 def traverse(node):
133 res = []
134 if hasattr(node, "fieldInfo"):
135 res.append("%s <- %s"%(node.type, repr(node.fieldInfo)))
136 if hasattr(node, "fieldInfos"):
137 res.append("%s -- %s"%(node.type, repr(node.fieldInfos)))
138 res.extend(filter(None, [traverse(child) for child in
139 node.iterNodeChildren()]))
140 if len(res)==1:
141 return res[0]
142 else:
143 return res
144 pprint.pprint(traverse(tree))
145