Changeset 135
- Timestamp:
- 08/13/07 06:14:24
- Files:
-
- trunk/geniusql/__init__.py (modified) (1 diff)
- trunk/geniusql/adapters.py (modified) (25 diffs)
- trunk/geniusql/astwalk.py (copied) (copied from branches/ast/geniusql/astwalk.py)
- trunk/geniusql/codewalk.py (modified) (2 diffs)
- trunk/geniusql/dbtypes.py (modified) (8 diffs)
- trunk/geniusql/deparse.py (copied) (copied from branches/ast/geniusql/deparse.py)
- trunk/geniusql/logic.py (modified) (9 diffs)
- trunk/geniusql/logicfuncs.py (modified) (2 diffs)
- trunk/geniusql/objects.py (modified) (12 diffs)
- trunk/geniusql/providers/ado.py (modified) (8 diffs)
- trunk/geniusql/providers/firebird.py (modified) (12 diffs)
- trunk/geniusql/providers/msaccess.py (modified) (6 diffs)
- trunk/geniusql/providers/mysql.py (modified) (8 diffs)
- trunk/geniusql/providers/postgres.py (modified) (7 diffs)
- trunk/geniusql/providers/psycopg.py (modified) (2 diffs)
- trunk/geniusql/providers/sqlite.py (modified) (8 diffs)
- trunk/geniusql/providers/sqlserver.py (modified) (4 diffs)
- trunk/geniusql/select.py (modified) (10 diffs)
- trunk/geniusql/test/benchmark.py (modified) (6 diffs)
- trunk/geniusql/test/test_logic.py (modified) (2 diffs)
- trunk/geniusql/test/zoo_fixture.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/geniusql/__init__.py
r134 r135 99 99 from geniusql import adapters 100 100 from geniusql import conns 101 from geniusql import de compile101 from geniusql import deparse 102 102 from geniusql import isolation 103 103 from geniusql import select trunk/geniusql/adapters.py
r134 r135 106 106 107 107 This must be performed in the Adapter (as opposed to the DatabaseType 108 or De compiler) in order to support custom transformations like our108 or Deparser) in order to support custom transformations like our 109 109 date example, above: 110 110 … … 139 139 140 140 op1 and op2 will be SQLExpression objects. 141 op will be a n index intoopcode.cmp_op. Use it to switch141 op will be a value from opcode.cmp_op. Use it to switch 142 142 based on the operator (since sqlop will be provider-specific). 143 143 sqlop will be the matching SQL for the given operator. … … 159 159 # sqlite 3 will return an int. 160 160 # This construction should handle both. 161 if value is None: 162 return None 161 163 return bool(int(value)) 162 164 … … 172 174 173 175 def pull(self, value, dbtype): 176 if value is None: 177 return None 174 178 return bool(value) 175 179 … … 188 192 189 193 def pull(self, value, dbtype): 194 if value is None: 195 return None 190 196 if isinstance(value, datetime.datetime): 191 197 return value … … 204 210 205 211 def pull(self, value, dbtype): 212 if value is None: 213 return None 206 214 # These are in order for a reason: datetime is a subclass of date! 207 215 if isinstance(value, datetime.datetime): … … 223 231 224 232 def pull(self, value, dbtype): 233 if value is None: 234 return None 225 235 if isinstance(value, datetime.time): 226 236 return value … … 251 261 252 262 def pull(self, value, dbtype): 263 if value is None: 264 return None 253 265 days, seconds = divmod(long(value), 86400) 254 266 return datetime.timedelta(int(days), int(seconds)) … … 265 277 266 278 def pull(self, value, dbtype): 279 if value is None: 280 return None 267 281 return float(value) 268 282 … … 287 301 288 302 def pull(self, value, dbtype): 303 if value is None: 304 return None 289 305 return self.pytype(value) 290 306 … … 315 331 316 332 def pull(self, value, dbtype): 333 if value is None: 334 return None 317 335 if isinstance(value, unicode): 318 336 return value.encode(dbtype.encoding) … … 324 342 325 343 def pull(self, value, dbtype): 344 if value is None: 345 return None 326 346 if isinstance(value, unicode): 327 347 return value … … 349 369 350 370 def pull(self, value, dbtype): 371 if value is None: 372 return None 351 373 # Coerce to str for pickle.loads restriction. 352 374 if isinstance(value, unicode): … … 357 379 358 380 381 def normalize_decimal(value): 382 """Return the given decimal value, normalized for SQL. 383 384 Normalization is by stripping trailing zeros after the decimal point. 385 This is critical to allow comparisons between "1", "1.", and "1.0". 386 """ 387 value = str(value) 388 if "." in value: 389 value = value.rstrip('0') 390 else: 391 value += "." 392 return "'%s'" % value 393 394 359 395 class number_to_TEXT(Adapter): 360 396 """Adapt a numeric Python type (int|long|float) to a TEXT dbtype.""" … … 371 407 372 408 def pull(self, value, dbtype): 409 if value is None: 410 return None 373 411 return self.pytype(value) 374 412 … … 386 424 387 425 op1 and op2 will be SQLExpression objects. 388 op will be a n index intoopcode.cmp_op.426 op will be a value from opcode.cmp_op. 389 427 sqlop will be the matching SQL for the given operator. 390 428 """ 391 if op not in ('=', '!='):429 if sqlop not in ('=', '!='): 392 430 raise TypeError("Numbers stored in TEXT columns cannot be " 393 431 "compared except for (in)equality.") 394 return "CStr(%s) %s CStr(%s)" % (op1.sql, op, op2.sql) 432 if op1.value is None: 433 val1 = op1.sql 434 else: 435 val1 = "'%s'" % op1.value 436 if op2.value is None: 437 val2 = op2.sql 438 else: 439 val2 = "'%s'" % op2.value 440 return "%s %s %s" % (val1, sqlop, val2) 395 441 396 442 … … 406 452 if issubclass(self.pytype, float): 407 453 # Make sure we get all 17 decimal digits. 408 return repr(value)454 return "'" + repr(value) + "'" 409 455 return str(value) 410 456 411 457 def pull(self, value, dbtype): 458 if value is None: 459 return None 412 460 return self.pytype(value) 413 461 … … 426 474 427 475 def pull(self, value, dbtype): 476 if value is None: 477 return None 428 478 # pywin32 build 205 began support for returning 429 479 # COM Currency objects as decimal objects. … … 437 487 if value is None: 438 488 return 'NULL' 439 return "'" + str(value) + "'"489 return normalize_decimal(value) 440 490 441 491 def binary_op(self, op1, op, sqlop, op2): … … 443 493 444 494 def compare_op(self, op1, op, sqlop, op2): 445 if op not in ('=', '!='):495 if sqlop not in ('=', '!='): 446 496 raise TypeError("Numbers stored in TEXT columns cannot be " 447 497 "compared except for (in)equality.") 448 return "CStr(%s) %s CStr(%s)" % (op1.sql, op, op2.sql) 498 if op1.value is None: 499 val1 = op1.sql 500 else: 501 val1 = normalize_decimal(op1.value) 502 if op2.value is None: 503 val2 = op2.sql 504 else: 505 val2 = normalize_decimal(op2.value) 506 return "%s %s %s" % (val1, sqlop, val2) 449 507 450 508 class decimal_to_SQL92REAL(Adapter): … … 462 520 463 521 def pull(self, value, dbtype): 522 if value is None: 523 return None 464 524 if isinstance(value, float): 465 525 value = repr(value) … … 478 538 return str(value) 479 539 def pull(self, value, dbtype): 540 if value is None: 541 return None 480 542 if (isinstance(value, basestring) or 481 543 (typerefs.decimal and … … 498 560 if value is None: 499 561 return 'NULL' 500 return "'" + str(value) + "'" 562 if not isinstance(value, typerefs.fixedpoint.FixedPoint): 563 value = typerefs.fixedpoint.FixedPoint(value) 564 return normalize_decimal(value) 501 565 502 566 def binary_op(self, op1, op, sqlop, op2): … … 504 568 505 569 def compare_op(self, op1, op, sqlop, op2): 506 if op not in ('=', '!='):570 if sqlop not in ('=', '!='): 507 571 raise TypeError("Numbers stored in TEXT columns cannot be " 508 572 "compared except for (in)equality.") 509 return "CStr(%s) %s CStr(%s)" % (op1.sql, op, op2.sql) 573 if op1.value is None: 574 val1 = op1.sql 575 else: 576 val1 = normalize_decimal(op1.value) 577 if op2.value is None: 578 val2 = op2.sql 579 else: 580 val2 = normalize_decimal(op2.value) 581 return "%s %s %s" % (val1, sqlop, val2) 510 582 511 583 class fixedpoint_to_SQL92REAL(Adapter): … … 518 590 519 591 def pull(self, value, dbtype): 592 if value is None: 593 return None 520 594 if isinstance(value, float): 521 595 value = repr(value) trunk/geniusql/codewalk.py
r134 r135 749 749 def walk(self): 750 750 self.stack = [] 751 self.newcode = []752 751 self.targets = {} 753 752 … … 1346 1345 self.flag = None 1347 1346 1347 1348 1348 del k, v 1349 1349 trunk/geniusql/dbtypes.py
r134 r135 48 48 def default_adapter(self, pytype): 49 49 """Return a default adapter instance for the given pytype, dbtype.""" 50 ptypes = [pytype, None] 51 for base in pytype.__bases__: 52 ptypes.append(base) 53 54 for p in ptypes: 55 if p in self.default_adapters: 56 return self.default_adapters[p] 57 58 raise TypeError("%s has no default adapter for %s. Looked for: %s" % 59 (self, pytype, ", ".join([repr(x) for x in ptypes]))) 50 # Use try for the common case where the pytype is in the dict. 51 try: 52 return self.default_adapters[pytype] 53 except KeyError: 54 defaults = self.default_adapters 55 if None in defaults: 56 return defaults[None] 57 for p in pytype.__bases__: 58 if p in defaults: 59 return defaults[p] 60 61 raise TypeError("%s has no default adapter for %s. Looked for: " 62 "%s, None, %s" % 63 (self, pytype, pytype, 64 ", ".join([repr(x) for x in ptype.__bases__]))) 60 65 61 66 … … 279 284 280 285 class SQL92SMALLINT(SQL92INTEGER): 281 # "The precision of SMALLINT shall be less than or 282 # equal to the precision of INTEGER." 286 """Base class for SQL 92 SMALLINT types. 287 288 "The precision of SMALLINT shall be less than or equal to the 289 precision of INTEGER." 290 """ 283 291 _bytes = max_bytes = 2 284 292 285 293 286 # "FLOAT specifies the data type approximate numeric, with binary287 # precision equal to or greater than the value of the specified288 # <precision>. The maximum value of <precision> is implementation-289 # defined. <precision> shall not be greater than this value."290 291 294 class SQL92FLOAT(AdjustablePrecisionType): 292 """Base class for SQL 92 FLOAT types.""" 295 """Base class for SQL 92 FLOAT types. 296 297 "FLOAT specifies the data type approximate numeric, with binary 298 precision equal to or greater than the value of the specified 299 <precision>. The maximum value of <precision> is implementation- 300 defined. <precision> shall not be greater than this value." 301 """ 293 302 default_pytype = float 294 303 295 304 296 # "REAL specifies the data type approximate numeric, with implementation-297 # defined precision."298 #299 # "DOUBLE PRECISION specifies the data type approximate numeric,300 # with implementation-defined precision that is greater than the301 # implementation-defined precision of REAL.302 303 305 class SQL92REAL(FrozenPrecisionType): 304 """Base class for SQL 92 REAL types.""" 306 """Base class for SQL 92 REAL types. 307 308 "REAL specifies the data type approximate numeric, with implementation- 309 defined precision." 310 """ 305 311 default_pytype = float 306 312 # By "precision" here, we mean the number of binary digits … … 322 328 323 329 class SQL92DOUBLE(SQL92REAL): 324 """Base class for SQL 92 DOUBLE PRECISION types.""" 330 """Base class for SQL 92 DOUBLE PRECISION types. 331 332 "DOUBLE PRECISION specifies the data type approximate numeric, 333 with implementation-defined precision that is greater than the 334 implementation-defined precision of REAL." 335 """ 325 336 _precision = max_precision = 53 326 337 default_adapters = {float: adapters.float_to_SQL92DOUBLE()} … … 334 345 335 346 336 # "NUMERIC specifies the data type exact numeric, with the decimal337 # precision and scale specified by the <precision> and <scale>."338 #339 # "DECIMAL specifies the data type exact numeric, with the decimal340 # scale specified by the <scale> and the implementation-defined341 # decimal precision equal to or greater than the value of the342 # specified <precision>."343 #344 # In terms of adaptation, there's not much difference between these.345 # Plenty of databases make them synonyms anyway.346 347 347 class SQL92DECIMAL(AdjustablePrecisionType): 348 348 """Base class for SQL 92 DECIMAL types. 349 350 For exact numeric types, 'precision' and 'scale' refer to decimal digits. 351 352 SQL 92 says: 353 "NUMERIC specifies the data type exact numeric, with the decimal 354 precision and scale specified by the <precision> and <scale>." 355 356 "DECIMAL specifies the data type exact numeric, with the decimal 357 scale specified by the <scale> and the implementation-defined 358 decimal precision equal to or greater than the value of the 359 specified <precision>." 360 361 In terms of adaptation, there's not much difference between these. 362 Plenty of databases make them synonyms anyway. 363 """ 349 364 scale = None 350 365 351 # For exact numeric types, 'precision' refers to decimal digits352 366 _precision = 18 353 367 max_precision = 1000 … … 423 437 424 438 class SQL99BOOLEAN(DatabaseType): 425 """DatabaseType which uses boolean values (True, False, Null).""" 426 # ANSI SQL:1999 says something like: 427 # "The data type boolean comprises the distinct truth values 428 # true and false. Unless prohibited by a NOT NULL constraint, 429 # the boolean data type also supports the unknown truth value as 430 # the null value. This specification does not make a distinction 431 # between the null value of the boolean data type and the unknown 432 # truth value that is the result of an SQL <predicate>, <search 433 # condition>, or <boolean value expression>; they may be used 434 # interchangeably to mean exactly the same thing." 439 """DatabaseType which uses boolean values (True, False, Null). 440 441 ANSI SQL:1999 says something like: 442 "The data type boolean comprises the distinct truth values 443 true and false. Unless prohibited by a NOT NULL constraint, 444 the boolean data type also supports the unknown truth value as 445 the null value. This specification does not make a distinction 446 between the null value of the boolean data type and the unknown 447 truth value that is the result of an SQL <predicate>, <search 448 condition>, or <boolean value expression>; they may be used 449 interchangeably to mean exactly the same thing." 450 """ 435 451 default_pytype = bool 436 452 default_adapters = {bool: adapters.bool_to_SQL99BOOLEAN()} … … 530 546 if precision <= dbtype.max_precision: 531 547 return dbtype(precision=precision) 532 return self.decimal_type(precision=precision )548 return self.decimal_type(precision=precision, scale=precision) 533 549 534 550 def dbtype_for_str(self, hints): … … 551 567 return self.known_types['bool'][0]() 552 568 569 # These are not adapter.push(bool) (which are used on one side of 570 # a comparison). Instead, these are used when the whole (sub)expression 571 # is True or False, e.g. "WHERE TRUE", or "WHERE TRUE and 'a'.'b' = 3". 572 expr_true = "TRUE" 573 expr_false = "FALSE" 574 575 # Because True and False get used so much in deparsing, 576 # we include a memoized function for obtaining SQLExpressions. 577 _adapted_bools = None 578 def bool_exprs(self, exprclass): 579 """Return SQLExpressions for expr_true, expr_false, comp_true and comp_false. 580 581 expr_true and expr_false are used when a (sub)expression is True or 582 False; for example, "WHERE TRUE and a.b = 3". 583 584 comp_true and comp_false are used in comparisons; for example, 585 "WHERE x.y == TRUE". 586 587 In many databases, these are equivalent. Microsoft SQL Server is a 588 notable exception, requiring "(1=1)" for expr_true, but "TRUE" for 589 comp_true. 590 """ 591 t = self.database_type(bool) 592 adapter = t.default_adapter(bool) 593 594 expr_true = exprclass(self.expr_true, '', t, bool) 595 expr_true.adapter = adapter 596 expr_true.value = True 597 598 expr_false = exprclass(self.expr_false, '', t, bool) 599 expr_false.adapter = adapter 600 expr_false.value = False 601 602 if self._adapted_bools is None: 603 comp_true = adapter.push(True, t) 604 comp_false = adapter.push(False, t) 605 self._adapted_bools = (comp_true, comp_false) 606 else: 607 comp_true, comp_false = self._adapted_bools 608 609 comp_true = exprclass(comp_true, '', t, bool) 610 comp_true.adapter = adapter 611 comp_true.value = True 612 613 comp_false = exprclass(comp_false, '', t, bool) 614 comp_false.adapter = adapter 615 comp_false.value = False 616 617 return (expr_true, expr_false, comp_true, comp_false) 618 553 619 def dbtype_for_datetime_datetime(self, hints): 554 620 return self.known_types['datetime'][0]() … … 564 630 # If your DB has an INTERVAL datatype, you should provide a 565 631 # native INTERVAL type. You'll also have to update the date 566 # arithmetic inside the de compiler and add a timedelta adapter.632 # arithmetic inside the deparser and add a timedelta adapter. 567 633 return self.known_types['timedelta'][0]() 568 634 except (KeyError, IndexError): trunk/geniusql/logic.py
r134 r135 184 184 185 185 186 from geniusql import codewalk 186 from geniusql import codewalk, astwalk 187 187 188 188 … … 244 244 245 245 class Expression(object): 246 """A filter for objects ."""246 """A filter for objects (an AST).""" 247 247 248 248 def __init__(self, func=None, kwtypes=None, earlybind=True): … … 260 260 """ 261 261 if func is None: 262 func = lambda x: True 263 self._load_func(func, False) 262 self.func = lambda *args, **kw: True 263 self.ast = astwalk.AST(ast.Const(True)) 264 self.ast.star_args = "args" 265 self.ast.dstar_args = "kwargs" 264 266 else: 265 self._load_func(func, earlybind) 267 self.func = func 268 lp = astwalk.LambdaParser(func, env=builtins, reduce=earlybind) 269 lp.walk() 270 self.ast = lp.ast 271 272 # Update func_globals so self.evaluate() works. 273 func.func_globals.update(builtins) 266 274 267 275 if kwtypes is None: … … 270 278 self.kwargs = {} 271 279 272 def _load_func(self, func, earlybind=True):273 # I can't believe this actually works (knock on wood).274 func.func_globals.update(builtins)275 if earlybind:276 # Early-bind as much as possible.277 binder = codewalk.EarlyBinder(func)278 self.func = binder.function()279 else:280 self.func = func281 282 280 def code(self): 283 281 """Return source code for self.func.""" 284 282 if hasattr(self, 'func'): 285 decom = codewalk.LambdaDecompiler(self.func, env=builtins)283 decom = astwalk.LambdaDeparser(self.ast, env=builtins) 286 284 return decom.code() 287 285 else: … … 336 334 else: 337 335 func, self.kwtypes, self.kwargs = state 336 338 337 # The most difficult thing about Expressions is unpickling. 339 338 # Any func_globals at the time of pickling are lost, so any … … 341 340 # such objects need to be injected into logic.builtins 342 341 # if you want them to be available here. 343 f = eval(func, builtins) 344 self._load_func(f) 342 self.func = eval(func, builtins) 343 lp = astwalk.LambdaParser(self.func, env=builtins, reduce=True) 344 lp.walk() 345 self.ast = lp.ast 345 346 346 347 def is_constant(self, value): … … 358 359 e = logic.Expression(lambda x: x.a == 3 and x.b == 1) 359 360 """ 360 co, names, consts = [], ['x', ], [None, ] 361 i = 0 362 for key, val in kwargs.iteritems(): 363 i += 1 364 names.append(key) 365 consts.append(val) 361 items = kwargs.items() 362 co = [] 363 kwlen = len(kwargs) 364 jump_target = (16 * kwlen) - 7 365 for i in xrange(1, kwlen + 1): 366 366 co += [124, 0, 0, 367 367 105, i, 0, 368 368 100, i, 0, 369 369 106, 2, 0, 370 111, 0, 0, 370 # Point all jump (111) instructions at len(co) - 4 371 111, jump_target - (((i - 1) * 16) + 12), 0, 371 372 1, 372 373 ] … … 376 377 co.append(83) 377 378 378 # Figure JUMP targets 379 for op in range(len(co)): 380 if co[op] == 111: 381 co[op + 1] = (len(co) - 4) - op 379 items = [('x', None)] + items 380 names, consts = zip(*items) 382 381 383 382 # Form code object and function. … … 386 385 # filename, name, firstlineno, lnotab[, freevars[, cellvars]]) 387 386 co = CodeType(1, 1, 2, 67, ''.join(map(chr, co)), 388 tuple(consts), tuple(names), ('x', ), 389 '', '<lambda>', 1, '') 387 consts, names, ('x', ), '', '<lambda>', 1, '') 390 388 func = FunctionType(co, {}) 391 389 return Expression(func, earlybind=False) trunk/geniusql/logicfuncs.py
r134 r135 64 64 """Late-bound datetime.datetime.now(). Taint this when early binding.""" 65 65 return _datetime.datetime.now() 66 now. bind_late = True66 now.irreducible = True 67 67 68 68 def utcnow(): 69 69 """Late-bound datetime.datetime.utcnow(). Taint this when early binding.""" 70 70 return _datetime.datetime.utcnow() 71 utcnow. bind_late = True71 utcnow.irreducible = True 72 72 73 73 def today(): 74 74 """Late-bound datetime.date.today(). Taint this when early binding.""" 75 75 return _datetime.date.today() 76 today. bind_late = True76 today.irreducible = True 77 77 78 78 def iscurrentweek(value): … … 82 82 else: 83 83 return False 84 iscurrentweek. bind_late = True84 iscurrentweek.irreducible = True 85 85 86 86 def count(values): trunk/geniusql/objects.py
r134 r135 21 21 from geniusql import dbtypes 22 22 from geniusql import conns 23 from geniusql import de compile23 from geniusql import deparse 24 24 from geniusql import isolation 25 25 from geniusql import logic … … 331 331 tpair = [(self.qname, self)] 332 332 restriction = logic.combine(restriction, kwargs) 333 de com = self.schema.db.decompiler(tpair, restriction,334 self.schema.db.typeset)335 ## de com.verbose = True336 code = de com.code()337 if de com.imperfect:333 dep = self.schema.db.deparser(tpair, restriction, 334 self.schema.db.typeset) 335 ## dep.verbose = True 336 code = dep.code() 337 if dep.imperfect: 338 338 raise ValueError("The given restriction could not safely be " 339 339 "translated to SQL.", restriction, code) … … 406 406 407 407 if parms: 408 w = self.id_clause(**kwargs) 408 409 sql = ('UPDATE %s SET %s WHERE %s;' % 409 (self.qname, ", ".join(parms), self.id_clause(**kwargs)))410 (self.qname, ", ".join(parms), w)) 410 411 self.schema.db.execute(sql) 411 412 … … 423 424 424 425 if parms: 426 w = self.whereclause(restriction, **kwargs) 425 427 sql = ('UPDATE %s SET %s WHERE %s;' % 426 (self.qname, ", ".join(parms), 427 self.whereclause(restriction, **kwargs))) 428 (self.qname, ", ".join(parms), w)) 428 429 self.schema.db.execute(sql) 429 430 430 431 def delete(self, **kwargs): 431 432 """Delete all rows which match the given identifier kwargs.""" 432 self.schema.db.execute('DELETE FROM %s WHERE %s;' % 433 (self.qname, self.id_clause(**kwargs))) 433 w = self.id_clause(**kwargs) 434 sql = 'DELETE FROM %s WHERE %s;' % (self.qname, w) 435 self.schema.db.execute(sql) 434 436 435 437 def delete_all(self, restriction=None, **kwargs): 436 438 """Delete all rows which match the given restriction.""" 437 439 w = self.whereclause(restriction, **kwargs) 438 self.schema.db.execute('DELETE FROM %s WHERE %s;' % (self.qname, w)) 440 sql = 'DELETE FROM %s WHERE %s;' % (self.qname, w) 441 self.schema.db.execute(sql) 439 442 440 443 def select(self, restriction=None, **kwargs): … … 456 459 dataset = self.schema.db.select((self, attrs, restriction, order), 457 460 limit=limit, strict=False) 458 if restriction and dataset.selector.imperfect: 461 if dataset.selector.imperfect: 462 if restriction is None: 463 raise ValueError("Could not generate perfect SQL.") 459 464 # Run a dummy object through our restriction before yielding. 460 465 for row in dataset: … … 770 775 771 776 typeset = dbtypes.DatabaseTypeSet() 772 de compiler = decompile.SQLDecompiler777 deparser = deparse.SQLDeparser 773 778 joinwrapper = select.TableWrapper 774 779 selectwriter = select.SelectWriter … … 910 915 911 916 sel = self.selectwriter(self, query) 912 data, _ = self.fetch(sel.sql(distinct, limit)) 917 sql = sel.sql(distinct, limit) 918 data, _ = self.fetch(sql) 913 919 914 920 d = Dataset(sel, data) … … 931 937 932 938 sel = self.selectwriter(self, query) 933 newtable = sel.result 934 935 # The selectwriter doesn't give the result table a name. 936 newtable.name = newtable.schema.table_name(name) 937 newtable.qname = self.quote(newtable.name) 938 939 newtable = sel.result_table(name) 939 940 # CREATE TABLE 940 941 newtable.schema[name] = newtable … … 950 951 data, _ = self.fetch(selsql) 951 952 953 output_keys = [names[0] for names in sel.output] 952 954 for row in Dataset(sel, data): 953 row = dict(zip( sel.output_keys, row))955 row = dict(zip(output_keys, row)) 954 956 # Run a dummy object through our restriction before inserting. 955 957 if not query.restriction(_ImperfectDummy(**row)): … … 958 960 newtable.insert(**row) 959 961 else: 962 qnames = [names[2] for names in sel.output] 960 963 sql = ("INSERT INTO %s (%s) %s" % 961 (newtable.qname, ", ".join( sel.output_list), selsql))964 (newtable.qname, ", ".join(qnames), selsql)) 962 965 self.execute(sql) 963 966 … … 993 996 self.dataset = dataset 994 997 self.dataiter = iter(dataset.data) 998 999 # pre-fetch cols and cache in an optimal format. 1000 c = dataset.selector.output_cols 1001 self.cols = [c[coldata[0]] for coldata in dataset.selector.output] 995 1002 996 1003 def __iter__(self): … … 1006 1013 under the assumption that the caller will do so itself. 1007 1014 """ 1008 d = self.dataset1009 1010 1015 raw_row = self.dataiter.next() 1011 1012 row = [] 1013 for i, colkey in enumerate(d.selector.output_keys): 1014 val = raw_row[i] 1015 col = d.selector.result[colkey] 1016 # Any column value can be None. Don't coerce it. 1017 if val is not None: 1018 val = col.adapter.pull(val, col.dbtype) 1019 row.append(val) 1020 1021 return row 1022 1016 return [col.adapter.pull(val, col.dbtype) for val, col 1017 in zip(raw_row, self.cols)] 1018 trunk/geniusql/providers/ado.py
r134 r135 31 31 32 32 import geniusql 33 from geniusql import adapters, conns, de compile, errors, select33 from geniusql import adapters, conns, deparse, errors, select 34 34 35 35 adOpenForwardOnly = 0 … … 73 73 74 74 def pull(self, value, dbtype): 75 if value is None: 76 return None 75 77 if isinstance(value, unicode): 76 78 # The value is a stringified NUMERIC of seconds. … … 115 117 116 118 def pull(self, value, dbtype): 119 if value is None: 120 return None 117 121 t = timedelta_from_com(value, self.epoch) 118 122 if t.days: … … 129 133 def pull(self, value, dbtype): 130 134 """Return a valid datetime.datetime from a COM date/time object.""" 135 if value is None: 136 return None 131 137 return datetime.datetime(value.year, value.month, value.day, 132 138 value.hour, value.minute, value.second, … … 167 173 168 174 def pull(self, value, dbtype): 175 if value is None: 176 return None 169 177 return datetime.date(value.year, value.month, value.day) 170 178 … … 202 210 203 211 204 class ADOSQLDe compiler(decompile.SQLDecompiler):212 class ADOSQLDeparser(deparse.SQLDeparser): 205 213 206 214 # --------------------------- Dispatchees --------------------------- # … … 216 224 def containedby(self, op1, op2): 217 225 self.imperfect = True 218 return de compile.SQLDecompiler.containedby(self, op1, op2)226 return deparse.SQLDeparser.containedby(self, op1, op2) 219 227 220 228 def builtins_icontainedby(self, op1, op2): … … 455 463 class ADODatabase(geniusql.Database): 456 464 457 de compiler = ADOSQLDecompiler465 deparser = ADOSQLDeparser 458 466 selectwriter = ADOSelectWriter 459 467 trunk/geniusql/providers/firebird.py
r134 r135 9 9 10 10 import geniusql 11 from geniusql import adapters, conns, dbtypes, de compile, errors, select, typerefs11 from geniusql import adapters, conns, dbtypes, deparse, errors, select, typerefs 12 12 from geniusql import isolation as _isolation 13 13 … … 45 45 46 46 def pull(self, value, dbtype): 47 if value is None: 48 return None 47 49 return self.pytype(value) 48 50 … … 95 97 96 98 def pull(self, value, dbtype): 99 if value is None: 100 return None 97 101 # TIMESTAMP - TIMESTAMP => DECIMAL(18, 9) Days + Fraction of day 98 102 # DATE - DATE => DECIMAL(9, 0) representing # of Days … … 140 144 class Firebird_decimal_adapter(adapters.decimal_to_SQL92DECIMAL): 141 145 def pull(self, value, dbtype): 146 if value is None: 147 return None 142 148 if isinstance(value, tuple): 143 149 return normalize(value, typerefs.decimal)[0] … … 146 152 class Firebird_decimal_Decimal_adapter(adapters.decimal_to_SQL92DECIMAL): 147 153 def pull(self, value, dbtype): 154 if value is None: 155 return None 148 156 if isinstance(value, tuple): 149 157 return normalize(value, typerefs.decimal.Decimal)[0] … … 152 160 class Firebird_fixedpoint_adapter(adapters.fixedpoint_to_SQL92DECIMAL): 153 161 def pull(self, value, dbtype): 162 if value is None: 163 return None 154 164 fp = typerefs.fixedpoint.FixedPoint 155 165 if isinstance(value, tuple): … … 172 182 class Firebird_int_adapter(adapters.int_to_SQL92INTEGER): 173 183 def pull(self, value, dbtype): 184 if value is None: 185 return None 174 186 if isinstance(value, tuple): 175 187 return normalize(value, int)[0] … … 178 190 class Firebird_long_adapter(adapters.int_to_SQL92INTEGER): 179 191 def pull(self, value, dbtype): 192 if value is None: 193 return None 180 194 if isinstance(value, tuple): 181 195 return normalize(value, long)[0] … … 184 198 class Firebird_number_to_SQL92DECIMAL(adapters.number_to_SQL92DECIMAL): 185 199 def pull(self, value, dbtype): 200 if value is None: 201 return None 186 202 if isinstance(value, tuple): 187 203 return normalize(value, self.pytype)[0] … … 389 405 class FirebirdTypeSet(dbtypes.DatabaseTypeSet): 390 406 407 # Firebird doesn't have true or false keywords. 408 expr_true = "1=1" 409 expr_false = "1=0" 410 391 411 known_types = {'float': [FLOAT, DOUBLE], 392 412 'varchar': [VARCHAR, BLOB], … … 416 436 417 437 418 class FirebirdSQLDecompiler(decompile.SQLDecompiler): 419 420 # Firebird doesn't have true or false keywords. 421 bool_true = "1=1" 422 bool_false = "1=0" 438 class FirebirdSQLDeparser(deparse.SQLDeparser): 423 439 424 440 like_escapes = [("\\", r"\\"), ("%", r"\%"), ("_", r"\_")] … … 892 908 893 909 selectwriter = FirebirdSelectWriter 894 de compiler = FirebirdSQLDecompiler910 deparser = FirebirdSQLDeparser 895 911 896 912 typeset = FirebirdTypeSet() trunk/geniusql/providers/msaccess.py
r134 r135 94 94 95 95 def pull(self, value, dbtype): 96 if value is None: 97 return None 96 98 if isinstance(value, tuple): 97 99 # See http://groups.google.com/group/comp.lang.python/ … … 104 106 105 107 def pull(self, value, dbtype): 108 if value is None: 109 return None 106 110 # pywin32 build 205 began support for returning 107 111 # COM Currency objects as decimal objects. … … 118 122 119 123 def pull(self, value, dbtype): 124 if value is None: 125 return None 120 126 if isinstance(value, typerefs.decimal.Decimal): 121 127 value = str(value) … … 142 148 """ 143 149 # ADO comparison operators for strings are case-insensitive. 144 if op < 6: 145 # ('<', '<=', '==', '!=', '>', '>=') 150 if op in ('<', '<=', '==', '!=', '>', '>='): 146 151 # Some operations on strings can be emulated with the 147 152 # StrComp function. Oddly enough, "StrComp(x, y) op 0" … … 341 346 342 347 343 class MSAccessDecompiler(ado.ADOSQLDecompiler): 344 sql_cmp_op = ('<', '<=', '=', '<>', '>', '>=', 'in', 'not in') 348 class MSAccessDeparser(ado.ADOSQLDeparser): 349 sql_cmp_op = {'<': '<', 350 '<=': '<=', 351 '==': '=', 352 '!=': '<>', 353 '>': '>', 354 '>=': '>=', 355 'in': 'in', 356 'not in': 'not in', 357 } 345 358 346 359 like_escapes = [("[", "[[]"), ("%", "[%]"), ("_", "[_]"), … … 645 658 class MSAccessDatabase(ado.ADODatabase): 646 659 647 de compiler = MSAccessDecompiler660 deparser = MSAccessDeparser 648 661 typeset = MSAccessTypeSet() 649 662 connectionmanager = MSAccessConnectionManager trunk/geniusql/providers/mysql.py
r134 r135 17 17 18 18 import geniusql 19 from geniusql import adapters, dbtypes, conns, de compile, errors, providers, typerefs19 from geniusql import adapters, dbtypes, conns, deparse, errors, providers, typerefs 20 20 21 21 … … 39 39 if op2.dbtype in (FLOAT, DOUBLE): 40 40 # MySQL provides no reliable method to compare floats in SQL. 41 # Raising TypeError will tell the SQL de compiler to mark float41 # Raising TypeError will tell the SQL deparser to mark float 42 42 # comparisons as imperfect (so they'll be done in Python). 43 43 raise TypeError("MySQL cannot reliably compare floats: %s" % sql) … … 242 242 class MySQLTypeSet(dbtypes.DatabaseTypeSet): 243 243 244 # TRUE and FALSE only work with 4.1 or better. 245 expr_true = "1" 246 expr_false = "0" 247 244 248 known_types = {'float': [FLOAT, DOUBLE], 245 249 # MySQL VARBINARY/BLOBs will do case-sensitive comparisons. … … 259 263 def __init__(self, version): 260 264 self.version = version 265 266 if self.version >= providers.Version("4.1.1"): 267 # TRUE and FALSE only work with 4.1 or better. 268 self.expr_true = "TRUE" 269 self.expr_false = "FALSE" 261 270 if self.version >= providers.Version("5.0.3"): 262 271 self.known_types['numeric'] = [DECIMAL503] … … 267 276 268 277 269 class MySQLDecompiler(decompile.SQLDecompiler): 270 271 # TRUE and FALSE only work with 4.1 or better. 272 bool_true = "1" 273 bool_false = "0" 278 class MySQLDeparser(deparse.SQLDeparser): 274 279 275 280 like_escapes = [("%", r"\%"), ("_", r"\_")] … … 279 284 280 285 281 class MySQLDecompiler411(MySQLDecompiler): 282 283 # TRUE and FALSE only work with 4.1 or better. 284 bool_true = "TRUE" 285 bool_false = "FALSE" 286 class MySQLDeparser411(MySQLDeparser): 286 287 287 288 # Before MySQL 4.1.1, BINARY comparisons could use UPPER() … … 583 584 if k in connargs]) 584 585 585 self.de compiler = MySQLDecompiler586 self.deparser = MySQLDeparser 586 587 587 588 # Get the version string from MySQL, to see if we need 588 # a different de compiler.589 # a different deparser. 589 590 conn = self.connections._get_conn(master=True) 590 591 rowdata, cols = self.fetch("SELECT version();", conn) … … 593 594 self._version = providers.Version(v) 594 595 595 # de compiler596 # deparser 596 597 if self._version > providers.Version("4.1.1"): 597 self.de compiler = MySQLDecompiler411598 self.deparser = MySQLDeparser411 598 599 599 600 self.typeset = MySQLTypeSet(self._version) trunk/geniusql/providers/postgres.py
r134 r135 13 13 14 14 import geniusql 15 from geniusql import adapters, dbtypes, de compile, errors15 from geniusql import adapters, dbtypes, deparse, errors 16 16 17 17 … … 44 44 45 45 def pull(self, value, dbtype): 46 if value is None: 47 return None 46 48 if isinstance(value, datetime.timedelta): 47 49 return value … … 121 123 122 124 def pull(self, value, dbtype): 125 if value is None: 126 return None 123 127 # Unescape octal sequences 124 128 value = unescape_oct.sub(replace_unoct, value) 125 if isinstance(value, unicode): 126 return value.encode(dbtype.encoding) 127 else: 128 return str(value) 129 130 # Return values don't ever seem to be unicode 131 ## if isinstance(value, unicode): 132 ## return value.encode(dbtype.encoding) 133 ## else: 134 return value 129 135 130 136 … … 144 150 145 151 def pull(self, value, dbtype): 152 if value is None: 153 return None 146 154 # Unescape octal sequences 147 155 value = unescape_oct.sub(replace_unoct, value) 148 if isinstance(value, unicode): 149 return value 150 else: 151 return unicode(value, dbtype.encoding) 156 # Return values don't ever seem to be unicode 157 ## if isinstance(value, unicode): 158 ## return value 159 ## else: 160 return unicode(value, dbtype.encoding) 152 161 153 162 … … 169 178 170 179 def pull(self, value, dbtype): 180 if value is None: 181 return None 171 182 # Unescape octal sequences 172 183 value = unescape_oct.sub(replace_unoct, value) 173 184 174 # Coerce to str for pickle.loads restriction. 175 if isinstance(value, unicode): 176 value = value.encode(dbtype.encoding) 177 else: 178 value = str(value) 185 # Return values don't ever seem to be unicode. 186 ## # Coerce to str for pickle.loads restriction. 187 ## if isinstance(value, unicode): 188 ## value = value.encode(dbtype.encoding) 189 ## else: 190 ## value = str(value) 179 191 return pickle.loads(value) 180 192 … … 377 389 378 390 379 class PgDe compiler(decompile.SQLDecompiler):391 class PgDeparser(deparse.SQLDeparser): 380 392 381 393 like_escapes = [("%", r"\\%"), ("_", r"\\_")] … … 665 677 encoding = 'SQL_ASCII' 666 678 667 de compiler = PgDecompiler679 deparser = PgDeparser 668 680 schemaclass = PgSchema 669 681 typeset = PgTypeSet() trunk/geniusql/providers/psycopg.py
r134 r135 84 84 cursor = conn.cursor() 85 85 try: 86 cursor.execute(sql) 86 try: 87 cursor.execute(sql) 88 except _psycopg.ProgrammingError, x: 89 x.args += (sql,) 90 raise 87 91 finally: 88 92 cursor.close() … … 104 108 cursor = conn.cursor() 105 109 try: 106 cursor.execute(sql) 110 try: 111 cursor.execute(sql) 112 except _psycopg.ProgrammingError, x: 113 x.args += (sql,) 114 raise 107 115 data = cursor.fetchall() 108 116 coldefs = cursor.description trunk/geniusql/providers/sqlite.py
r134 r135 5 5 6 6 import geniusql 7 from geniusql import adapters, dbtypes, conns, de compile, errors, providers, select, typerefs7 from geniusql import adapters, dbtypes, conns, deparse, errors, providers, select, typerefs 8 8 from geniusql import isolation as _isolation 9 9 … … 38 38 _autoincrement_support = (_version >= providers.Version([3, 1, 0])) 39 39 _cast_support = (_version >= providers.Version([3, 2, 3])) 40 40 _trim_support = (_version >= providers.Version([3, 3, 14])) 41 41 42 42 # ------------------------------ Adapters ------------------------------ # … … 122 122 123 123 124 125 124 # --------------------------- Database Types --------------------------- # 126 125 … … 143 142 None: SQLite_Pickler(), 144 143 }) 144 145 145 146 146 class REAL(dbtypes.SQL92DOUBLE): … … 199 199 'other': [], 200 200 } 201 202 203 204 class SQLiteDecompiler(decompile.SQLDecompiler): 205 206 bool_true = "1" 207 bool_false = "0" 201 202 # These are not adapter.push(bool) (which are used on one side of 203 # a comparison). Instead, these are used when the whole (sub)expression 204 # is True or False, e.g. "WHERE TRUE", or "WHERE TRUE and 'a'.'b' = 3". 205 expr_true = "1" 206 expr_false = "0" 207 208 209 210 class SQLiteDeparser(deparse.SQLDeparser): 208 211 209 212 like_escapes = [("%", "\%"), ("_", "\_")] … … 760 763 """ 761 764 if column.autoincrement: 765 # From http://www.sqlite.org/datatypes.html: 766 # "One exception to the typelessness of SQLite is a column whose 767 # type is INTEGER PRIMARY KEY. (And you must use "INTEGER" not 768 # "INT". A column of type INT PRIMARY KEY is typeless just like 769 # any other.) INTEGER PRIMARY KEY columns must contain a 32-bit 770 # signed integer. Any attempt to insert non-integer data will 771 # result in an error." 762 772 coldef = "INTEGER PRIMARY KEY AUTOINCREMENT" 763 773 else: … … 839 849 sql_name_max_length = 0 840 850 841 de compiler = SQLiteDecompiler851 deparser = SQLiteDeparser 842 852 selectwriter = SQLiteSelectWriter 843 853 typeset = SQLiteTypeSet() … … 894 904 except (_sqlite.OperationalError, _sqlite.DatabaseError), x: 895 905 msg = x.args[0] 896 if ((msg.startswith("no such ") or906 if ((msg.startswith("no such table") or 897 907 msg == "database schema has changed")): 898 908 if not self.connections.in_transaction(): trunk/geniusql/providers/sqlserver.py
r134 r135 22 22 """ 23 23 # ADO comparison operators for strings are case-insensitive. 24 if op < 6: 25 # ('<', '<=', '==', '!=', '>', '>=') 24 if op in ('<', '<=', '==', '!=', '>', '>='): 26 25 # Some operations on strings can be emulated with the 27 26 # Convert function. … … 236 235 237 236 class SQLServerTypeSet(dbtypes.DatabaseTypeSet): 237 238 # These are not adapter.push(bool) (which are used on one side of 239 # a comparison). Instead, these are used when the whole (sub)expression 240 # is True or False, e.g. "WHERE TRUE", or "WHERE TRUE and 'a'.'b' = 3". 241 expr_true = "(1=1)" 242 expr_false = "(1=0)" 238 243 239 244 known_types = {'float': [REAL, FLOAT], … … 279 284 280 285 281 class SQLServerDecompiler(ado.ADOSQLDecompiler): 282 283 # These are not the same as coerce_bool_to_any (which is used on one side of 284 # a comparison). Instead, these are used when the whole (sub)expression 285 # is True or False, e.g. "WHERE TRUE", or "WHERE TRUE and 'a'.'b' = 3". 286 bool_true = "(1=1)" 287 bool_false = "(1=0)" 286 class SQLServerDeparser(ado.ADOSQLDeparser): 288 287 289 288 like_escapes = [("[", "[[]"), ("%", "[%]"), ("_", "[_]"), … … 481 480 class SQLServerDatabase(ado.ADODatabase): 482 481 483 de compiler = SQLServerDecompiler482 deparser = SQLServerDeparser 484 483 typeset = SQLServerTypeSet() 485 484 connectionmanager = SQLServerConnectionManager trunk/geniusql/select.py
r134 r135 193 193 input_list: a list of SQL expressions, one for each column in the 194 194 SELECT clause. These will include any "expr AS name" alias. 195 output_list: a list of the SQL names (aliases), one per output column. 196 output_keys: the keys for each column in the final table. 195 output: a list of tuples of the form: 196 (column key, column, SQL name (alias), quoted SQL name (alias)) 197 One per output column. 198 output_cols: a dict of source cols for the final table. 197 199 """ 198 200 … … 209 211 if isinstance(relation, Join): 210 212 if isinstance(relation.table1, Join): 211 self. result = relation.table2.schema.table('')212 else: 213 self. result = relation.table1.schema.table('')213 self.schema = relation.table2.schema 214 else: 215 self.schema = relation.table1.schema 214 216 self.tables = self.wrap(relation) 215 217 self.fromclause = self.joinclause(self.tables) 216 218 elif isinstance(relation, geniusql.Schema): 217 219 # This is how we say we want to SELECT scalars (no FROM clause) 218 self. result = relation.table('')220 self.schema = relation 219 221 self.tables = [] 220 222 self.fromclause = "" 221 223 else: 222 self. result = relation.schema.table('')224 self.schema = relation.schema 223 225 self.tables = [self.db.joinwrapper(relation)] 224 226 self.fromclause = relation.qname … … 227 229 228 230 self.input_list = [] 229 self.output _list= []230 self.output_ keys = []231 self.output = [] 232 self.output_cols = {} 231 233 self._groupby = [] 232 234 if isinstance(query.attributes, logic.Expression): 233 self.de compile_attributes()235 self.deparse_attributes() 234 236 else: 235 237 if isinstance(relation, Join): … … 238 240 alias = t.alias or t.qname 239 241 table = t.table 240 for a in attrs: 241 self._copy_column(table, alias, a) 242 for colkey in attrs: 243 col = table[colkey] 244 if colkey in self.output_cols: 245 # Get the key for the table. 246 colkey = '%s_%s' % (table.schema.key_for(table), colkey) 247 colname = '%s_%s' % (table.name, col.name) 248 colqname = self.db.quote(colname) 249 selname = '%s.%s AS %s' % (alias, col.qname, colqname) 250 else: 251 colname = col.name 252 colqname = col.qname 253 selname = '%s.%s' % (alias, colqname) 254 self.input_list.append(selname) 255 self.output.append((colkey, colname, colqname)) 256 self.output_cols[colkey] = col 242 257 else: 243 258 # 'relation' is a single Table object. 244 for a in query.attributes: 245 self._copy_column(relation, None, a) 259 for colkey in query.attributes: 260 col = relation[colkey] 261 self.input_list.append(col.qname) 262 self.output.append((colkey, col.name, col.qname)) 263 self.output_cols[colkey] = col 246 264 247 265 if query.order is None: 248 266 self.order = None 249 267 elif isinstance(query.order, logic.Expression): 250 self.de compile_order()268 self.deparse_order() 251 269 else: 252 270 if isinstance(relation, Join): … … 257 275 self.order = [relation[key].qname for key in query.order] 258 276 259 def _copy_column(self, table, alias, colkey): 260 """Copy the given column from the given table into our result table. 261 262 alias: the name that will be used for the given source table in the 263 FROM clause of the SQL SELECT statement. If None, this signifies 264 that the "SELECT columnlist FROM table" only references a single 265 table, in which case the column list will not use dotted column 266 names. 277 def result_table(self, name): 278 """Return a new Table object for the result of this select. 279 280 This is too expensive to do when you don't need it, so it's 281 a separate function here. Try not to call it more than once 282 for a given SelectWriter instance. 267 283 """ 268 col = table[colkey] 269 newcol = col.copy() 270 newcol.key = False 271 newcol.autoincrement = False 272 newcol.sequence_name = None 273 newcol.initial = 1 274 275 if alias is None: 276 # Single table. 277 selname = col.qname 278 else: 279 selname = '%s.%s' % (alias, col.qname) 280 if colkey in self.result: 281 # Get the key for the table. 282 colkey = '%s_%s' % (table.schema.key_for(table), colkey) 283 newcol.name = '%s_%s' % (table.name, newcol.name) 284 newcol.qname = table.schema.db.quote(newcol.name) 285 selname = '%s AS %s' % (selname, newcol.qname) 286 self.input_list.append(selname) 287 self.output_list.append(newcol.qname) 288 self.output_keys.append(colkey) 289 self.result[colkey] = newcol 284 newtable = self.schema.table(name) 285 for colkey, name, qname in self.output: 286 col = self.output_cols[colkey] 287 newcol = col.copy() 288 newcol.name = name 289 newcol.qname = qname 290 newcol.key = False 291 newcol.autoincrement = False 292 newcol.sequence_name = None 293 newcol.initial = 1 294 newtable[colkey] = newcol 295 return newtable 290 296 291 297 def sql(self, distinct=False, limit=None, into=""): … … 302 308 append("FROM") 303 309 append(self.fromclause) 304 if self.whereclause:305 append("WHERE")306 append(self.whereclause)310 if self.whereclause: 311 append("WHERE") 312 append(self.whereclause) 307 313 if self._groupby and len(self._groupby) < len(self.input_list): 308 314 append("GROUP BY") … … 318 324 """Return an SQL WHERE clause, and an 'imperfect' flag.""" 319 325 tpairs = [(t.alias or t.qname, t.table) for t in self.tables] 320 decom = self.db.decompiler(tpairs, self.query.restriction, self.db.typeset) 321 code = decom.code() 322 return code, decom.imperfect 326 dep = self.db.deparser(tpairs, self.query.restriction, self.db.typeset) 327 ## dep.verbose = True 328 code = dep.code() 329 return code, dep.imperfect 323 330 324 331 def wrap(self, join): … … 372 379 if isinstance(path, logic.Expression): 373 380 tpairs = [(t.alias or t.qname, t.table) for t in self.tables] 374 decom = self.db.decompiler(tpairs, path, self.db.typeset) 375 decom.verbose = True 376 decom.walk() 377 print decom.stack 378 atom = decom.stack[0] 379 from geniusql import decompile 380 if atom is decompile.cannot_represent: 381 raise ValueError("The supplied expression could not be " 382 "translated to SQL: %r" % path) 381 dep = self.db.deparser(tpairs, path, self.db.typeset) 382 ## dep.verbose = True 383 dep.walk() 384 atom = dep.stack[0] 383 385 return atom.sql 384 386 else: … … 425 427 % (name1, name2)) 426 428 427 def de compile_attributes(self):429 def deparse_attributes(self): 428 430 tpairs = [(t.alias or t.qname, t.table) for t in self.tables] 429 decom = self.db.decompiler 430 decom = decom(tpairs, self.query.attributes, self.db.typeset) 431 ## decom.verbose = True 432 433 from geniusql import objects, decompile 434 for atom in decom.field_list(): 435 if atom is decompile.cannot_represent: 436 raise ValueError("The supplied expression could not be " 437 "translated to SQL: %r" % 438 self.query.attributes) 439 440 if atom.name in self.result: 431 dep = self.db.deparser 432 dep = dep(tpairs, self.query.attributes, self.db.typeset) 433 ## dep.verbose = True 434 435 for atom in dep.field_list(): 436 if atom.name in self.output_cols: 441 437 bare_name = atom.name 442 438 index = 1 443 while atom.name in self. result:439 while atom.name in self.output_cols: 444 440 atom.name = '%s%s' % (bare_name, index) 445 441 index += 1 … … 447 443 qname = self.db.quote(atom.name) 448 444 self.input_list.append('%s AS %s' % (atom.sql, qname)) 449 self.output_keys.append(atom.name) 450 self.output_list.append(qname) 445 self.output.append((atom.name, atom.name, qname)) 451 446 if not atom.aggregate: 452 447 self._groupby.append(atom.sql) 453 448 454 col = objects.Column(atom.pytype, atom.dbtype, name=atom.name) 455 col.qname = self.db.quote(col.name) 456 col.adapter = atom.adapter 457 self.result[atom.name] = col 458 459 def decompile_order(self): 449 self.output_cols[atom.name] = atom 450 451 def deparse_order(self): 460 452 tpairs = [(t.alias or t.qname, t.table) for t in self.tables] 461 decom = self.db.decompiler 462 decom = decom(tpairs, self.query.order, self.db.typeset) 463 ## decom.verbose = True 464 465 from geniusql import decompile 466 ob = [] 467 for atom in decom.field_list(): 468 if atom is decompile.cannot_represent: 469 raise ValueError("The supplied expression could not be " 470 "translated to SQL: %r" % 471 self.query.order) 472 ob.append(atom.sql) 473 474 self.order = ob 453 dep = self.db.deparser 454 dep = dep(tpairs, self.query.order, self.db.typeset) 455 ## dep.verbose = True 456 self.order = [atom.sql for atom in dep.field_list()] trunk/geniusql/test/benchmark.py
r134 r135 18 18 Animal['ID'] = schema.column(int, autoincrement=True, key=True) 19 19 Animal['ZooID'] = schema.column(int) 20 Animal.add_index('ZooID') 20 21 Animal['Name'] = schema.column(hints={'bytes': 100}) 21 22 Animal['Species'] = schema.column(hints={'bytes': 100}) … … 26 27 Animal['PreferredFoodID'] = schema.column(int) 27 28 Animal['AlternateFoodID'] = schema.column(int) 28 Animal.add_index('ZooID')29 29 Animal.references['Animal'] = ('ID', 'Animal', 'MotherID') 30 30 schema['Animal'] = Animal … … 33 33 Zoo['ID'] = schema.column(int, autoincrement=True, key=True) 34 34 Zoo.add_index('ID') 35 Zoo['Name'] = schema.column( )35 Zoo['Name'] = schema.column(hints={'bytes': 255}) 36 36 Zoo['Founded'] = schema.column(datetime.date) 37 37 Zoo['Opens'] = schema.column(datetime.time) … … 129 129 130 130 for x in xrange(ITERATIONS): 131 assert len(Zoo.select_all()) == 5 131 allzoos = Zoo.select_all() 132 assert len(allzoos) == 5, allzoos 132 133 assert len(Animal.select_all(lambda x: True)) == ITERATIONS + 12 133 134 assert len(Animal.select_all(lambda x: x.Legs == 4)) == 4 … … 267 268 268 269 zm = ZooMark() 269 ## from cherrypy.lib import profiler 270 ## p = profiler.Profiler(thisdir) 270 if profile: 271 from cherrypy.lib import profiler 272 p = profiler.Profiler(thisdir) 271 273 for method in [x for x in dir(zm) if x.startswith("step_")]: 272 274 startTime = datetime.datetime.now() 273 275 meth = getattr(zm, method) 274 ## p.run(method) 275 meth() 276 if profile: 277 p.run(meth) 278 else: 279 meth() 276 280 print "Ran %s in: %s" % (method, datetime.datetime.now() - startTime) 277 281 finally: … … 288 292 args = sys.argv[1:] 289 293 if args: 290 ITERATIONS = int(args[0]) 294 ITERATIONS = int(args[-1]) 295 profile = "--profile" in args 291 296 292 297 run("psycopg", "geniusql_bench", trunk/geniusql/test/test_logic.py
r134 r135 17 17 e = logic.Expression(lambda x: icontains(x.Status, 'c')) 18 18 self.assertEqual(repr(e), lx + "icontains(x.Status, 'c'))") 19 co_code = e.func.func_code.co_code20 co_code = logic.codewalk.named_opcodes(map(ord, co_code))21 if sys.version_info >= (2, 5):22 # Python 2.5 stopped including args in co_names,23 # so the indices into co_names changed.24 self.assertEqual(co_code,25 ['LOAD_CONST', 2, 0,26 'LOAD_FAST', 0, 0,27 'LOAD_ATTR', 2, 0,28 'LOAD_CONST', 0, 0,29 'CALL_FUNCTION', 2, 0,30 'RETURN_VALUE'])31 else:32 self.assertEqual(co_code,33 ['LOAD_CONST', 2, 0,34 'LOAD_FAST', 0, 0,35 'LOAD_ATTR', 2, 0,36 'LOAD_CONST', 1, 0,37 'CALL_FUNCTION', 2, 0,38 'RETURN_VALUE'])39 19 40 20 # 4/28/04: This one failed in endue.html.nav, … … 240 220 f = logic.comparison('Name', 2, 'Harry') 241 221 g = logic.Expression(lambda x: x.Name == 'Harry') 242 self.assertEqual(f.func.func_code , g.func.func_code)222 self.assertEqual(f.func.func_code.co_code, g.func.func_code.co_code) 243 223 244 224 f = logic.comparison('Size', 4, 300) 245 225 g = logic.Expression(lambda x: x.Size > 300) 246 self.assertEqual(f.func.func_code , g.func.func_code)226 self.assertEqual(f.func.func_code.co_code, g.func.func_code.co_code) 247 227 248 228 f = logic.comparison(u'ID', 2, u'30003') 249 229 g = logic.Expression(lambda x: x.ID == u'30003') 250 self.assertEqual(f.func.func_code , g.func.func_code)230 self.assertEqual(f.func.func_code.co_code, g.func.func_code.co_code) 251 231 252 232 trunk/geniusql/test/zoo_fixture.py
r134 r135 386 386 self.assertEqual(matches(lambda x: day(x.LastEscape) == 21), 1) 387 387 388 # Test AND, OR with cannot_represent.388 # Test AND, OR with CannotRepresent. 389 389 # Notice that we reference a method ('count') which no 390 390 # known SM handles, so it will default back to Expr.eval().
