Package gavo :: Package adql :: Module common
[frames] | no frames]

Source Code for Module gavo.adql.common

  1  """ 
  2  Exceptions and helper functions for ADQL processing. 
  3  """ 
  4   
  5  #c Copyright 2008-2019, the GAVO project 
  6  #c 
  7  #c This program is free software, covered by the GNU GPL.  See the 
  8  #c COPYING file in the source distribution. 
  9   
 10   
 11  from gavo import utils 
 12   
13 -class Error(utils.Error):
14 """A base class for the exceptions from this module. 15 """ 16 # XXX todo: We should wrap pyparsing ParseExceptions as well. 17 pass
18
19 -class NotImplementedError(Error):
20 """is raised for features we don't (yet) support. 21 """
22
23 -class ColumnNotFound(Error, utils.NotFoundError):
24 """is raised if a column name cannot be resolved. 25 """
26 - def __init__(self, colName, hint=None):
27 utils.NotFoundError.__init__(self, colName, "column", "table metadata", 28 hint=hint)
29
30 -class TableNotFound(Error, utils.NotFoundError):
31 """is raised when a table name cannot be resolved. 32 """
33 - def __init__(self, tableName, hint=None):
34 utils.NotFoundError.__init__(self, tableName, "table", "table metadata", 35 hint=hint)
36
37 -class MorphError(Error):
38 """is raised when the expectations of the to-ADQL morphers are violated. 39 """ 40 pass
41
42 -class AmbiguousColumn(Error):
43 """is raised if a column name matches more than one column in a 44 compound query. 45 """
46
47 -class NoChild(Error):
48 """is raised if a node is asked for a non-existing child. 49 """
50 - def __init__(self, searchedType, toks):
51 self.searchedType, self.toks = searchedType, toks
52
53 - def __str__(self):
54 return "No %s child found in %s"%(self.searchedType, self.toks)
55
56 -class MoreThanOneChild(NoChild):
57 """is raised if a node is asked for a unique child but has more than 58 one. 59 """
60 - def __str__(self):
61 return "Multiple %s children found in %s"%(self.searchedType, 62 self.toks)
63
64 -class BadKeywords(Error):
65 """is raised when an ADQL node is constructed with bad keywords. 66 67 This is a development help and should not occur in production code. 68 """
69 - def __str__(self):
70 return "Bad keywords: "+utils.safe_str(self.args)
71 72
73 -class UfuncError(Error):
74 """is raised if something is wrong with a call to a user defined 75 function. 76 """
77
78 -class GeometryError(Error):
79 """is raised if something is wrong with a geometry. 80 """
81
82 -class RegionError(GeometryError):
83 """is raised if a region specification is in some way bad. 84 """
85
86 -class FlattenError(Error):
87 """is raised when something cannot be flattened. 88 """
89
90 -class IncompatibleTables(Error):
91 """is raised when the operands of a set operation are not deemed 92 compatible. 93 """
94
95 -class Absent(object):
96 """is a sentinel to pass as default to nodes.getChildOfType. 97 """
98 99
100 -def getUniqueMatch(matches, colName):
101 """returns the only item of matches if there is exactly one, raises an 102 appropriate exception if not. 103 """ 104 if len(matches)==1: 105 return matches[0] 106 elif not matches: 107 raise ColumnNotFound(colName) 108 else: 109 matches = set(matches) 110 if len(matches)!=1: 111 raise AmbiguousColumn(colName) 112 else: 113 return matches.pop()
114 115
116 -def computeCommonColumns(tableNode):
117 """returns a set of column names that only occur once in the result 118 table. 119 120 For a natural join, that's all column names occurring in all tables, 121 for a USING join, that's all names occurring in USING, else it's 122 an empty set. 123 124 """ 125 joinType = getattr(tableNode, "getJoinType", lambda: "CROSS")() 126 if joinType=="NATURAL": 127 # NATURAL JOIN, collect common names 128 return reduce(lambda a,b: a&b, 129 [set(t.fieldInfos.columns) for t in tableNode.joinedTables]) 130 elif joinType=="USING": 131 return set(tableNode.joinSpecification.usingColumns) 132 else: # CROSS join, comma, etc. 133 return set()
134 135
136 -class FieldInfoGetter(object):
137 """An abstract method to retrieve table metadata. 138 139 A subclass of this must be passed into adql.parseAnnotating. 140 Implementations must fill out the getInfosFor(tableName) method, 141 which must return a sequence of (column name, adql.FieldInfo) pairs 142 for the named table. 143 144 plain strings for table names will be normalised (lowercased). 145 """
146 - def __init__(self):
147 self.extraFieldInfos = {} 148 self.cache = {}
149
150 - def normalizeName(self, tableName):
151 if isinstance(tableName, basestring): 152 return tableName.lower() 153 elif hasattr(tableName, "getNormalized"): 154 # a nodes.TableName, presumably 155 return tableName.getNormalized() 156 else: 157 return tableName
158
159 - def __call__(self, tableName):
160 normalized = self.normalizeName(tableName) 161 if normalized in self.extraFieldInfos: 162 return self.extraFieldInfos[normalized] 163 164 if normalized not in self.cache: 165 self.cache[normalized] = self.getInfosFor(normalized) 166 if self.cache[normalized] is None: 167 raise utils.NotFoundError(str(normalized), "table", 168 "system and uploaded tables") 169 return self.cache[normalized]
170
171 - def addExtraFieldInfos(self, tableName, fieldInfos):
172 """adds field infos for tableName. 173 174 fieldInfos must be a sequence of (columnName, adql.FieldInfo) pairs. 175 176 Note that tableName is normalised to lowercase here. 177 """ 178 self.extraFieldInfos[self.normalizeName(tableName)] = fieldInfos
179
180 - def getInfosFor(self, tableName):
181 raise NotImplementedError("Abstract FieldInfoGetter used!")
182