Contact: fumanchu@aminus.org

Log in as guest/geniusql to create tickets

Changeset 20

Show
Ignore:
Timestamp:
03/06/07 00:09:22
Author:
fumanchu
Message:

Worked around MySQL case-lowering by adding SelectWriter?.output_keys and passing the selectwriter instance to Dataset.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/geniusql/objects.py

    r19 r20  
    1010 
    1111 
     12class 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 
    1223class Index: 
    1324    """An index on a table column (or columns) in a database.""" 
     
    3243 
    3344 
    34 class IndexSet(dict): 
     45class IndexSet(Bijection): 
    3546     
    3647    def __new__(cls, table): 
     
    4051        dict.__init__(self) 
    4152        self.table = table 
     53     
     54    def __getstate__(self): 
     55        return self.items() 
     56     
     57    def __setstate__(self, state): 
     58        self.update(state) 
    4259     
    4360    def alias(self, oldname, newname): 
     
    6986        if t.created: 
    7087            t.schema.db.execute_ddl('DROP INDEX %s ON %s;' % 
    71                              (self[key].qname, t.qname)) 
     88                                    (self[key].qname, t.qname)) 
    7289        dict.__delitem__(self, key) 
    7390 
     
    137154 
    138155 
    139 class Table(dict): 
     156class Table(Bijection): 
    140157    """A table in a database; a dict of Column objects. 
    141158     
     
    184201        return newtable 
    185202    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 
    186211     
    187212    def alias(self, oldname, newname): 
     
    413438        for row in dataset: 
    414439            row = dict(zip(attrs, row)) 
    415             if restriction and dataset.imperfect: 
     440            if restriction and dataset.selector.imperfect: 
    416441                # Run a dummy object through our restriction before yielding. 
    417442                if not restriction(_ImperfectDummy(**row)): 
     
    444469 
    445470 
    446 class Schema(dict): 
     471class Schema(Bijection): 
    447472    """A dict for managing a set of tables. 
    448473     
     
    452477    geniusql consumer might want their code to use TitledNames to 
    453478    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). 
    454482     
    455483    When a consumer adds and deletes items from a Schema object, 
     
    849877        """Return a Dataset of matching data, coerced to Python types.""" 
    850878        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
    853881     
    854882    def insert_into(self, name, relation, attributes, restriction=None, 
     
    878906             
    879907            # SELECT ALL 
    880             data, coldefs = self.fetch(sel.sql(distinct)) 
     908            data, _ = self.fetch(sel.sql(distinct)) 
    881909             
    882             names = [x[0] for x in coldefs] 
    883             dataset = Dataset(data, coldefs, newtable, sel.imperfect) 
     910            dataset = Dataset(sel, data) 
    884911            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: 
    887914                    # Run a dummy object through our restriction before inserting. 
    888915                    if not restriction(_ImperfectDummy(**row)): 
     
    902929    """A populated relation; the result of a SELECT.""" 
    903930     
    904     def __init__(self, data, coldefs, table, imperfect): 
     931    def __init__(self, selector, data): 
     932        self.selector = selector 
    905933        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 
    910935        self.cursor = 0 
    911936     
     
    914939     
    915940    def next(self): 
     941        """Return the next row of data, coerced to Python types.""" 
    916942        try: 
    917943            row = self.data[self.cursor] 
     
    921947         
    922948        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): 
    925950            val = row[i] 
    926             col = self.table[name
     951            col = self.selector.result[colkey
    927952            val = self.coerce(val, col.dbtype, col.pytype) 
    928953            coerced_row.append(val) 
  • trunk/geniusql/select.py

    r19 r20  
    494494        if given, this will be used to construct a WHERE clause. The args 
    495495        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. 
    496500    """ 
    497501     
     
    528532        self.input_list = [] 
    529533        self.output_list = [] 
     534        self.output_keys = [] 
    530535        if self.attributes is None: 
    531536            # Return all columns 
     
    539544                # 'relation' is a single Table object. 
    540545                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] 
    544550        newcol = col.copy() 
    545551        newcol.key = False 
     
    549555         
    550556        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) 
    552560            newcol.name = '%s_%s' % (table.name, newcol.name) 
    553561            newcol.qname = table.schema.db.quote(newcol.name) 
     
    555563        self.input_list.append(selname) 
    556564        self.output_list.append(newcol.qname) 
    557         self.result[newcol.name] = newcol 
     565        self.output_keys.append(colkey) 
     566        self.result[colkey] = newcol 
    558567     
    559568    def _get_columns(self, tablewrapper, attrs=None): 
     
    564573            # Place the identifier (primary key) properties first 
    565574            # 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()] 
    567576            cols.sort() 
    568             for iskey, col in cols: 
    569                 self._copy_column(table, alias, col
     577            for is_primary_key, key in cols: 
     578                self._copy_column(table, alias, key
    570579        else: 
    571580            for a in attrs: 
    572                 self._copy_column(table, alias, table[a]
     581                self._copy_column(table, alias, a
    573582     
    574583    def sql(self, distinct=False, into=""): 
  • trunk/geniusql/test/zoo_fixture.py

    r19 r20  
    66logname = os.path.join(thisdir, "geniusqltest.log") 
    77 
    8  
    9 try: 
    10     import pythoncom 
    11 except ImportError: 
    12     pythoncom = None 
    138 
    149try: 
     
    3934 
    4035class ZooTests(unittest.TestCase): 
     36     
     37    def assertEqualSet(self, a, b): 
     38        self.assertEqual(set(a), set(b)) 
    4139     
    4240    def test_1_create_tables(self): 
     
    511509                                           [('Name', ), ('Species', )])) 
    512510            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")]
    520518             
    521519            zooed_animals = list(db.select(schema['Zoo'] >> schema['Animal'], 
    522520                                           [('Name', ), ('Species', )])) 
    523521            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                                 ]) 
    538533             
    539534            zooed_animals = list(db.select(schema['Zoo'] << schema['Animal'], 
    540535                                           [('Name', ), ('Species', )])) 
    541536            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                                 ]
    551546             
    552547            # Try a multiple-arg expression 
     
    565560            self.assertEqual(len(list(db.select(tree, None, f))), 2) 
    566561             
    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              
    590562            # Try mentioning the same class twice. 
    591563            tree = (schema['Animal'] << schema['Animal']) 
     
    596568            db.connections.commit() 
    597569     
    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() 
    622585     
    623586    def test_9_delete(self): 
     
    647610                                   ('ID', 'Name')], 
    648611                                  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, 
    652617                         [{'Food_ID': 2, 'ZooID': 4, 'Name': u'Live Fish', 
    653618                           'ID': 8, 'Species': u'Emperor Penguin'},