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