Changeset 35
- Timestamp:
- 03/15/07 22:49:03
- Files:
-
- trunk/geniusql/__init__.py (modified) (1 diff)
- trunk/geniusql/decompile.py (modified) (2 diffs)
- trunk/geniusql/objects.py (modified) (2 diffs)
- trunk/geniusql/providers/ado.py (modified) (1 diff)
- trunk/geniusql/select.py (modified) (5 diffs)
- trunk/geniusql/test/zoo_fixture.py (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/geniusql/__init__.py
r34 r35 1 """Geniusql, a Python database library. 2 3 The Column and Index classes model corresponding database objects, and are 4 intentionally simple. They should rarely contain any SQL or "smarts" of 5 any kind, besides the "qname", the quoted name, of the column or index. 6 At most, subclasses and consumers might put implementation-specific data 7 into them. 8 9 The IndexSet, Table, and Database objects are all dict-like containers, 10 and therefore have a key for each value. Those keys should equate to things 11 at the consumer layer; for example, a Database may possess a pair of the 12 form: {'YoYo': Table('yoyo')} -- the key is the "friendly" name, but the 13 Table.name is a lowercase version of that, because that's what the database 14 uses in SQL to refer to that table. 15 """ 1 """Geniusql, a Python database library.""" 16 2 17 3 __version__ = "1.0alpha" trunk/geniusql/decompile.py
r34 r35 6 6 'ConstWrapper', 'ColumnWrapper', 'Sentinel', 7 7 'cannot_represent', 'kw_arg', 'SQLDecompiler', 8 'AttributeDecompiler', 8 9 ] 9 10 11 # -------------------------- SQL DECOMPILATION -------------------------- #12 10 13 11 … … 382 380 return "LENGTH(" + x + ")" 383 381 382 383 # ----------------------- Attribute Decompilation ----------------------- # 384 385 386 class ColumnExpr(object): 387 """Wraps a column for use in AttributeDecompiler's stack. 388 389 When we hit LOAD_ATTR while decompiling, we need to keep the column 390 metadata around. 391 392 expr: the expression to be used for this column in the SELECT clause. 393 This should not contain an alias ("AS" clause); that will be 394 provided by the consumer, usually via the 'key' attribute. 395 """ 396 397 def __init__(self, table, col, key, expr): 398 self.table = table 399 self.col = col 400 self.key = key 401 self.alias = key 402 self.expr = expr 403 self.aggregate = False 404 405 406 class AttributeDecompiler(codewalk.Visitor): 407 """Produce an SQL source list from a supplied Expression object. 408 409 Attributes of each argument in the signature will be mapped to table 410 columns. Keyword arguments should be bound using Expression.bind_args 411 before calling this decompiler. 412 413 After walk(), self.stack should be reduced to a list of ColumnExpr's. 414 """ 415 416 # Some constants are function or class objects, 417 # which should not be coerced. 418 no_coerce = (FunctionType, 419 type, 420 type(len), # <type 'builtin_function_or_method'> 421 ) 422 423 sql_cmp_op = ('<', '<=', '=', '!=', '>', '>=', 'in', 'not in') 424 425 def __init__(self, tables, expr, adapter): 426 self.tables = tables 427 self.expr = expr 428 self.adapter = adapter 429 self.groups = [] 430 codewalk.Visitor.__init__(self, expr.func) 431 432 def walk(self): 433 self.stack = [] 434 codewalk.Visitor.walk(self) 435 436 def visit_LOAD_DEREF(self, lo, hi): 437 raise ValueError("Illegal reference found in %s." % self.expr) 438 439 def visit_LOAD_GLOBAL(self, lo, hi): 440 raise ValueError("Illegal global found in %s." % self.expr) 441 442 def visit_LOAD_FAST(self, lo, hi): 443 arg_index = lo + (hi << 8) 444 if arg_index < self.co_argcount: 445 # We've hit a reference to a positional arg, which in our case 446 # implies a reference to a DB table. Append the (qname, table) 447 # tuple for later unpacking inside visit_LOAD_ATTR. 448 self.stack.append(self.tables[arg_index]) 449 else: 450 # Since lambdas don't support local bindings, 451 # any remaining local name must be a keyword arg. 452 raise ValueError("Illegal keyword arg found in %s." % self.expr) 453 454 def visit_LOAD_ATTR(self, lo, hi): 455 name = self.co_names[lo + (hi << 8)] 456 tos = self.stack.pop() 457 if isinstance(tos, tuple): 458 # The name in question refers to a DB column (see visit_LOAD_FAST). 459 alias, table = tos 460 col = table[name] 461 atom = ColumnExpr(table, col, name, '%s.%s' % (alias, col.qname)) 462 else: 463 # 'tos.name' will reference an attribute of the tos object. 464 # Stick the tos and name in a tuple for later processing 465 # (for example, in visit_CALL_FUNCTION). 466 atom = (tos, name) 467 self.stack.append(atom) 468 469 def visit_LOAD_CONST(self, lo, hi): 470 val = self.co_consts[lo + (hi << 8)] 471 if not isinstance(val, self.no_coerce): 472 val = ConstWrapper(val, self.adapter.coerce(val)) 473 self.stack.append(val) 474 475 def visit_CALL_FUNCTION(self, lo, hi): 476 kwargs = {} 477 for i in xrange(hi): 478 val = self.stack.pop() 479 key = self.stack.pop() 480 kwargs[key] = val 481 kwargs = [k + "=" + v for k, v in kwargs.iteritems()] 482 483 args = [] 484 for i in xrange(lo): 485 arg = self.stack.pop() 486 args.append(arg) 487 args.reverse() 488 489 if kwargs: 490 args += kwargs 491 492 func = self.stack.pop() 493 494 # Handle function objects. 495 funcname = func.__module__ + "_" + func.__name__ 496 funcname = funcname.replace(".", "_") 497 if funcname.startswith("_"): 498 funcname = "func" + funcname 499 dispatch = getattr(self, funcname) 500 self.stack.append(dispatch(*args)) 501 502 def visit_UNARY_NOT(self): 503 op = self.stack.pop() 504 self.stack.append("NOT (" + op + ")") 505 506 # --------------------------- Dispatchees --------------------------- # 507 508 def dejavu_day(self, x): 509 x.alias = "day_%s" % x.alias 510 x.expr = "DAY(" + x.expr + ")" 511 return x 512 513 def func__builtin___min(self, x): 514 x.aggregate = True 515 x.alias = "min_%s" % x.alias 516 x.expr = "MIN(" + x.expr + ")" 517 return x 518 519 def func__builtin___max(self, x): 520 x.aggregate = True 521 x.alias = "max_%s" % x.alias 522 x.expr = "MAX(" + x.expr + ")" 523 return x 524 525 def geniusql_decompile_count(self, x): 526 x.aggregate = True 527 x.alias = "count_%s" % x.alias 528 x.expr = "COUNT(" + x.expr + ")" 529 return x 530 trunk/geniusql/objects.py
r34 r35 1 """Geniusql architectural classes. 2 3 The Column and Index classes model corresponding database objects, and are 4 intentionally simple. They should rarely contain any SQL or "smarts" of 5 any kind, besides the "qname", the quoted name, of the column or index. 6 At most, subclasses and consumers might put implementation-specific data 7 into them. 8 9 The IndexSet, Table, and Database objects are all dict-like containers, 10 and therefore have a key for each value. Those keys should equate to things 11 at the consumer layer; for example, a Database may possess a pair of the 12 form: {'YoYo': Table('yoyo')} -- the key is the "friendly" name, but the 13 Table.name is a lowercase version of that, because that's what the database 14 uses in SQL to refer to that table. 15 """ 16 1 17 import threading 2 18 from dejavu import logic … … 756 772 typeadapter = adapters.TypeAdapter() 757 773 decompiler = decompile.SQLDecompiler 774 attributedecompiler = decompile.AttributeDecompiler 758 775 joinwrapper = select.TableWrapper 759 776 selectwriter = select.SelectWriter trunk/geniusql/providers/ado.py
r34 r35 677 677 query = query.encode(self.adaptertosql.encoding) 678 678 679 self.log( repr((id(conn), id(getattr(conn, "conn", None)), query)))679 self.log(query) 680 680 try: 681 681 bareconn = conn trunk/geniusql/select.py
r34 r35 1 from types import FunctionType 2 1 3 from geniusql import errors 2 4 from dejavu import logic … … 111 113 must be in the same order as the tables in the relation. 112 114 113 input_list: the sources mentioned for each column in the FROM clause 114 output_list: the final names (aliases) for each column in the FROM clause. 115 output_keys: the final keys for each column in the FROM clause. 115 input_list: a list of SQL expressions, one for each column in the 116 SELECT clause. These will include any "expr AS name" alias. 117 output_list: a list of the SQL names (aliases), one for each column. 118 output_keys: the keys for each column in the final table. 116 119 """ 117 120 … … 149 152 self.output_list = [] 150 153 self.output_keys = [] 154 self._groupby = [] 151 155 if self.attributes is None: 152 156 # Return all columns 153 157 for t in self.tables: 154 158 self._get_columns(t, None) 159 elif isinstance(self.attributes, FunctionType): 160 self.attributes = logic.Expression(self.attributes) 161 self.decompile_attributes() 162 elif isinstance(self.attributes, logic.Expression): 163 self.decompile_attributes() 155 164 else: 156 165 if isinstance(relation, Join): … … 227 236 w = "" 228 237 229 return "SELECT %s%s%s FROM %s%s;" % (d, cols, into, self.fromclause, w) 238 if self._groupby and len(self._groupby) < len(self.input_list): 239 gb = " GROUP BY " + ", ".join(self._groupby) 240 else: 241 gb = "" 242 243 return ("SELECT %s%s%s FROM %s%s%s;" % 244 (d, cols, into, self.fromclause, w, gb)) 230 245 231 246 def where(self): … … 323 338 raise errors.ReferenceError("No reference found between %s and %s." 324 339 % (name1, name2)) 325 340 341 def decompile_attributes(self): 342 tpairs = [(t.alias or t.qname, t.table) for t in self.tables] 343 decom = self.db.attributedecompiler 344 decom = decom(tpairs, self.attributes, self.db.adaptertosql) 345 decom.walk() 346 for atom in decom.stack: 347 newcol = atom.col.copy() 348 newcol.key = False 349 newcol.autoincrement = False 350 newcol.sequence_name = None 351 newcol.initial = 1 352 353 if atom.alias in self.result: 354 schema = atom.table.schema 355 atom.alias = '%s_%s' % (schema.key_for(atom.table), atom.alias) 356 newcol.name = '%s_%s' % (atom.table.name, newcol.name) 357 newcol.qname = schema.db.quote(newcol.name) 358 359 self.output_list.append(newcol.qname) 360 self.output_keys.append(atom.alias) 361 if not atom.aggregate: 362 self._groupby.append(atom.expr) 363 self.result[atom.alias] = newcol 364 365 expr = atom.expr 366 if atom.alias != atom.key: 367 expr = '%s AS %s' % (expr, atom.alias) 368 self.input_list.append(expr) 369 trunk/geniusql/test/zoo_fixture.py
r34 r35 401 401 def test_5_Aggregates(self): 402 402 try: 403 Animal = schema['Animal'] 404 Visit = schema['Visit'] 405 Vet = schema['Vet'] 406 403 407 # views 404 view = db.select( schema['Animal'], ['Legs'])408 view = db.select(Animal, ['Legs']) 405 409 legs = [x[0] for x in view] 406 410 legs.sort() … … 419 423 'Ape': None, 420 424 } 421 for species, lifespan in db.select( schema['Animal'], ['Species', 'Lifespan']):425 for species, lifespan in db.select(Animal, ['Species', 'Lifespan']): 422 426 if expected[species] is None: 423 427 self.assertEqual(lifespan, None) … … 436 440 # distinct 437 441 legs = [x[0] for x in 438 db.select( schema['Animal'], ['Legs'], distinct=True)]442 db.select(Animal, ['Legs'], distinct=True)] 439 443 legs.sort() 440 444 self.assertEqual(legs, [1, 2, 4, 100, 1000000]) … … 442 446 # This may raise a warning on some DB's. 443 447 f = (lambda x: x.Species == 'Lion') 444 escapees = db.select(schema['Animal'], ['Legs'], f, distinct=True) 445 self.assertEqual(list(escapees), [[4]]) 448 lionlegs = db.select(Animal, ['Legs'], f, distinct=True) 449 self.assertEqual(list(lionlegs), [[4]]) 450 451 # Test attribute lambdas 452 visits = db.select(Animal << Visit << Vet, 453 lambda a, v, vet: (a.Species, vet.Name), 454 lambda a, v, vet: vet.Name != None, 455 distinct = True) 456 visits = list(visits) 457 visits.sort() 458 self.assertEqual(visits, [[u'Emperor Penguin', u'Jim McBain'], 459 [u'Tiger', u'Charles Schroeder']]) 460 461 # Test attribute lambdas with GROUP BY 462 firstvisit = db.select(Animal << Visit, 463 lambda a, v: (a.Species, min(v.Date))) 464 firstvisit = list(firstvisit) 465 firstvisit.sort() 466 self.assertEqual(firstvisit, 467 [[u'Adelie Penguin', None], 468 [u'Ape', None], 469 [u'Bear', None], 470 [u'Centipede', None], 471 [u'Emperor Penguin', datetime.date(2001, 1, 1)], 472 [u'Leopard', None], 473 [u'Lion', None], 474 [u'Millipede', None], 475 [u'Ostrich', None], 476 [u'Slug', None], 477 [u'Tiger', datetime.date(2001, 1, 1)]] 478 ) 479 480 # Test global agg funcs 481 visits = db.select(Animal << Visit, 482 lambda a, v: (a.Species, count(v.Date))) 483 visits = list(visits) 484 visits.sort() 485 self.assertEqual(visits, [[]]) 446 486 finally: 447 487 db.connections.commit()
