Changeset 57
- Timestamp:
- 04/03/07 23:10:58
- Files:
-
- trunk/geniusql/dbtypes.py (modified) (5 diffs)
- trunk/geniusql/providers/msaccess.py (modified) (1 diff)
- trunk/geniusql/providers/postgres.py (modified) (11 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/geniusql/dbtypes.py
r56 r57 82 82 83 83 84 class FrozenStringType( DatabaseType):84 class FrozenStringType(FrozenByteType): 85 85 """DatabaseType for string types whose byte-length is not adjuatable. 86 86 … … 172 172 _initargs = DatabaseType._initargs + ("precision", "max_precision") 173 173 174 _precision = max_precision = 255174 _precision = max_precision = 53 175 175 def _get_precision(self): 176 176 return self._precision … … 192 192 max_precision = 53 193 193 194 _precision = 255194 _precision = 53 195 195 def _get_precision(self): 196 196 return self._precision 197 197 def _set_precision(self, value): 198 198 if value is not None: 199 if value > 1: 200 raise ValueError("%r is less than min precision 1." % value) 199 if value < 1: 200 raise ValueError("%r: %r is less than min precision 1." % 201 (self, value)) 201 202 elif value > self.max_precision: 202 raise ValueError("%r is greater than max precision %r." %203 ( value, self.max_precision))203 raise ValueError("%r: %r is greater than max precision %r." % 204 (self, value, self.max_precision)) 204 205 self._precision = value 205 206 precision = property(_get_precision, _set_precision, … … 279 280 280 281 281 class DateTimeType(DatabaseType): 282 283 default_pytype = datetime.datetime 282 class FixedRangeType(DatabaseType): 283 """DatabaseType which represents values within a fixed range.""" 284 284 _min = None 285 285 _max = None 286 286 287 287 _initargs = DatabaseType._initargs + ("_min", "_max") 288 289 def range(self): 290 """Return self.max - self.min.""" 291 return self._max - self._min 288 292 289 293 def min(self): … … 295 299 return self._max 296 300 301 class DateTimeType(FixedRangeType): 302 default_pytype = datetime.datetime 303 default_adapters = {datetime.datetime: adapters.SQL92TIMESTAMP} 304 305 class DateType(FixedRangeType): 306 default_pytype = datetime.date 307 default_adapters = {datetime.date: adapters.SQL92DATE} 308 309 class TimeType(DatabaseType): 310 default_pytype = datetime.time 311 default_adapters = {datetime.time: adapters.SQL92TIME} 312 313 314 class BooleanType(DatabaseType): 315 """DatabaseType which uses boolean values (True, False, Null).""" 316 default_pytype = bool 317 default_adapters = {bool: adapters.BOOLEAN} 318 trunk/geniusql/providers/msaccess.py
r56 r57 241 241 242 242 243 class YESNO(dbtypes. DatabaseType):243 class YESNO(dbtypes.BooleanType): 244 244 # "The BOOLEAN data types are logical types that result in either True 245 245 # or False values. They use 1 byte of memory for storage, and their 246 246 # synonyms are BIT, LOGICAL, LOGICAL1, and YESNO. A True value is 247 247 # equal to -1 while a False value is equal to 0." 248 default_adapters = {bool: adapters.BOOLEAN}248 pass 249 249 250 250 trunk/geniusql/providers/postgres.py
r56 r57 13 13 14 14 import geniusql 15 from geniusql import adapters, decompile, errors, typerefs 16 17 18 class PgTIMESTAMP(adapters.SQL92TIMESTAMP): 15 from geniusql import adapters, dbtypes, decompile, errors 16 17 18 # ------------------------------ Adapters ------------------------------ # 19 20 21 class PgDATE_Adapter(adapters.SQL92DATE): 22 19 23 def binary_op(self, op1, op, op2): 20 sql1 = op1.sql 21 if op1.value is not None and op2.pytype is datetime.date: 24 if op2.pytype is datetime.timedelta: 22 25 # Postgres assumes a "date" is actually midnight, so we 23 26 # need to drop any h:m:s from our interval. 24 sql1 = "interval '%s days'" % op1.value.days 25 return "(%s %s %s)" % (sql1, op, op2.sql) 26 27 28 class PgDATE(adapters.SQL92DATE): 29 30 def binary_op(self, op1, op, op2): 31 sql1, sql2 = op1.sql, op2.sql 32 if op2.pytype is datetime.timedelta and op2.value is not None: 33 # Postgres assumes a "date" is actually midnight, so we 34 # need to drop any h:m:s from our interval. 35 sql2 = "interval '%s days'" % op2.value.days 27 return "(%s %s date_trunc('day', %s))" % (op1.sql, op, op2.sql) 36 28 elif op2.pytype is datetime.date: 37 29 # Cast to timestamp to achieve an INTERVAL result 38 sql1 = "%s::TIMESTAMP" % sql1 39 sql2 = "%s::TIMESTAMP" % sql2 40 return "(%s %s %s)" % (sql1, op, sql2) 41 42 43 class INTERVAL(adapters.Adapter): 30 return "(%s::TIMESTAMP %s %s::TIMESTAMP)" % (op1.sql, op, op2.sql) 31 return "(%s %s %s)" % (op1.sql, op, op2.sql) 32 33 34 class PgINTERVAL_Adapter(adapters.Adapter): 44 35 45 36 def push(self, value): … … 85 76 86 77 return datetime.timedelta(days, s) 87 88 89 class BYTEA(adapters.Pickler): 78 79 def binary_op(self, op1, op, op2): 80 if op1.pytype == datetime.date: 81 # Postgres assumes a "date" is actually midnight, so we 82 # need to drop any h:m:s from our interval. 83 return "(date_trunc('day', %s) %s %s)" % (op1.sql, op, op2.sql) 84 return "(%s %s %s)" % (op1.sql, op, op2.sql) 85 86 87 class PgBYTEA_Adapter(adapters.Pickler): 90 88 91 89 def push(self, value): … … 100 98 101 99 102 class PgVARCHAR (adapters.SQL92VARCHAR):100 class PgVARCHAR_Adapter(adapters.SQL92VARCHAR): 103 101 104 102 def push(self, value): … … 121 119 122 120 123 class PgUNICODE (adapters.UNICODE):121 class PgUNICODE_Adapter(adapters.UNICODE): 124 122 125 123 def push(self, value): … … 142 140 143 141 144 class P ickler(adapters.Pickler):142 class PgPickler(adapters.Pickler): 145 143 146 144 def push(self, value): … … 169 167 170 168 171 class REAL(adapters.SQL92REAL):169 class PgFLOAT4_Adapter(adapters.SQL92REAL): 172 170 173 171 def push(self, value): … … 178 176 179 177 178 179 # ---------------------------- DatabaseTypes ---------------------------- # 180 181 # See http://www.postgresql.org/docs/8.1/static/datatype.html 182 183 # Not implemented here: 184 # 185 # box rectangular box in the plane 186 # cidr IPv4 or IPv6 network address 187 # circle circle in the plane 188 # inet IPv4 or IPv6 host address 189 # line infinite line in the plane 190 # lseg line segment in the plane 191 # macaddr MAC address 192 # path geometric path in the plane 193 # point geometric point in the plane 194 # polygon closed geometric path in the plane 195 # timetz time of day, including time zone 196 # timestamptz date and time, including time zone 197 198 199 class BOOLEAN(dbtypes.BooleanType): 200 """A logical Boolean (true/false).""" 201 synonyms = ['BOOL'] 202 203 204 class BYTEA(dbtypes.FrozenByteType): 205 """A type for binary data ("byte array").""" 206 default_adapters = {str: PgBYTEA_Adapter} 207 default_pytype = str 208 209 class BIT(dbtypes.AdjustableStringType): 210 """A fixed-length bit string""" 211 variable = False 212 default_adapters = {str: PgVARCHAR_Adapter, 213 unicode: PgUNICODE_Adapter, 214 None: PgPickler, 215 } 216 217 class VARBIT(dbtypes.AdjustableStringType): 218 """A variable-length bit string.""" 219 synonyms = ['BIT VARYING'] 220 variable = True 221 default_adapters = {str: PgVARCHAR_Adapter, 222 unicode: PgUNICODE_Adapter, 223 None: PgPickler, 224 } 225 226 class CHAR(dbtypes.AdjustableStringType): 227 """A fixed-length character string.""" 228 synonyms = ['CHARACTER'] 229 variable = False 230 default_adapters = {str: PgVARCHAR_Adapter, 231 unicode: PgUNICODE_Adapter, 232 None: PgPickler, 233 } 234 235 class VARCHAR(dbtypes.AdjustableStringType): 236 """A variable-length character string.""" 237 synonyms = ['CHARACTER VARYING'] 238 variable = True 239 default_adapters = {str: PgVARCHAR_Adapter, 240 unicode: PgUNICODE_Adapter, 241 None: PgPickler, 242 } 243 244 245 class ComparableInfinity(object): 246 247 def __cmp__(self, other): 248 if isinstance(other, self.__class__): 249 return False 250 return True 251 252 253 class TEXT(dbtypes.FrozenStringType): 254 """A variable-length character string.""" 255 # TEXT has no hard byte limit. 256 bytes = max_bytes = ComparableInfinity() 257 default_adapters = {str: PgVARCHAR_Adapter, 258 unicode: PgUNICODE_Adapter, 259 None: PgPickler, 260 } 261 262 263 264 # Float types. 265 # "In addition to ordinary numeric values, the floating-point types 266 # have several special values: Infinity, -Infinity, NaN." 267 268 class FLOAT4(dbtypes.InexactNumericType): 269 """A single precision floating-point number.""" 270 synonyms = ['REAL'] 271 precision = max_precision = 24 272 default_adapters = {float: PgFLOAT4_Adapter} 273 274 def cast(self, sql, totype): 275 """Cast the given SQL expression from this type to another.""" 276 if isinstance(totype, FLOAT4): 277 return sql 278 elif isinstance(totype, FLOAT8): 279 # Upcast to the larger type 280 return "(%s)::FLOAT8" % sql 281 raise TypeError("Could not cast %r from %r to %r." 282 % (sql, self, totype)) 283 284 class FLOAT8(dbtypes.InexactNumericType): 285 """A double precision floating-point number.""" 286 synonyms = ['DOUBLE PRECISION'] 287 precision = max_precision = 53 288 default_adapters = {float: adapters.SQL92DOUBLE} 289 290 def cast(self, sql, totype): 291 """Cast the given SQL expression from this type to another.""" 292 if isinstance(totype, FLOAT8): 293 return sql 294 elif isinstance(totype, FLOAT4): 295 # Downcast to the smaller type 296 return "(%s)::FLOAT4" % sql 297 raise TypeError("Could not cast %r from %r to %r." 298 % (sql, self, totype)) 299 300 301 class INT2(dbtypes.IntegerType): 302 """A signed two-byte integer.""" 303 synonyms = ['SMALLINT'] 304 signed = True 305 bytes = max_bytes = 2 306 307 class INT4(dbtypes.IntegerType): 308 """A signed four-byte integer.""" 309 310 # "The data types serial and bigserial are not true types, but merely 311 # a notational convenience for setting up unique identifier columns 312 # (similar to the AUTO_INCREMENT property supported by some other 313 # databases). 314 synonyms = ['INT', 'INTEGER', 'SERIAL', 'SERIAL4'] 315 316 signed = True 317 bytes = max_bytes = 4 318 319 class INT8(dbtypes.IntegerType): 320 synonyms = ['BIGINT', 'BIGSERIAL', 'SERIAL8'] 321 signed = True 322 bytes = max_bytes = 8 323 324 325 class TIMESTAMP(dbtypes.DateTimeType): 326 """A date and time.""" 327 pass 328 329 class DATE(dbtypes.DateType): 330 """A calendar date (year, month, day).""" 331 default_adapters = {datetime.date: PgDATE_Adapter} 332 333 class TIME(dbtypes.TimeType): 334 """A time of day.""" 335 pass 336 337 class INTERVAL(dbtypes.AdjustablePrecisionType): 338 """A time span.""" 339 default_adapters = {datetime.timedelta: PgINTERVAL_Adapter} 340 default_pytype = datetime.timedelta 341 342 343 class NUMERIC(dbtypes.ExactNumericType): 344 """An exact numeric of selectable precision.""" 345 346 # "In addition to ordinary numeric values, the numeric type allows the 347 # special value NaN, meaning "not-a-number". Any operation on NaN yields 348 # another NaN. When writing this value as a constant in a SQL command, 349 # you must put quotes around it, for example UPDATE table SET x = 'NaN'. 350 # On input, the string NaN is recognized in a case-insensitive manner." 351 352 synonyms = ['DECIMAL'] 353 precision = max_precision = 1000 354 355 356 class MONEY(dbtypes.FrozenPrecisionType): 357 """A currency amount.""" 358 default_pytype = dbtypes.ExactNumericType.default_pytype 359 360 361 180 362 class PgAdapterSet(adapters.AdapterSet): 181 363 182 def pytype_for_TIMESTAMPTZ(self, hints): 183 return datetime.datetime 184 def pytype_for_TIMETZ(self, hints): 185 return datetime.time 186 def pytype_for_INT2(self, hints): 187 return int 188 def pytype_for_INT4(self, hints): 189 return int 190 def pytype_for_INT8(self, hints): 191 return long 192 def pytype_for_FLOAT4(self, hints): 193 return float 194 def pytype_for_FLOAT8(self, hints): 195 return float 196 def pytype_for_MONEY(self, hints): 197 return float 198 def pytype_for_BYTEA(self, hints): 199 return str 200 def pytype_for_BPCHAR(self, hints): 201 return str 202 def pytype_for_INTERVAL(self, hints): 203 return datetime.timedelta 204 205 # Postgres has a wonderful interval type we can use. 206 def dbtype_for_datetime_timedelta(self, hints): 207 return "INTERVAL" 208 209 210 PgAdapterSet.defaults.update({(str, "BYTEA"): BYTEA, 211 (float, "REAL"): REAL, 212 (float, "FLOAT4"): REAL, 213 (str, "any"): PgVARCHAR, 214 (unicode, "any"): PgUNICODE, 215 (datetime.datetime, "any"): PgTIMESTAMP, 216 (datetime.date, "any"): PgDATE, 217 (datetime.timedelta, "INTERVAL"): INTERVAL, 218 }) 219 for k, v in PgAdapterSet.defaults.items(): 220 if issubclass(v, adapters.Pickler): 221 PgAdapterSet.defaults[k] = Pickler 222 del k, v 364 known_types = {'float': [FLOAT4, FLOAT8], 365 'varchar': [VARCHAR, TEXT, VARBIT, BYTEA], 366 'char': [CHAR, BIT], 367 'int': [INT2, INT4, INT8], 368 'bool': [BOOLEAN], 369 'datetime': [TIMESTAMP], 370 'date': [DATE], 371 'time': [TIME], 372 'timedelta': [INTERVAL], 373 'numeric': [NUMERIC], 374 'other': [MONEY], 375 } 376 223 377 224 378 … … 274 428 def builtins_utcnow(self): 275 429 return self.get_expr("NOW()", datetime.datetime) 430 431 def builtins_today(self): 432 neg, h, m = adapters.localtime_offset 433 sign = "" 434 if neg: 435 sign = "-" 436 offset = "%s:%s" % (h, m) 437 return self.get_expr("date_trunc('day', NOW() AT TIME ZONE INTERVAL '%s%s')" 438 % (sign, offset), datetime.date) 276 439 277 440 … … 368 531 dbtype, _ = self.db.fetch("SELECT typname, typlen FROM pg_type " 369 532 "WHERE oid = %s" % row[1], conn=conn) 370 if dbtype: 371 dbtype = dbtype[0][0].upper() 372 else: 373 dbtype = None 374 c = geniusql.Column(adapterset.python_type(dbtype), dbtype, 375 None, {}, row[2] in indices, 376 row[0], self.db.quote(row[0])) 533 dbtype = adapterset.canonicalize(dbtype[0][0].upper()) 534 535 c = geniusql.Column(dbtype.default_pytype, dbtype, 536 None, key=row[2] in indices, 537 name=row[0], qname=self.db.quote(row[0])) 377 538 c.adapter = adapterset.default(c.pytype, dbtype) 378 539 379 if dbtype in ( 'FLOAT4', 'FLOAT8'):380 c.hints['precision']= row[3]381 elif dbtype in ( 'MONEY', 'NUMERIC'):382 c.hints['precision']= (row[4] >> 16) & 65535383 c.hints['scale']= (row[4] & 65535) - 4540 if dbtype in (FLOAT4, FLOAT8): 541 dbtype.precision = row[3] 542 elif dbtype in (MONEY, NUMERIC): 543 dbtype.precision = (row[4] >> 16) & 65535 544 dbtype.scale = (row[4] & 65535) - 4 384 545 385 546 # Default value … … 404 565 c.default = None 405 566 406 if dbtype .startswith('BPCHAR') or dbtype.startswith('VARCHAR'):567 if dbtype is VARCHAR: 407 568 # See http://archives.postgresql.org/pgsql-interfaces/2004-07/msg00021.php 408 c.hints['bytes']= row[4] - 4569 dbtype.bytes = row[4] - 4 409 570 else: 410 571 bytes = row[3] 411 572 if bytes > 0: 412 c.hints['bytes'] = bytes 413 elif dbtype == 'TEXT': 414 c.hints['bytes'] = 0 573 dbtype.bytes = bytes 415 574 416 575 cols.append(c) … … 461 620 default = " DEFAULT %s" % default 462 621 463 return '%s %s%s' % (column.qname, column.dbtype , default)622 return '%s %s%s' % (column.qname, column.dbtype.ddl(), default) 464 623 465 624 def create_sequence(self, table, column):
