1 """
2 FieldInfos are collections of inferred metadata on the columns present
3 within ADQL relations.
4
5 In generation, this module distinguishes between query-like (select...)
6 and table-like (from table references) field infos. The functions
7 here are called from the addFieldInfo methods of the respective
8 nodes classes.
9 """
10
11
12
13
14
15
16
17 import weakref
18
19
20
21
22
23 from gavo.adql import common
26 """returns true when table could be referred to by toName.
27
28 This means that either the name matches or toName is table's original
29 name.
30
31 toName is a qualified name (i.e., including schema), table is some
32 node that has a tableName.
33 """
34 if not hasattr(table, "tableName"):
35 return toName==""
36
37 return (table.tableName.qName==toName.qName
38 or (
39 table.originalTable
40 and
41 table.originalTable==toName.qName))
42
45 """
46 A base class for field annotations.
47
48 Subclasses of those are attached to physical tables, joins, and
49 subqueries.
50
51 The information on columns is kept in two places:
52
53 - seq -- a sequence of attributes of the columns in the
54 order in which they are selected
55 - columns -- maps column names to attributes or None if a column
56 name is not unique. Column names are normalized by lowercasing here
57 (which, however, does not affect L{utils.QuotedName}s).
58
59 A FieldInfos object is instanciated with the object it will annotate,
60 and the annotation (i.e., setting of the fieldInfos attribute on
61 the parent) will happen during instanciation.
62 """
68
70 return "<Column information %s>"%(repr(self.seq))
71
83
85 """adds a new visible column to this info.
86
87 This entails both entering it in self.columns and in self.seq.
88 """
89 label = label.lower()
90 if label in self.columns:
91 if self.columns[label]!=info:
92 self.columns[label] = None
93 else:
94 self.columns[label] = info
95
96
97
98
99
100
101
102
103 if info.userData and getattr(info.userData[0], "originalName", None):
104 col = info.userData[0]
105 self.columns[col.originalName] = info
106
107 self.seq.append((label, info))
108
110 """returns a FieldInfo object for colName.
111
112 Unknown columns result in a ColumnNotFound exception.
113
114 refName is ignored here; we may check that it's identical with
115 parent's name later.
116 """
117 colName = colName.lower()
118 fi = self.columns.get(colName, common.Absent)
119 if fi is common.Absent:
120 raise common.ColumnNotFound(colName)
121 if fi is None:
122
123 if refName is not None:
124 return self.locateTable(refName).getFieldInfo(colName)
125 raise common.AmbiguousColumn(colName)
126 return fi
127
129 """raises an IncompatibleTables if the FieldInfos other
130 have different length or names from self.
131 """
132 if len(self.seq)!=len(other.seq):
133 raise common.IncompatibleTables("Operands in set operation"
134 " have differing result tuple lengths.", "query")
135
136 for ownCol, otherCol in zip(self.seq, other.seq):
137 if ownCol[0]!=otherCol[0]:
138 raise common.IncompatibleTables("Operands if set operation"
139 " have differing names. First differing name: %s vs. %s"%(
140 ownCol[0], otherCol[0]))
141
144 """FieldInfos coming from something that's basically a table in the DB.
145
146 This includes joins.
147
148 To instanciate those, use the makeForNode class method below.
149 """
150
151 @classmethod
153 """returns a TableFieldInfos instance for an ADQL tableNode.
154
155 context is an AnnotationContext.
156
157 Whatever tableNode actually is, it needs an originalTable
158 attribute which is used to retrieve the column info.
159 """
160 result = cls(tableNode, context)
161
162 if tableNode.originalTable:
163
164 for colName, fieldInfo in context.retrieveFieldInfos(
165 tableNode.originalTable):
166 result.addColumn(colName, fieldInfo)
167
168
169
170 commonColumns = common.computeCommonColumns(tableNode)
171 emittedCommonColumns = set()
172 for jt in getattr(tableNode, "joinedTables", ()):
173 for label, info in jt.fieldInfos.seq:
174 if label in commonColumns:
175 if label not in emittedCommonColumns:
176 result.addColumn(label, info)
177 emittedCommonColumns.add(label)
178 else:
179 result.addColumn(label, info)
180
181
182 with context.customResolver(result.getFieldInfo):
183 _annotateNodeRecurse(tableNode, context)
184 return result
185
188
191 """helps QueryFieldInfos.
192 """
193 for c in node.iterNodeChildren():
194 _annotateNodeRecurse(c, context)
195 if hasattr(node, "addFieldInfo") and node.fieldInfo is None:
196 node.addFieldInfo(context)
197
200 """FieldInfos inferred from a FROM clause.
201
202 To instanciate those, use the makeForNode class method below.
203 """
204
205
206
207
208
209 enclosingQuery = None
210
211
212 @classmethod
241
243 """returns the enclosing query specification if this is a subquery within
244 a where clause.
245 """
246 ancs = context.ancestors
247 index = len(ancs)-1
248 while index>=0:
249 if ancs[index].type=="whereClause":
250 return ancs[index-1]
251 index -= 1
252
254 self.subTables = list(
255 queryNode.fromClause.tableReference.getAllTables())
256 self.tableReference = queryNode.fromClause.tableReference
257
258
259
260 encQS = self._getEnclosingQuery(context)
261 if encQS:
262 self.subTables.append(encQS)
263 self.subTables.extend(
264 encQS.fromClause.tableReference.getAllTables())
265 self.enclosingQuery = encQS
266
268 """returns a field info for colName from anything in the from clause.
269
270 That is, the columns in the select clause are ignored. Use this to
271 resolve expressions from the queries' select clause.
272
273 See getFieldInfo for refName
274 """
275 colName = colName.lower()
276 matched = []
277 if refName is None:
278
279 subCols = self.tableReference.fieldInfos.columns
280 if colName in subCols and subCols[colName]:
281 matched.append(subCols[colName])
282 if self.enclosingQuery:
283 subCols = (self.enclosingQuery.fromClause.
284 tableReference.fieldInfos.columns)
285 if colName in subCols and subCols[colName]:
286 matched.append(subCols[colName])
287
288 else:
289
290 subCols = self.locateTable(refName).fieldInfos.columns
291 if colName in subCols and subCols[colName]:
292 matched.append(subCols[colName])
293
294
295 return common.getUniqueMatch(matched, colName)
296
298 """returns a field info for colName in self or any tables this
299 query takes columns from.
300
301 To do that, it collects all fields of colName in self and subTables and
302 returns the matching field if there's exactly one. Otherwise, it
303 will raise ColumnNotFound or AmbiguousColumn.
304
305 If the node.TableName instance refName is given, the search will be
306 restricted to the matching tables.
307 """
308 ownMatch = None
309 if refName is None:
310 ownMatch = self.columns.get(colName, None)
311 if ownMatch:
312 return ownMatch
313 else:
314 return self.getFieldInfoFromSources(colName, refName)
315