Package gavo :: Package utils :: Module typeconversions
[frames] | no frames]

Source Code for Module gavo.utils.typeconversions

  1  """ 
  2  Conversions between type systems. 
  3   
  4  The DC software has to deal with a quite a few type systems (see 
  5  base.typesystems). In general, we keep metadata in the SQL type system; 
  6  in particular, column's and param's type attribute takes values in that. 
  7   
  8  In fact, we use a couple of extensions: 
  9   
 10          - file -- this corresponds to a file upload from the web (i.e., a pair 
 11                  (filename, file object)).  It would be conceivable to turn this into 
 12                  blobs at some point, but right now we simply don't touch it. 
 13          - vexpr-float, -text, -date, -mjd -- vizier-like expressions coming in from 
 14                  the web.  These are always strings. 
 15          - raw -- handed right through, whatever it is.  For target formats that 
 16                  can't do this, usually strings are used. 
 17          - unicode -- this is TEXT in the database, but while normal text will 
 18            be rendered as byte strings in VOTables (with non-ASCII-characters 
 19            replaced by ?), unicode will become an array of unicodeChars. 
 20   
 21  This module contains a base class and the VOTable type system conversion, 
 22  as the VOTable module (that should not depend on base) depends on it. 
 23  The remaining actual converters are in base.typesystems, as they may depend 
 24  on details of base.  Even the SQL converters should be taken from there 
 25  when code can rely on gavo.base; this module should be considered an  
 26  implementation detail. 
 27  """ 
 28   
 29  #c Copyright 2008-2019, the GAVO project 
 30  #c 
 31  #c This program is free software, covered by the GNU GPL.  See the 
 32  #c COPYING file in the source distribution. 
 33   
 34   
 35  # XXX TODO: Think how this can be "inverted" by just defining types and 
 36  # collecting all their aspects in a single class 
 37   
 38  import re 
 39   
 40  from gavo.utils import excs 
 41   
 42   
43 -class ConversionError(excs.Error):
44 pass
45 46
47 -class FromSQLConverter(object):
48 """is an abstract base class for type converters from the SQL type system. 49 50 Implementing classes have to provide a dict simpleMap mapping sql type 51 strings to target types, and a method mapComplex that receives a type 52 and a length (both strings, derived from SQL array types) and either 53 returns None (no matching type) or the target type. 54 55 Implementing classes should also provide a typeSystem attribute giving 56 a short name of the type system they convert to. 57 """ 58 _charTypes = set(["character varying", "varchar", "character", "char"]) 59
60 - def convert(self, sqlType):
61 res = None 62 if sqlType in self.simpleMap: 63 res = self.simpleMap[sqlType] 64 else: 65 mat = re.match(r"(.*)[[(](\d+|\*|)[])]", sqlType) 66 if mat: 67 res = self.mapComplex(mat.group(1), mat.group(2)) 68 if res is None: 69 if sqlType=="raw": 70 return "raw" 71 raise ConversionError("No %s type for %s"%(self.typeSystem, sqlType)) 72 return res
73
74 - def mapComplex(self, type, length):
75 return
76 77
78 -class ToVOTableConverter(FromSQLConverter):
79 typeSystem = "VOTable" 80 81 simpleMap = { 82 "smallint": ("short", None, None), 83 "integer": ("int", None, None), 84 "bigint": ("long", None, None), 85 "real": ("float", None, None), 86 "boolean": ("boolean", None, None), 87 "double precision": ("double", None, None), 88 "text": ("char", "*", None), 89 "char": ("char", "1", None), # Note: "1" is TOPCAT workaround of 2018. 90 "date": ("char", "*", None), 91 "timestamp": ("char", "19", "timestamp"), 92 "time": ("char", "*", None), 93 "box": ("double", "*", None), 94 "vexpr-mjd": ("char", "*", None), 95 "vexpr-string": ("char", "*", None), 96 "vexpr-date": ("char", "*", None), 97 "vexpr-float": ("char", "*", None), 98 "file": ("bytea", "*", None), # this is for (lame) metadata generation 99 "pql-float": ("char", "*", None), 100 "pql-string": ("char", "*", None), 101 "pql-date": ("char", "*", None), 102 "pql-int": ("char", "*", None), 103 "pql-upload": ("char", "*", None), # (the upload parameter) 104 "raw": ("unsignedByte", "*", None), 105 "bytea": ("unsignedByte", None, None), 106 "spoint": ("double", "2", "point"), 107 "scircle": ("double", "3", "circle"), 108 "spoly": ("double", "*", "polygon"), 109 "smoc": ("char", "*", "moc"), 110 "sbox": ("double", "4", "x:box"), 111 "unicode": ("unicodeChar", "*", None), 112 "int4range": ("int", "2", "interval"), 113 } 114
115 - def mapComplex(self, type, length):
116 if length=='': 117 length = '*' 118 119 if type in self._charTypes: 120 return "char", length, None 121 122 # consequence of TOPCAT workaround as of 2018 123 if type=="char" and length=='1': 124 length = None 125 126 if length is not None: 127 # Special handling for arrays of something 128 if type=="bytea": 129 return ("unsignedByte", '*', None) 130 # we may leave a leading * there, which upstream would have 131 # to fix (oh, madness) 132 133 t, l, xtype = self.simpleMap[type] 134 if l is None: 135 newLength = length 136 else: 137 newLength = "%sx%s"%(l, length) 138 139 return (t, newLength, xtype) 140 141 raise NotImplementedError( 142 "VOTable mapComplex cannot handle %s[%s]"%(type, length))
143 144
145 -class FromVOTableConverter(object):
146 typeSystem = "db" 147 148 simpleMap = { 149 ("short", '1'): "smallint", 150 ("int", '1'): "integer", 151 ("long", '1'): "bigint", 152 ("float", '1'): "real", 153 ("boolean", '1'): "boolean", 154 ("double", '1'): "double precision", 155 ("char", "*"): "text", 156 ("char", '1'): "char", 157 ("unsignedByte", '1'): "smallint", 158 ("raw", '1'): "raw", 159 } 160 161 xtypeMap = { 162 "adql:POINT": "spoint", 163 "adql:REGION": "spoly", 164 "adql:TIMESTAMP": "timestamp", 165 "timestamp": "timestamp", 166 "point": "spoint", 167 "circle": "scircle", 168 "polygon": "spoly", 169 "x:box": "sbox", 170 } 171
172 - def convert(self, type, arraysize, xtype=None):
173 if self.xtypeMap.get(xtype): 174 return self.xtypeMap[xtype] 175 if arraysize=="1" or arraysize=="" or arraysize is None: 176 arraysize = "1" 177 if (type, arraysize) in self.simpleMap: 178 return self.simpleMap[type, arraysize] 179 else: 180 return self.mapComplex(type, arraysize)
181
182 - def mapComplex(self, type, arraysize):
183 if arraysize=="*": 184 arraysize = "" 185 if type=="char": 186 return "text" 187 if type=="unicodeChar": 188 return "unicode" 189 if type=="unsignedByte" and arraysize!="1": 190 return "bytea[]" 191 if (type, '1') in self.simpleMap: 192 return "%s[%s]"%(self.simpleMap[type, '1'], arraysize) 193 raise ConversionError("No SQL type for %s, %s"%(type, arraysize))
194 195 196 sqltypeToVOTable = ToVOTableConverter().convert 197 voTableToSQLType = FromVOTableConverter().convert 198