diff roundup/backends/back_gadfly.py @ 1156:d0da32fbdedc

gadfly backend now complete can handle schema changes in non-Multilinks (though only one at a time at present)
author Richard Jones <richard@users.sourceforge.net>
date Mon, 16 Sep 2002 08:04:46 +0000
parents 81941abedb0a
children 14467c765167
line wrap: on
line diff
--- a/roundup/backends/back_gadfly.py	Mon Sep 16 06:51:13 2002 +0000
+++ b/roundup/backends/back_gadfly.py	Mon Sep 16 08:04:46 2002 +0000
@@ -1,4 +1,4 @@
-# $Id: back_gadfly.py,v 1.20 2002-09-15 23:06:20 richard Exp $
+# $Id: back_gadfly.py,v 1.21 2002-09-16 08:04:46 richard Exp $
 __doc__ = '''
 About Gadfly
 ============
@@ -148,15 +148,20 @@
                 klass.index(nodeid)
         self.indexer.save_index()
 
-    def determine_columns(self, spec):
+    def determine_columns(self, properties):
         ''' Figure the column names and multilink properties from the spec
+
+            "properties" is a list of (name, prop) where prop may be an
+            instance of a hyperdb "type" _or_ a string repr of that type.
         '''
         cols = []
         mls = []
         # add the multilinks separately
-        for col, prop in spec.properties.items():
+        for col, prop in properties:
             if isinstance(prop, Multilink):
                 mls.append(col)
+            elif isinstance(prop, type('')) and prop.find('Multilink') != -1:
+                mls.append(col)
             else:
                 cols.append('_'+col)
         cols.sort()
@@ -165,9 +170,6 @@
     def update_class(self, spec, dbspec):
         ''' Determine the differences between the current spec and the
             database version of the spec, and update where necessary
-
-            NOTE that this doesn't work for adding/deleting properties!
-             ... until gadfly grows an ALTER TABLE command, it's not going to!
         '''
         spec_schema = spec.schema()
         if spec_schema == dbspec:
@@ -197,29 +199,54 @@
         # now compare
         for propname in spec_propnames:
             prop = spec_props[propname]
-            if __debug__:
-                print >>hyperdb.DEBUG, 'update_class ...', `prop`
             if dbspec_props.has_key(propname) and prop==dbspec_props[propname]:
                 continue
             if __debug__:
-                print >>hyperdb.DEBUG, 'update_class', `prop`
+                print >>hyperdb.DEBUG, 'update_class ADD', (propname, prop)
 
             if not dbspec_props.has_key(propname):
                 # add the property
                 if isinstance(prop, Multilink):
-                    sql = 'create table %s_%s (linkid varchar, nodeid '\
-                        'varchar)'%(spec.classname, prop)
-                    if __debug__:
-                        print >>hyperdb.DEBUG, 'update_class', (self, sql)
-                    cursor.execute(sql)
-                else:
-                    # XXX gadfly doesn't have an ALTER TABLE command
-                    raise NotImplementedError
-                    sql = 'alter table _%s add column (_%s varchar)'%(
-                        spec.classname, propname)
-                    if __debug__:
-                        print >>hyperdb.DEBUG, 'update_class', (self, sql)
-                    cursor.execute(sql)
+                    # all we have to do here is create a new table, easy!
+                    self.create_multilink_table(cursor, spec, propname)
+                    continue
+
+                # no ALTER TABLE, so we:
+                # 1. pull out the data, including an extra None column
+                oldcols, x = self.determine_columns(dbspec[1])
+                oldcols.append('id')
+                oldcols.append('__retired__')
+                cn = spec.classname
+                sql = 'select %s,? from _%s'%(','.join(oldcols), cn)
+                if __debug__:
+                    print >>hyperdb.DEBUG, 'update_class', (self, sql, None)
+                cursor.execute(sql, (None,))
+                olddata = cursor.fetchall()
+
+                # 2. drop the old table
+                cursor.execute('drop table _%s'%cn)
+
+                # 3. create the new table
+                cols, mls = self.create_class_table(cursor, spec)
+                # ensure the new column is last
+                cols.remove('_'+propname)
+                assert oldcols == cols, "Column lists don't match!"
+                cols.append('_'+propname)
+
+                # 4. populate with the data from step one
+                s = ','.join(['?' for x in cols])
+                scols = ','.join(cols)
+                sql = 'insert into _%s (%s) values (%s)'%(cn, scols, s)
+
+                # GAH, nothing had better go wrong from here on in... but
+                # we have to commit the drop...
+                self.conn.commit()
+
+                # we're safe to insert now
+                if __debug__:
+                    print >>hyperdb.DEBUG, 'update_class', (self, sql, olddata)
+                cursor.execute(sql, olddata)
+
             else:
                 # modify the property
                 if __debug__:
@@ -232,7 +259,7 @@
             if spec_props.has_key(propname):
                 continue
             if __debug__:
-                print >>hyperdb.DEBUG, 'update_class', `prop`
+                print >>hyperdb.DEBUG, 'update_class REMOVE', `prop`
 
             # delete the property
             if isinstance(prop, Multilink):
@@ -241,32 +268,52 @@
                     print >>hyperdb.DEBUG, 'update_class', (self, sql)
                 cursor.execute(sql)
             else:
-                # XXX gadfly doesn't have an ALTER TABLE command
-                raise NotImplementedError
-                sql = 'alter table _%s delete column _%s'%(spec.classname,
-                    propname)
-                if __debug__:
-                    print >>hyperdb.DEBUG, 'update_class', (self, sql)
-                cursor.execute(sql)
+                # no ALTER TABLE, so we:
+                # 1. pull out the data, excluding the removed column
+                oldcols, x = self.determine_columns(spec.properties.items())
+                oldcols.append('id')
+                oldcols.append('__retired__')
+                # remove the missing column
+                oldcols.remove('_'+propname)
+                cn = spec.classname
+                sql = 'select %s from _%s'%(','.join(oldcols), cn)
+                cursor.execute(sql, (None,))
+                olddata = sql.fetchall()
 
-    def create_class(self, spec):
-        ''' Create a database table according to the given spec.
+                # 2. drop the old table
+                cursor.execute('drop table _%s'%cn)
+
+                # 3. create the new table
+                cols, mls = self.create_class_table(self, cursor, spec)
+                assert oldcols != cols, "Column lists don't match!"
+
+                # 4. populate with the data from step one
+                qs = ','.join(['?' for x in cols])
+                sql = 'insert into _%s values (%s)'%(cn, s)
+                cursor.execute(sql, olddata)
+
+    def create_class_table(self, cursor, spec):
+        ''' create the class table for the given spec
         '''
-        cols, mls = self.determine_columns(spec)
+        cols, mls = self.determine_columns(spec.properties.items())
 
         # add on our special columns
         cols.append('id')
         cols.append('__retired__')
 
-        cursor = self.conn.cursor()
-
         # create the base table
-        cols = ','.join(['%s varchar'%x for x in cols])
-        sql = 'create table _%s (%s)'%(spec.classname, cols)
+        scols = ','.join(['%s varchar'%x for x in cols])
+        sql = 'create table _%s (%s)'%(spec.classname, scols)
         if __debug__:
             print >>hyperdb.DEBUG, 'create_class', (self, sql)
         cursor.execute(sql)
 
+        return cols, mls
+
+    def create_journal_table(self, cursor, spec):
+        ''' create the journal table for a class given the spec and 
+            already-determined cols
+        '''
         # journal table
         cols = ','.join(['%s varchar'%x
             for x in 'nodeid date tag action params'.split()])
@@ -275,13 +322,26 @@
             print >>hyperdb.DEBUG, 'create_class', (self, sql)
         cursor.execute(sql)
 
+    def create_multilink_table(self, cursor, spec, ml):
+        ''' Create a multilink table for the "ml" property of the class
+            given by the spec
+        '''
+        sql = 'create table %s_%s (linkid varchar, nodeid varchar)'%(
+            spec.classname, ml)
+        if __debug__:
+            print >>hyperdb.DEBUG, 'create_class', (self, sql)
+        cursor.execute(sql)
+
+    def create_class(self, spec):
+        ''' Create a database table according to the given spec.
+        '''
+        cursor = self.conn.cursor()
+        cols, mls = self.create_class_table(cursor, spec)
+        self.create_journal_table(cursor, spec)
+
         # now create the multilink tables
         for ml in mls:
-            sql = 'create table %s_%s (linkid varchar, nodeid varchar)'%(
-                spec.classname, ml)
-            if __debug__:
-                print >>hyperdb.DEBUG, 'create_class', (self, sql)
-            cursor.execute(sql)
+            self.create_multilink_table(cursor, spec, ml)
 
         # ID counter
         sql = 'insert into ids (name, num) values (?,?)'
@@ -421,7 +481,7 @@
             print >>hyperdb.DEBUG, 'addnode', (self, classname, nodeid, node)
         # gadfly requires values for all non-multilink columns
         cl = self.classes[classname]
-        cols, mls = self.determine_columns(cl)
+        cols, mls = self.determine_columns(cl.properties.items())
 
         # default the non-multilink columns
         for col, prop in cl.properties.items():
@@ -513,7 +573,7 @@
             print >>hyperdb.DEBUG, 'getnode', (self, classname, nodeid)
         # figure the columns we're fetching
         cl = self.classes[classname]
-        cols, mls = self.determine_columns(cl)
+        cols, mls = self.determine_columns(cl.properties.items())
         scols = ','.join(cols)
 
         # perform the basic property fetch

Roundup Issue Tracker: http://roundup-tracker.org/