Changeset 20
- Timestamp:
- 03/06/07 00:09:22
- Files:
-
- trunk/geniusql/objects.py (modified) (14 diffs)
- trunk/geniusql/select.py (modified) (6 diffs)
- trunk/geniusql/test/zoo_fixture.py (modified) (6 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/geniusql/objects.py
r19 r20 10 10 11 11 12 class Bijection(dict): 13 """Bijective dict. Each key maps to only one value (and vice-versa).""" 14 15 def inverse(self, value): 16 """For the given value, return its corresponding key.""" 17 for key, val in self.iteritems(): 18 if val is value: 19 return key 20 raise ValueError("The given value could not be found: %r" % value) 21 22 12 23 class Index: 13 24 """An index on a table column (or columns) in a database.""" … … 32 43 33 44 34 class IndexSet( dict):45 class IndexSet(Bijection): 35 46 36 47 def __new__(cls, table): … … 40 51 dict.__init__(self) 41 52 self.table = table 53 54 def __getstate__(self): 55 return self.items() 56 57 def __setstate__(self, state): 58 self.update(state) 42 59 43 60 def alias(self, oldname, newname): … … 69 86 if t.created: 70 87 t.schema.db.execute_ddl('DROP INDEX %s ON %s;' % 71 (self[key].qname, t.qname))88 (self[key].qname, t.qname)) 72 89 dict.__delitem__(self, key) 73 90 … … 137 154 138 155 139 class Table( dict):156 class Table(Bijection): 140 157 """A table in a database; a dict of Column objects. 141 158 … … 184 201 return newtable 185 202 copy = __copy__ 203 204 def __getstate__(self): 205 return (self.name, self.qname, self.items(), self.indices) 206 207 def __setstate__(self, state): 208 self.name, self.qname, items, self.indices = state 209 self.update(items) 210 self.indices.table = self 186 211 187 212 def alias(self, oldname, newname): … … 413 438 for row in dataset: 414 439 row = dict(zip(attrs, row)) 415 if restriction and dataset. imperfect:440 if restriction and dataset.selector.imperfect: 416 441 # Run a dummy object through our restriction before yielding. 417 442 if not restriction(_ImperfectDummy(**row)): … … 444 469 445 470 446 class Schema( dict):471 class Schema(Bijection): 447 472 """A dict for managing a set of tables. 448 473 … … 452 477 geniusql consumer might want their code to use TitledNames to 453 478 refer to each table. 479 480 This is a subclass of Bijection, so each key must map to one 481 and only one Table object (and vice-versa). 454 482 455 483 When a consumer adds and deletes items from a Schema object, … … 849 877 """Return a Dataset of matching data, coerced to Python types.""" 850 878 sel = self.selectwriter(self, relation, attributes, restriction) 851 data, coldefs= self.fetch(sel.sql(distinct))852 return Dataset( data, coldefs, sel.result, sel.imperfect)879 data, _ = self.fetch(sel.sql(distinct)) 880 return Dataset(sel, data) 853 881 854 882 def insert_into(self, name, relation, attributes, restriction=None, … … 878 906 879 907 # SELECT ALL 880 data, coldefs= self.fetch(sel.sql(distinct))908 data, _ = self.fetch(sel.sql(distinct)) 881 909 882 names = [x[0] for x in coldefs] 883 dataset = Dataset(data, coldefs, newtable, sel.imperfect) 910 dataset = Dataset(sel, data) 884 911 for row in dataset: 885 row = dict(zip( names, row))886 if restriction and dataset.imperfect:912 row = dict(zip(sel.output_keys, row)) 913 if restriction and sel.imperfect: 887 914 # Run a dummy object through our restriction before inserting. 888 915 if not restriction(_ImperfectDummy(**row)): … … 902 929 """A populated relation; the result of a SELECT.""" 903 930 904 def __init__(self, data, coldefs, table, imperfect): 931 def __init__(self, selector, data): 932 self.selector = selector 905 933 self.data = data 906 self.coldefs = coldefs 907 self.table = table 908 self.coerce = self.table.schema.db.adapterfromdb.coerce 909 self.imperfect = imperfect 934 self.coerce = self.selector.result.schema.db.adapterfromdb.coerce 910 935 self.cursor = 0 911 936 … … 914 939 915 940 def next(self): 941 """Return the next row of data, coerced to Python types.""" 916 942 try: 917 943 row = self.data[self.cursor] … … 921 947 922 948 coerced_row = [] 923 for i, desc in enumerate(self.coldefs): 924 name, type = desc[:2] 949 for i, colkey in enumerate(self.selector.output_keys): 925 950 val = row[i] 926 col = self. table[name]951 col = self.selector.result[colkey] 927 952 val = self.coerce(val, col.dbtype, col.pytype) 928 953 coerced_row.append(val) trunk/geniusql/select.py
r19 r20 494 494 if given, this will be used to construct a WHERE clause. The args 495 495 must be in the same order as the tables in the relation. 496 497 input_list: the sources mentioned for each column in the FROM clause 498 output_list: the final names (aliases) for each column in the FROM clause. 499 output_keys: the final keys for each column in the FROM clause. 496 500 """ 497 501 … … 528 532 self.input_list = [] 529 533 self.output_list = [] 534 self.output_keys = [] 530 535 if self.attributes is None: 531 536 # Return all columns … … 539 544 # 'relation' is a single Table object. 540 545 for a in self.attributes: 541 self._copy_column(relation, relation.qname, relation[a]) 542 543 def _copy_column(self, table, alias, col): 546 self._copy_column(relation, relation.qname, a) 547 548 def _copy_column(self, table, alias, colkey): 549 col = table[colkey] 544 550 newcol = col.copy() 545 551 newcol.key = False … … 549 555 550 556 selname = '%s.%s' % (alias, col.qname) 551 if newcol.name in self.result: 557 if colkey in self.result: 558 # Get the key for the table. 559 colkey = '%s_%s' % (table.schema.inverse(table), colkey) 552 560 newcol.name = '%s_%s' % (table.name, newcol.name) 553 561 newcol.qname = table.schema.db.quote(newcol.name) … … 555 563 self.input_list.append(selname) 556 564 self.output_list.append(newcol.qname) 557 self.result[newcol.name] = newcol 565 self.output_keys.append(colkey) 566 self.result[colkey] = newcol 558 567 559 568 def _get_columns(self, tablewrapper, attrs=None): … … 564 573 # Place the identifier (primary key) properties first 565 574 # in case others depend upon them. 566 cols = [(col.key, col) for col in table.itervalues()]575 cols = [(col.key, key) for key, col in table.iteritems()] 567 576 cols.sort() 568 for is key, colin cols:569 self._copy_column(table, alias, col)577 for is_primary_key, key in cols: 578 self._copy_column(table, alias, key) 570 579 else: 571 580 for a in attrs: 572 self._copy_column(table, alias, table[a])581 self._copy_column(table, alias, a) 573 582 574 583 def sql(self, distinct=False, into=""): trunk/geniusql/test/zoo_fixture.py
r19 r20 6 6 logname = os.path.join(thisdir, "geniusqltest.log") 7 7 8 9 try:10 import pythoncom11 except ImportError:12 pythoncom = None13 8 14 9 try: … … 39 34 40 35 class ZooTests(unittest.TestCase): 36 37 def assertEqualSet(self, a, b): 38 self.assertEqual(set(a), set(b)) 41 39 42 40 def test_1_create_tables(self): … … 511 509 [('Name', ), ('Species', )])) 512 510 self.assertEqual(len(zooed_animals), 6) 513 self.assertEqual (set([tuple(row) for row in zooed_animals]),514 set([("Wild Animal Park", "Leopard"),515 ("Wild Animal Park", "Lion"),516 ("San Diego Zoo", "Tiger"),517 ("San Diego Zoo", "Millipede"),518 ("Sea_World", "Emperor Penguin"),519 ("Sea_World", "Adelie Penguin")]))511 self.assertEqualSet([tuple(row) for row in zooed_animals], 512 [("Wild Animal Park", "Leopard"), 513 ("Wild Animal Park", "Lion"), 514 ("San Diego Zoo", "Tiger"), 515 ("San Diego Zoo", "Millipede"), 516 ("Sea_World", "Emperor Penguin"), 517 ("Sea_World", "Adelie Penguin")]) 520 518 521 519 zooed_animals = list(db.select(schema['Zoo'] >> schema['Animal'], 522 520 [('Name', ), ('Species', )])) 523 521 self.assertEqual(len(zooed_animals), 12) 524 self.assertEqual(set([tuple(row) for row in zooed_animals]), 525 set([("Wild Animal Park", "Leopard"), 526 ("Wild Animal Park", "Lion"), 527 ("San Diego Zoo", "Tiger"), 528 ("San Diego Zoo", "Millipede"), 529 ("Sea_World", "Emperor Penguin"), 530 ("Sea_World", "Adelie Penguin"), 531 (None, "Slug"), 532 (None, "Bear"), 533 (None, "Ostrich"), 534 (None, "Centipede"), 535 (None, "Ape"), 536 (None, "Ape"), 537 ])) 522 self.assertEqualSet([tuple(row) for row in zooed_animals], 523 [("Wild Animal Park", "Leopard"), 524 ("Wild Animal Park", "Lion"), 525 ("San Diego Zoo", "Tiger"), 526 ("San Diego Zoo", "Millipede"), 527 ("Sea_World", "Emperor Penguin"), 528 ("Sea_World", "Adelie Penguin"), 529 (None, "Slug"), (None, "Bear"), 530 (None, "Ostrich"), (None, "Centipede"), 531 (None, "Ape"), (None, "Ape"), 532 ]) 538 533 539 534 zooed_animals = list(db.select(schema['Zoo'] << schema['Animal'], 540 535 [('Name', ), ('Species', )])) 541 536 self.assertEqual(len(zooed_animals), 7) 542 self.assertEqual (set([tuple(row) for row in zooed_animals]),543 set([("Wild Animal Park", "Leopard"),544 ("Wild Animal Park", "Lion"),545 ("San Diego Zoo", "Tiger"),546 ("San Diego Zoo", "Millipede"),547 ("Sea_World", "Emperor Penguin"),548 ("Sea_World", "Adelie Penguin"),549 (u'Montr\xe9al Biod\xf4me', None),550 ]))537 self.assertEqualSet([tuple(row) for row in zooed_animals], 538 [("Wild Animal Park", "Leopard"), 539 ("Wild Animal Park", "Lion"), 540 ("San Diego Zoo", "Tiger"), 541 ("San Diego Zoo", "Millipede"), 542 ("Sea_World", "Emperor Penguin"), 543 ("Sea_World", "Adelie Penguin"), 544 (u'Montr\xe9al Biod\xf4me', None), 545 ]) 551 546 552 547 # Try a multiple-arg expression … … 565 560 self.assertEqual(len(list(db.select(tree, None, f))), 2) 566 561 567 ## # MSAccess can't handle an INNER JOIN nested in an OUTER JOIN.568 ## # Test that this fails for MSAccess, but works for other SM's.569 ## trees = []570 ## def make_tree():571 ## trees.append( (Animal & Zoo) >> Vet )572 ## warnings.filterwarnings("ignore", category=errors.FeatureWarning)573 ## try:574 ## make_tree()575 ## finally:576 ## warnings.filters.pop(0)577 ##578 ## azv = []579 ## def set_azv():580 ## f = (lambda a, z, v: z.Name == 'Sea_World')581 ## azv.append(box.recall(trees[0], f))582 ##583 ## smname = arena.stores['testSM'].__class__.__name__584 ## if smname in ("StorageManagerADO_MSAccess",):585 ## self.assertRaises(pythoncom.com_error, set_azv)586 ## else:587 ## set_azv()588 ## self.assertEqual(len(azv[0]), 2)589 590 562 # Try mentioning the same class twice. 591 563 tree = (schema['Animal'] << schema['Animal']) … … 596 568 db.connections.commit() 597 569 598 ## def test_8_CustomAssociations(self): 599 ## box = arena.new_sandbox() 600 ## try: 601 ## # Try different association paths 602 ## std_expected = ['Live Bunny Wabbit', 'Live Fish', 'Live Fish', 'T-Bone'] 603 ## cus_expected = ['Dead Fish', 'Dead Fish', 'Live Bunny Wabbit', 'T-Bone'] 604 ## uj = Animal & Food 605 ## for path, expected in [# standard path 606 ## (None, std_expected), 607 ## # custom path 608 ## ('Alternate Food', cus_expected)]: 609 ## 610 ## uj.path = path 611 ## foods = [food for animal, food in box.recall(uj)] 612 ## foods.sort(dejavu.sort('Name')) 613 ## self.assertEqual([f.Name for f in foods], expected) 614 ## 615 ## # Test the magic association methods 616 ## tiger = box.unit(Animal, Species='Tiger') 617 ## self.assertEqual(tiger.Food().Name, 'Live Bunny Wabbit') 618 ## self.assertEqual(tiger.AlternateFood().Name, 'T-Bone') 619 ## 620 ## finally: 621 ## box.flush_all() 570 def test_8_CustomAssociations(self): 571 try: 572 # Try different association paths 573 std_expected = ['Live Bunny Wabbit', 'Live Fish', 'Live Fish', 'T-Bone'] 574 cus_expected = ['Dead Fish', 'Dead Fish', 'Live Bunny Wabbit', 'T-Bone'] 575 uj = schema['Animal'] & schema['Food'] 576 for path, expected in [# standard path 577 (None, std_expected), 578 # custom path 579 ('Alternate Food', cus_expected)]: 580 uj.path = path 581 foods = list(db.select(uj, [(), ('Name',)])) 582 self.assertEqualSet([name for name, in foods], expected) 583 finally: 584 db.connections.commit() 622 585 623 586 def test_9_delete(self): … … 647 610 ('ID', 'Name')], 648 611 lambda a, f: f.Name == 'Live Fish') 649 self.assertEqual(set(newtable.keys()), 650 set(['ID', 'Species', 'ZooID', 'Food_ID', 'Name'])) 651 self.assertEqual(newtable.select_all(), 612 self.assertEqualSet(newtable.keys(), 613 ['ID', 'Species', 'ZooID', 'Food_ID', 'Name']) 614 results = newtable.select_all() 615 results.sort() 616 self.assertEqual(results, 652 617 [{'Food_ID': 2, 'ZooID': 4, 'Name': u'Live Fish', 653 618 'ID': 8, 'Species': u'Emperor Penguin'},
