Contact: fumanchu@aminus.org

Log in as guest/geniusql to create tickets

Changeset 23

Show
Ignore:
Timestamp:
03/08/07 18:52:55
Author:
fumanchu
Message:

New Table.add_primary, drop_primary methods. Also made a new 'Bijection' superclass for Schema, Table, and IndexSet?. Also fixed a bug in SQLite temp tables (indices were not being copied).

Files:

Legend:

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

    r22 r23  
    1919                return key 
    2020        raise ValueError("The given value could not be found: %r" % value) 
     21     
     22    def alias(self, oldname, newname): 
     23        """Move the object at the given, existing key to the new key. 
     24         
     25        Consumer code should call this method when user-supplied object 
     26        names do not match the names in the database. 
     27        """ 
     28        if oldname == newname: 
     29            return 
     30         
     31        obj = self[oldname] 
     32        if newname in self: 
     33            dict.__delitem__(self, newname) 
     34        dict.__delitem__(self, oldname) 
     35        dict.__setitem__(self, newname, obj) 
    2136 
    2237 
     
    5772    def __setstate__(self, state): 
    5873        self.update(state) 
    59      
    60     def alias(self, oldname, newname): 
    61         """Move the Index at the given, existing key to the new key. 
    62          
    63         Consumer code should call this method when user-supplied index 
    64         names do not match the names in the database. 
    65         """ 
    66         if oldname == newname: 
    67             return 
    68         obj = self[oldname] 
    69         if newname in self: 
    70             dict.__delitem__(self, newname) 
    71         dict.__delitem__(self, oldname) 
    72         dict.__setitem__(self, newname, obj) 
    7374     
    7475    def __setitem__(self, key, index): 
     
    9091 
    9192 
    92 class Column
     93class Column(object)
    9394    """A column in a table in a database. 
    9495     
     
    169170        Table, or calls methods like 'rename'. 
    170171    indices: a dict-like IndexSet of Index objects. 
    171     references: a dict of the form: {name: (nearColKey, farTableKey, farColKey)}. 
     172    references: a dict of the form: 
     173        {name: (near Column key, far Table key, far Column key)}. 
    172174    """ 
    173175     
     
    210212        self.indices.table = self 
    211213     
    212     def alias(self, oldname, newname): 
    213         """Move the Column at the given, existing key to the new key. 
    214          
    215         Consumer code should call this method when user-supplied column 
    216         names do not match the names in the database. 
    217         """ 
    218         if oldname == newname: 
    219             return 
    220          
    221         obj = self[oldname] 
    222         if newname in self: 
    223             dict.__delitem__(self, newname) 
    224         dict.__delitem__(self, oldname) 
    225         dict.__setitem__(self, newname, obj) 
    226      
    227214    def _add_column(self, column): 
    228215        """Internal function to add the column to the database.""" 
     
    309296        return i 
    310297     
     298    def add_primary(self): 
     299        """Set the PRIMARY KEY for this Table, using its Column.key values.""" 
     300        pk = [column.qname for column in self.itervalues() if column.key] 
     301        if pk: 
     302            self.schema.db.execute("ALTER TABLE %s ADD PRIMARY KEY (%s);" % 
     303                                   (self.qname, ", ".join(pk))) 
     304     
     305    def drop_primary(self): 
     306        """Remove any PRIMARY KEY for this Table.""" 
     307        raise NotImplementedError 
    311308     
    312309    # ---------------------------- OLTP/CRUD ---------------------------- # 
     
    332329            if self[key].key: 
    333330                ids[key] = value 
    334         return self.whereclause(**ids) 
     331        try: 
     332            return self.whereclause(**ids) 
     333        except Exception, x: 
     334            x.args += (inputs,) 
     335            raise 
    335336     
    336337    def insert(self, **inputs): 
     
    597598        finally: 
    598599            self._discover_lock.release() 
    599      
    600     def alias(self, oldname, newname): 
    601         """Move the Table at the given, existing key to the new key. 
    602          
    603         Consumer code should call this method when user-supplied table 
    604         names do not match the names in the database. 
    605         """ 
    606         if oldname == newname: 
    607             return 
    608          
    609         obj = self[oldname] 
    610         if newname in self: 
    611             dict.__delitem__(self, newname) 
    612         dict.__delitem__(self, oldname) 
    613         dict.__setitem__(self, newname, obj) 
    614600     
    615601    def _column_name(self, tablename, columnkey): 
  • trunk/geniusql/providers/ado.py

    r22 r23  
    378378            conn = None 
    379379            cat = None 
     380     
     381    def drop_primary(self): 
     382        """Remove any PRIMARY KEY for this Table.""" 
     383        db = self.schema.db 
     384         
     385        data, _ = db.fetch(adSchemaIndexes, schema=True) 
     386        pknames = [row[5] for row in data 
     387                   if (self.name == row[2]) and row[6]] 
     388        for name in pknames: 
     389            db.execute('ALTER TABLE %s DROP CONSTRAINT %s;' 
     390                       % (self.qname, name)) 
    380391 
    381392 
  • trunk/geniusql/providers/firebird.py

    r22 r23  
    301301         
    302302        return newids 
     303     
     304    def drop_primary(self): 
     305        """Remove any PRIMARY KEY for this Table.""" 
     306        db = self.schema.db 
     307         
     308        data, _ = db.fetch( 
     309            "SELECT RDB$CONSTRAINT_NAME FROM RDB$RELATION_CONSTRAINTS " 
     310            "WHERE (RDB$CONSTRAINT_TYPE = 'PRIMARY KEY') " 
     311            "AND (RDB$RELATION_NAME = '%s')" % self.name) 
     312        for row in data: 
     313            db.execute('ALTER TABLE %s DROP CONSTRAINT %s;' 
     314                       % (self.qname, row[0].rstrip())) 
    303315 
    304316 
  • trunk/geniusql/providers/mysql.py

    r22 r23  
    181181    def _grab_new_ids(self, idkeys, conn): 
    182182        return {idkeys[0]: conn.insert_id()} 
     183     
     184    def drop_primary(self): 
     185        """Remove any PRIMARY KEY for this Table.""" 
     186        self.schema.db.execute('ALTER TABLE %s DROP PRIMARY KEY;' % self.qname) 
    183187 
    184188 
  • trunk/geniusql/providers/postgres.py

    r22 r23  
    131131            newids[idkey] = data[0][0] 
    132132        return newids 
     133     
     134    def drop_primary(self): 
     135        """Remove any PRIMARY KEY for this Table.""" 
     136        db = self.schema.db 
     137         
     138        # Get the OID of the table 
     139        data, _ = db.fetch("SELECT oid FROM pg_class WHERE " 
     140                           "relname = '%s'" % self.name) 
     141        table_OID = data[0][0] 
     142         
     143        data, _ = db.fetch("SELECT conname, * FROM pg_constraint WHERE conrelid " 
     144                           "= %s AND contype = 'p'" % table_OID) 
     145        for row in data: 
     146            constraint_name = row[0] 
     147            db.execute('ALTER TABLE %s DROP CONSTRAINT "%s";' 
     148                       % (self.qname, constraint_name)) 
    133149 
    134150 
  • trunk/geniusql/providers/sqlite.py

    r22 r23  
    285285 
    286286class SQLiteTable(geniusql.Table): 
    287      
    288     def _parent_key(self): 
    289         """Return the key of this Table in its parent Database.""" 
    290         names = [x for x in self.schema if self.schema[x].name == self.name] 
    291         return names[0] 
     287    """A table in a database; a dict of Column objects. 
     288     
     289    Values in this dict must be instances of Column (or a subclass of it). 
     290    Keys should be consumer-friendly names for each Column value. 
     291     
     292    name: the SQL name for this table (unquoted). 
     293    qname: the SQL name for this table (quoted). 
     294    schema: the schema for this table. 
     295    created: whether or not this Table has a concrete implementation in the 
     296        database. If False (the default), then changes to Table items can be 
     297        made with impunity. If True, then appropriate ALTER TABLE commands 
     298        are executed whenever a consumer adds or deletes items from the 
     299        Table, or calls methods like 'rename'. 
     300    indices: a dict-like IndexSet of Index objects. 
     301    references: a dict of the form: 
     302        {name: (near Column key, far Table key, far Column key)}. 
     303     
     304    Various versions of SQLite have limited support for ALTER TABLE. 
     305    When necessary, this class will compensate with the following process: 
     306     
     307      1. Create a temp table which has the desired new schema. 
     308      2. Copy the entire dataset from the original table to the temp table. 
     309      3. Drop the original table. 
     310      4. Re-create the original table with the desired new schema. 
     311      5. Copy the entire dataset from the temp table to the re-created table. 
     312      6. Drop the temporary table. 
     313     
     314    Needless to say, this can take a LOT longer than most other stores. 
     315    """ 
    292316     
    293317    def _temp_copy(self): 
    294         # Create the temporary table with the new fields (no indices). 
     318        # Make a temporary table. Callers then modify it before binding. 
     319        schema = self.schema 
    295320        temptable = self.copy() 
    296         tempkey = "temp_" + self._parent_key() 
    297         temptable.name = self.schema.table_name(tempkey) 
    298         temptable.qname = self.schema.db.quote(temptable.name) 
    299         temptable.indices.clear() 
     321        tempkey = "temp_" + schema.key_for(self) 
     322        temptable.name = schema.table_name(tempkey) 
     323        temptable.qname = schema.db.quote(temptable.name) 
     324        # Update index names. 
     325        for key, i in temptable.indices.iteritems(): 
     326            i.name = schema.table_name("i" + temptable.name + i.colname) 
     327            i.qname = schema.db.quote(i.name) 
     328            i.tablename = temptable.name 
    300329        return tempkey, temptable 
    301330     
    302     def _copy_from_temp(self, temptable, thiskey, tempkey): 
     331    def _copy_from_temp(self, temptable, tempkey): 
    303332        """Copy data from a temp table to a new table for self.""" 
     333        schema = self.schema 
    304334         
    305335        # Drop the old table and create the new, final table. 
     
    307337        newtable.name = self.name 
    308338        newtable.qname = self.qname 
    309         self.schema[thiskey] = newtable 
     339        # Update index names. 
     340        for key, i in newtable.indices.iteritems(): 
     341            i.name = schema.table_name("i" + newtable.name + i.colname) 
     342            i.qname = schema.db.quote(i.name) 
     343            i.tablename = newtable.name 
     344        schema[schema.key_for(self)] = newtable 
    310345         
    311346        # Copy data from the temp table to the final table. 
     
    313348        # mixes up the fields (during rename, at least). 
    314349        selfields = ", ".join([c.qname for c in temptable.values()]) 
    315         self.schema.db.execute("INSERT INTO %s (%s) SELECT %s FROM %s;" % 
    316                                (newtable.qname, selfields, selfields, 
    317                                 temptable.qname)) 
     350        schema.db.execute("INSERT INTO %s (%s) SELECT %s FROM %s;" % 
     351                          (newtable.qname, selfields, selfields, 
     352                           temptable.qname)) 
    318353         
    319354        # Drop the intermediate table. 
    320         del self.schema[tempkey] 
     355        del schema[tempkey] 
    321356     
    322357    if not _add_column_support: 
     
    337372            # Add the new column to the copy. 
    338373            dict.__setitem__(temptable, key, column) 
    339             # Bind the temp table to the DB
     374            # Bind the temp table to the DB (this will call CREATE TABLE)
    340375            self.schema[tempkey] = temptable 
    341376             
     
    353388             
    354389            # Copy data from the temp table to a new table for self. 
    355             self._copy_from_temp(temptable, self._parent_key(), tempkey) 
     390            self._copy_from_temp(temptable, tempkey) 
    356391     
    357392    def __delitem__(self, key): 
     
    369404        # Drop the column from the copy. 
    370405        dict.__delitem__(temptable, key) 
    371         # Bind the temp table to the DB
     406        # Bind the temp table to the DB (this will call CREATE TABLE)
    372407        self.schema[tempkey] = temptable 
    373408         
    374409        # Copy data from the old table to the temp table. 
    375         selfields = [] 
    376         for k, c in temptable.iteritems(): 
    377             qname = c.qname 
    378             selfields.append(qname) 
     410        selfields = [c.qname for c in c in temptable.itervalues()] 
    379411        self.schema.db.execute_ddl("INSERT INTO %s SELECT %s FROM %s;" % 
    380412                                   (temptable.qname, ", ".join(selfields), 
    381413                                    self.qname)) 
    382414         
    383         self._copy_from_temp(temptable, self._parent_key(), tempkey) 
     415        self._copy_from_temp(temptable, tempkey) 
    384416         
    385417        if column.autoincrement: 
     
    401433            # Make a temporary copy. 
    402434            tempkey, temptable = self._temp_copy() 
    403             # Bind the temp table to the DB
     435            # Bind the temp table to the DB (this will call CREATE TABLE)
    404436            self.schema[tempkey] = temptable 
    405437             
     
    415447                                        self.qname)) 
    416448             
    417             self._copy_from_temp(temptable, self._parent_key(), tempkey) 
     449            self._copy_from_temp(temptable, tempkey) 
    418450     
    419451    def _grab_new_ids(self, idkeys, conn): 
     
    423455            new_id = conn.sqlite_last_insert_rowid() 
    424456        return {idkeys[0]: new_id} 
     457     
     458    def add_primary(self): 
     459        """Set the PRIMARY KEY for this Table, using its Column.key values.""" 
     460        pk = [column.qname for column in self.itervalues() if column.key] 
     461        if pk: 
     462            # Make a temporary copy. 
     463            tempkey, temptable = self._temp_copy() 
     464            # Bind the temp table to the DB. 
     465            self.schema[tempkey] = temptable 
     466            # Copy data from the old table to the temp table. 
     467            selfields = [c.qname for c in temptable.itervalues()] 
     468            self.schema.db.execute_ddl("INSERT INTO %s SELECT %s FROM %s;" % 
     469                                       (temptable.qname, ", ".join(selfields), 
     470                                        self.qname)) 
     471            # Create the new table and copy data from the temp table to it. 
     472            self._copy_from_temp(temptable, tempkey) 
     473     
     474    def drop_primary(self): 
     475        """Remove any PRIMARY KEY for this Table.""" 
     476        # Make a temporary copy. 
     477        tempkey, temptable = self._temp_copy() 
     478        # Drop all .key's from the copy. 
     479        for col in temptable.itervalues(): 
     480            col.key = False 
     481        # Bind the temp table to the DB. 
     482        self.schema[tempkey] = temptable 
     483         
     484        # Copy data from the old table to the temp table. 
     485        selfields = [c.qname for c in temptable.itervalues()] 
     486        self.schema.db.execute_ddl("INSERT INTO %s SELECT %s FROM %s;" % 
     487                                   (temptable.qname, ", ".join(selfields), 
     488                                    self.qname)) 
     489        # Create the new table and copy data from the temp table to it. 
     490        self._copy_from_temp(temptable, tempkey) 
     491 
    425492 
    426493 
     
    605672        cols = [] 
    606673        for row in data: 
    607             dbtype = row[2].upper() 
    608             c = geniusql.Column(self.db.typeadapter.python_type(dbtype), dbtype, 
    609                                 row[4], {}, bool(row[5]), 
    610                                 row[1], self.db.quote(row[1])) 
     674            cid, name, dbtype, notnull, default, pk = row 
     675            dbtype = dbtype.upper() 
     676            c = geniusql.Column(self.db.typeadapter.python_type(dbtype), 
     677                                dbtype, default, {}, bool(pk), 
     678                                name, self.db.quote(name)) 
    611679             
    612680            # "A single row can hold up to 2 ** 30 bytes of data 
  • trunk/geniusql/test/zoo_fixture.py

    r22 r23  
    603603        ostrich = schema['Animal'].select(Species='Ostrich') 
    604604        self.assertEqual(ostrich, None) 
     605     
     606    def test_primary_key_support(self): 
     607        # Drop and re-add the PK on, oh, how about the Animal table? 
     608        schema['Animal'].drop_primary() 
     609        schema['Animal'].add_primary() 
    605610     
    606611    def test_insert_into(self):