1515# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717#
18- #$Id: rdbms_common.py,v 1.193 2007-10-25 07:26:11 richard Exp $
18+ #$Id: rdbms_common.py,v 1.194 2008-02-07 03:28:34 richard Exp $
1919""" Relational database (SQL) backend common code.
2020
2121Basics:
2929- journals are stored adjunct to the per-class tables
3030- table names and columns have "_" prepended so the names can't clash with
3131 restricted names (like "order")
32- - retirement is determined by the __retired__ column being true
32+ - retirement is determined by the __retired__ column being > 0
3333
3434Database-specific changes may generally be pushed out to the overridable
3535sql_* methods, since everything else should be fairly generic. There's
4242that maps to a table. If that information differs from the hyperdb schema,
4343then we update it. We also store in the schema dict a version which
4444allows us to upgrade the database schema when necessary. See upgrade_db().
45+
46+ To force a unqiueness constraint on the key properties we put the item
47+ id into the __retired__ column duing retirement (so it's 0 for "active"
48+ items) and place a unqiueness constraint on key + __retired__. This is
49+ particularly important for the users class where multiple users may
50+ try to have the same username, with potentially many retired users with
51+ the same name.
4552"""
4653__docformat__ = 'restructuredtext'
4754
@@ -239,7 +246,8 @@ def post_init(self):
239246
240247 # update this number when we need to make changes to the SQL structure
241248 # of the backen database
242- current_db_version = 4
249+ current_db_version = 5
250+ db_version_updated = False
243251 def upgrade_db (self ):
244252 """ Update the SQL database to reflect changes in the backend code.
245253
@@ -272,7 +280,11 @@ def upgrade_db(self):
272280 if version < 4 :
273281 self .fix_version_3_tables ()
274282
283+ if version < 5 :
284+ self .fix_version_4_tables ()
285+
275286 self .database_schema ['version' ] = self .current_db_version
287+ self .db_version_updated = True
276288 return 1
277289
278290 def fix_version_3_tables (self ):
@@ -283,9 +295,21 @@ def fix_version_3_tables(self):
283295 self .sql ('ALTER TABLE %ss ADD %s_value TEXT' % (name , name ))
284296
285297 def fix_version_2_tables (self ):
286- """ Default (used by sqlite): NOOP"""
298+ # Default (used by sqlite): NOOP
287299 pass
288300
301+ def fix_version_4_tables (self ):
302+ # note this is an explicit call now
303+ c = self .cursor
304+ for cn , klass in self .classes .items ():
305+ c .execute ('select id from _%s where __retired__<>0' % (cn ,))
306+ for (id ,) in c .fetchall ():
307+ c .execute ('update _%s set __retired__=%s where id=%s' % (cn ,
308+ self .arg , self .arg ), (id , id ))
309+
310+ if klass .key :
311+ self .add_class_key_required_unique_constraint (cn , klass .key )
312+
289313 def _convert_journal_tables (self ):
290314 """Get current journal table contents, drop the table and re-create"""
291315 c = self .cursor
@@ -530,9 +554,18 @@ def create_class_table_indexes(self, spec):
530554 spec .classname , spec .key )
531555 self .sql (index_sql3 )
532556
557+ # and the unique index for key / retired(id)
558+ self .add_class_key_required_unique_constraint (spec .classname ,
559+ spec .key )
560+
533561 # TODO: create indexes on (selected?) Link property columns, as
534562 # they're more likely to be used for lookup
535563
564+ def add_class_key_required_unique_constraint (self , cn , key ):
565+ sql = '''create unique index _%s_key_retired_idx
566+ on _%s(__retired__, _%s)''' % (cn , cn , key )
567+ self .sql (sql )
568+
536569 def drop_class_table_indexes (self , cn , key ):
537570 # drop the old table indexes first
538571 l = ['_%s_id_idx' % cn , '_%s_retired_idx' % cn ]
@@ -555,10 +588,15 @@ def create_class_table_key_index(self, cn, key):
555588 def drop_class_table_key_index (self , cn , key ):
556589 table_name = '_%s' % cn
557590 index_name = '_%s_%s_idx' % (cn , key )
558- if not self .sql_index_exists (table_name , index_name ):
559- return
560- sql = 'drop index ' + index_name
561- self .sql (sql )
591+ if self .sql_index_exists (table_name , index_name ):
592+ sql = 'drop index ' + index_name
593+ self .sql (sql )
594+
595+ # and now the retired unique index too
596+ index_name = '_%s_key_retired_idx' % cn
597+ if self .sql_index_exists (table_name , index_name ):
598+ sql = 'drop index _%s_key_retired_idx' % cn
599+ self .sql (sql )
562600
563601 def create_journal_table (self , spec ):
564602 """ create the journal table for a class given the spec and
@@ -1760,7 +1798,7 @@ def retire(self, nodeid):
17601798 # conversion (hello, sqlite)
17611799 sql = 'update _%s set __retired__=%s where id=%s' % (self .classname ,
17621800 self .db .arg , self .db .arg )
1763- self .db .sql (sql , (1 , nodeid ))
1801+ self .db .sql (sql , (nodeid , nodeid ))
17641802 if self .do_journal :
17651803 self .db .addjournal (self .classname , nodeid , '' "retired" , None )
17661804
@@ -1802,7 +1840,7 @@ def is_retired(self, nodeid):
18021840 sql = 'select __retired__ from _%s where id=%s' % (self .classname ,
18031841 self .db .arg )
18041842 self .db .sql (sql , (nodeid ,))
1805- return int (self .db .sql_fetchone ()[0 ])
1843+ return int (self .db .sql_fetchone ()[0 ]) > 0
18061844
18071845 def destroy (self , nodeid ):
18081846 """Destroy a node.
@@ -1880,9 +1918,9 @@ def lookup(self, keyvalue):
18801918
18811919 # use the arg to handle any odd database type conversion (hello,
18821920 # sqlite)
1883- sql = "select id from _%s where _%s=%s and __retired__ <> %s" % (
1921+ sql = "select id from _%s where _%s=%s and __retired__= %s" % (
18841922 self .classname , self .key , self .db .arg , self .db .arg )
1885- self .db .sql (sql , (keyvalue , 1 ))
1923+ self .db .sql (sql , (keyvalue , 0 ))
18861924
18871925 # see if there was a result that's not retired
18881926 row = self .db .sql_fetchone ()
@@ -1947,8 +1985,8 @@ def find(self, **propspec):
19471985 s += '_%s in (%s)' % (prop , ',' .join ([a ]* len (values )))
19481986 where .append ('(' + s + ')' )
19491987 if where :
1950- allvalues = (1 , ) + allvalues
1951- sql .append ("""select id from _%s where __retired__ <> %s
1988+ allvalues = (0 , ) + allvalues
1989+ sql .append ("""select id from _%s where __retired__= %s
19521990 and %s""" % (self .classname , a , ' and ' .join (where )))
19531991
19541992 # now multilinks
@@ -1957,15 +1995,15 @@ def find(self, **propspec):
19571995 continue
19581996 if not values :
19591997 continue
1960- allvalues += (1 , )
1998+ allvalues += (0 , )
19611999 if type (values ) is type ('' ):
19622000 allvalues += (values ,)
19632001 s = a
19642002 else :
19652003 allvalues += tuple (values .keys ())
19662004 s = ',' .join ([a ]* len (values ))
19672005 tn = '%s_%s' % (self .classname , prop )
1968- sql .append ("""select id from _%s, %s where __retired__ <> %s
2006+ sql .append ("""select id from _%s, %s where __retired__= %s
19692007 and id = %s.nodeid and %s.linkid in (%s)""" % (self .classname ,
19702008 tn , a , tn , tn , s ))
19712009
@@ -1996,9 +2034,9 @@ def stringFind(self, **requirements):
19962034
19972035 # generate the where clause
19982036 s = ' and ' .join (['lower(_%s)=%s' % (col , self .db .arg ) for col in where ])
1999- sql = 'select id from _%s where %s and __retired__<> %s' % (
2037+ sql = 'select id from _%s where %s and __retired__= %s' % (
20002038 self .classname , s , self .db .arg )
2001- args .append (1 )
2039+ args .append (0 )
20022040 self .db .sql (sql , tuple (args ))
20032041 # XXX numeric ids
20042042 l = [str (x [0 ]) for x in self .db .sql_fetchall ()]
@@ -2017,12 +2055,13 @@ def getnodeids(self, retired=None):
20172055 """
20182056 # flip the sense of the 'retired' flag if we don't want all of them
20192057 if retired is not None :
2058+ args = (0 , )
20202059 if retired :
2021- args = ( 0 , )
2060+ compare = '>'
20222061 else :
2023- args = ( 1 , )
2024- sql = 'select id from _%s where __retired__ <> %s' % (self .classname ,
2025- self .db .arg )
2062+ compare = '='
2063+ sql = 'select id from _%s where __retired__%s %s' % (self .classname ,
2064+ compare , self .db .arg )
20262065 else :
20272066 args = ()
20282067 sql = 'select id from _%s' % self .classname
@@ -2276,7 +2315,7 @@ def filter(self, search_matches, filterspec, sort=[], group=[]):
22762315 props = self .getprops ()
22772316
22782317 # don't match retired nodes
2279- where .append ('_%s.__retired__ <> 1 ' % icn )
2318+ where .append ('_%s.__retired__=0 ' % icn )
22802319
22812320 # add results of full text search
22822321 if search_matches is not None :
@@ -2341,7 +2380,7 @@ def filter_sql(self, sql):
23412380 The SQL select must include the item id as the first column.
23422381
23432382 This function DOES NOT filter out retired items, add on a where
2344- clause "__retired__ <> 1 " if you don't want retired nodes.
2383+ clause "__retired__=0 " if you don't want retired nodes.
23452384 """
23462385 if __debug__ :
23472386 start_t = time .time ()
@@ -2502,7 +2541,7 @@ def import_list(self, propnames, proplist):
25022541 # conversion (hello, sqlite)
25032542 sql = 'update _%s set __retired__=%s where id=%s' % (self .classname ,
25042543 self .db .arg , self .db .arg )
2505- self .db .sql (sql , (1 , newid ))
2544+ self .db .sql (sql , (newid , newid ))
25062545 return newid
25072546
25082547 def export_journals (self ):
0 commit comments