changeset 2244:ac4f295499a4

fixed journal marshalling in RDBMS backends [SF#943627] fixed handling of key values starting with numbers [SF#941363] fixed journal "param" column size in RDBMS backends
author Richard Jones <richard@users.sourceforge.net>
date Sun, 02 May 2004 23:16:05 +0000
parents 20507f6759fc
children c5fb778ec7e4
files CHANGES.txt doc/index.txt roundup/backends/back_mysql.py roundup/backends/back_sqlite.py roundup/backends/rdbms_common.py roundup/backends/sessions_rdbms.py roundup/cgi/actions.py roundup/cgi/client.py roundup/cgi/templating.py roundup/hyperdb.py templates/classic/dbinit.py
diffstat 11 files changed, 87 insertions(+), 41 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGES.txt	Sat May 01 08:19:00 2004 +0000
+++ b/CHANGES.txt	Sun May 02 23:16:05 2004 +0000
@@ -10,6 +10,9 @@
 - fix nested scope bug in rdbms multilink sorting
 - re-seed the random number generator for each request
 - postgresql backend altered to not use popen (thanks Georges Martin)
+- fixed journal marshalling in RDBMS backends (sf bug 943627)
+- fixed handling of key values starting with numbers (sf bug 941363)
+- fixed journal "param" column size in RDBMS backends
 
 
 2004-04-18 0.7.0b3
--- a/doc/index.txt	Sat May 01 08:19:00 2004 +0000
+++ b/doc/index.txt	Sun May 02 23:16:05 2004 +0000
@@ -102,6 +102,7 @@
 Georges Martin,
 Gordon McMillan,
 John F Meinel Jr,
+Truls E. Nęss,
 Patrick Ohly,
 Luke Opperman,
 Will Partain,
--- a/roundup/backends/back_mysql.py	Sat May 01 08:19:00 2004 +0000
+++ b/roundup/backends/back_mysql.py	Sun May 02 23:16:05 2004 +0000
@@ -130,6 +130,7 @@
         hyperdb.Password  : str,
         hyperdb.Boolean   : int,
         hyperdb.Number    : lambda x: x,
+        hyperdb.Multilink : lambda x: x,    # used in journal marshalling
     }
 
     def sql_open_connection(self):
@@ -302,13 +303,14 @@
                     print >>hyperdb.DEBUG, '... data', entry
                 execute(sql, tuple(entry))
 
-            # now load up the old journal data
+            # now load up the old journal data to migrate it
             cols = ','.join('nodeid date tag action params'.split())
             sql = 'select %s from %s__journal'%(cols, cn)
             if __debug__:
                 print >>hyperdb.DEBUG, 'migration', (self, sql)
             execute(sql)
 
+            # data conversions
             olddata = []
             for nodeid, journaldate, journaltag, action, params in \
                     self.cursor.fetchall():
@@ -394,7 +396,7 @@
             for x in 'nodeid date tag action params'.split()])
         sql = '''create table %s__journal (
             nodeid integer, date timestamp, tag varchar(255),
-            action varchar(255), params varchar(255)) type=%s'''%(
+            action varchar(255), params text) type=%s'''%(
             spec.classname, self.mysql_backend)
         if __debug__:
             print >>hyperdb.DEBUG, 'create_journal_table', (self, sql)
--- a/roundup/backends/back_sqlite.py	Sat May 01 08:19:00 2004 +0000
+++ b/roundup/backends/back_sqlite.py	Sun May 02 23:16:05 2004 +0000
@@ -1,4 +1,4 @@
-# $Id: back_sqlite.py,v 1.26 2004-04-18 23:05:18 richard Exp $
+# $Id: back_sqlite.py,v 1.27 2004-05-02 23:16:05 richard Exp $
 '''Implements a backend for SQLite.
 
 See https://pysqlite.sourceforge.net/ for pysqlite info
@@ -36,6 +36,7 @@
         hyperdb.Password  : str,
         hyperdb.Boolean   : int,
         hyperdb.Number    : lambda x: x,
+        hyperdb.Multilink : lambda x: x,    # used in journal marshalling
     }
     sql_to_hyperdb_value = {
         hyperdb.String : str,
@@ -45,6 +46,7 @@
         hyperdb.Password  : lambda x: password.Password(encrypted=x),
         hyperdb.Boolean   : int,
         hyperdb.Number    : rdbms_common._num_cvt,
+        hyperdb.Multilink : lambda x: x,    # used in journal marshalling
     }
 
     def sql_open_connection(self):
--- a/roundup/backends/rdbms_common.py	Sat May 01 08:19:00 2004 +0000
+++ b/roundup/backends/rdbms_common.py	Sun May 02 23:16:05 2004 +0000
@@ -1,4 +1,4 @@
-# $Id: rdbms_common.py,v 1.95 2004-04-26 20:59:25 richard Exp $
+# $Id: rdbms_common.py,v 1.96 2004-05-02 23:16:05 richard Exp $
 ''' Relational database (SQL) backend common code.
 
 Basics:
@@ -438,7 +438,7 @@
             for x in 'nodeid date tag action params'.split()])
         sql = '''create table %s__journal (
             nodeid integer, date timestamp, tag varchar(255),
-            action varchar(255), params varchar(25))'''%spec.classname
+            action varchar(255), params text)'''%spec.classname
         if __debug__:
             print >>hyperdb.DEBUG, 'create_journal_table', (self, sql)
         self.cursor.execute(sql)
@@ -619,12 +619,14 @@
 
     hyperdb_to_sql_value = {
         hyperdb.String : str,
+        # fractional seconds by default
         hyperdb.Date   : lambda x: x.formal(sep=' ', sec='%.3f'),
         hyperdb.Link   : int,
         hyperdb.Interval  : str,
         hyperdb.Password  : str,
         hyperdb.Boolean   : lambda x: x and 'TRUE' or 'FALSE',
         hyperdb.Number    : lambda x: x,
+        hyperdb.Multilink : lambda x: x,    # used in journal marshalling
     }
     def addnode(self, classname, nodeid, node):
         ''' Add the specified node to its class's db.
@@ -820,6 +822,7 @@
         hyperdb.Password  : lambda x: password.Password(encrypted=x),
         hyperdb.Boolean   : int,
         hyperdb.Number    : _num_cvt,
+        hyperdb.Multilink : lambda x: x,    # used in journal marshalling
     }
     def getnode(self, classname, nodeid):
         ''' Get a node from the database.
@@ -973,6 +976,26 @@
         if __debug__:
             print >>hyperdb.DEBUG, 'addjournal', (nodeid, journaldate,
                 journaltag, action, params)
+    
+        # make the journalled data marshallable
+        if isinstance(params, type({})):
+            properties = self.getclass(classname).getprops()
+            for param, value in params.items():
+                property = properties[param]
+                cvt = self.hyperdb_to_sql_value[property.__class__]
+                if isinstance(property, Password):
+                    params[param] = cvt(value)
+                elif isinstance(property, Date):
+                    params[param] = cvt(value)
+                elif isinstance(property, Interval):
+                    params[param] = cvt(value)
+                elif isinstance(property, Boolean):
+                    params[param] = cvt(value)
+
+        params = repr(params)
+
+        dc = self.hyperdb_to_sql_value[hyperdb.Date]
+        journaldate = dc(journaldate)
 
         self.save_journal(classname, cols, nodeid, journaldate,
             journaltag, action, params)
@@ -1001,16 +1024,34 @@
             raise IndexError, '%s has no node %s'%(classname, nodeid)
 
         cols = ','.join('nodeid date tag action params'.split())
-        return self.load_journal(classname, cols, nodeid)
+        journal = self.load_journal(classname, cols, nodeid)
+
+        # now unmarshal the data
+        dc = self.sql_to_hyperdb_value[hyperdb.Date]
+        res = []
+        properties = self.getclass(classname).getprops()
+        for nodeid, date_stamp, user, action, params in journal:
+            params = eval(params)
+            for param, value in params.items():
+                property = properties[param]
+                cvt = self.sql_to_hyperdb_value[property.__class__]
+                if isinstance(property, Password):
+                    params[param] = cvt(value)
+                elif isinstance(property, Date):
+                    params[param] = cvt(value)
+                elif isinstance(property, Interval):
+                    params[param] = cvt(value)
+                elif isinstance(property, Boolean):
+                    params[param] = cvt(value)
+            # XXX numeric ids
+            res.append((str(nodeid), dc(date_stamp), user, action, params))
+        return res
 
     def save_journal(self, classname, cols, nodeid, journaldate,
             journaltag, action, params):
         ''' Save the journal entry to the database
         '''
-        # make the params db-friendly
-        params = repr(params)
-        dc = self.hyperdb_to_sql_value[hyperdb.Date]
-        entry = (nodeid, dc(journaldate), journaltag, action, params)
+        entry = (nodeid, journaldate, journaltag, action, params)
 
         # do the insert
         a = self.arg
@@ -1029,13 +1070,7 @@
         if __debug__:
             print >>hyperdb.DEBUG, 'load_journal', (self, sql, nodeid)
         self.cursor.execute(sql, (nodeid,))
-        res = []
-        dc = self.sql_to_hyperdb_value[hyperdb.Date]
-        for nodeid, date_stamp, user, action, params in self.cursor.fetchall():
-            params = eval(params)
-            # XXX numeric ids
-            res.append((str(nodeid), dc(date_stamp), user, action, params))
-        return res
+        return self.cursor.fetchall()
 
     def pack(self, pack_before):
         ''' Delete all journal entries except "create" before 'pack_before'.
--- a/roundup/backends/sessions_rdbms.py	Sat May 01 08:19:00 2004 +0000
+++ b/roundup/backends/sessions_rdbms.py	Sun May 02 23:16:05 2004 +0000
@@ -1,4 +1,4 @@
-#$Id: sessions_rdbms.py,v 1.2 2004-03-31 23:08:39 richard Exp $
+#$Id: sessions_rdbms.py,v 1.3 2004-05-02 23:16:05 richard Exp $
 """This module defines a very basic store that's used by the CGI interface
 to store session and one-time-key information.
 
@@ -25,7 +25,7 @@
         n = self.name
         self.cursor.execute('select count(*) from %ss where %s_key=%s'%(n,
             n, self.db.arg), (infoid,))
-        return self.cursor.fetchone()[0]
+        return int(self.cursor.fetchone()[0])
 
     _marker = []
     def get(self, infoid, value, default=_marker):
--- a/roundup/cgi/actions.py	Sat May 01 08:19:00 2004 +0000
+++ b/roundup/cgi/actions.py	Sun May 02 23:16:05 2004 +0000
@@ -1,4 +1,4 @@
-#$Id: actions.py,v 1.23 2004-04-05 06:13:42 richard Exp $
+#$Id: actions.py,v 1.24 2004-05-02 23:16:05 richard Exp $
 
 import re, cgi, StringIO, urllib, Cookie, time, random
 
@@ -494,7 +494,8 @@
         try:
             message = self._editnodes(props, links)
         except (ValueError, KeyError, IndexError, exceptions.Reject), message:
-            self.client.error_message.append(_('Apply Error: ') + str(message))
+            import traceback;traceback.print_exc()
+            self.client.error_message.append(_('Edit Error: ') + str(message))
             return
 
         # commit now that all the tricky stuff is done
@@ -532,7 +533,6 @@
         try:
             # when it hits the None element, it'll set self.nodeid
             messages = self._editnodes(props, links)
-
         except (ValueError, KeyError, IndexError, exceptions.Reject), message:
             # these errors might just be indicative of user dumbness
             self.client.error_message.append(_('Error: ') + str(message))
--- a/roundup/cgi/client.py	Sat May 01 08:19:00 2004 +0000
+++ b/roundup/cgi/client.py	Sun May 02 23:16:05 2004 +0000
@@ -1,4 +1,4 @@
-# $Id: client.py,v 1.173 2004-04-25 22:19:15 richard Exp $
+# $Id: client.py,v 1.174 2004-05-02 23:16:05 richard Exp $
 
 """WWW request handler (also used in the stand-alone server).
 """
@@ -624,13 +624,13 @@
         """
         sessions = self.db.getSessionManager()
 
-        # generate a session key
-        s = '%s%s'%(time.time(), random.random())
-        print s
-        self.session = binascii.b2a_base64(s).strip()
-        while sessions.exists(self.session):
+        # generate a unique session key
+        while 1:
             s = '%s%s'%(time.time(), random.random())
-            self.session = binascii.b2a_base64(s).strip()
+            s = binascii.b2a_base64(s).strip()
+            if not sessions.exists(s):
+                break
+        self.session = s
 
         # clean up the base64
         if self.session[-1] == '=':
--- a/roundup/cgi/templating.py	Sat May 01 08:19:00 2004 +0000
+++ b/roundup/cgi/templating.py	Sun May 02 23:16:05 2004 +0000
@@ -289,10 +289,11 @@
         for item in l:
             if item == 'user':
                 m.append(HTMLUserClass(self._client, item))
-            m.append(HTMLClass(self._client, item))
+            else:
+                m.append(HTMLClass(self._client, item))
         return m
 
-def lookupIds(db, prop, ids, fail_ok=0, num_re=re.compile('-?\d+')):
+def lookupIds(db, prop, ids, fail_ok=0, num_re=re.compile('^-?\d+$')):
     ''' "fail_ok" should be specified if we wish to pass through bad values
         (most likely form values that we wish to represent back to the user)
     '''
@@ -310,7 +311,7 @@
                     l.append(entry)
     return l
 
-def lookupKeys(linkcl, key, ids, num_re=re.compile('-?\d+')):
+def lookupKeys(linkcl, key, ids, num_re=re.compile('^-?\d+$')):
     ''' Look up the "key" values for "ids" list - though some may already
     be key values, not ids.
     '''
@@ -449,7 +450,7 @@
         ''' Return this class' designator (classname) '''
         return self._classname
 
-    def getItem(self, itemid, num_re=re.compile('-?\d+')):
+    def getItem(self, itemid, num_re=re.compile('^-?\d+$')):
         ''' Get an item of this class by its item id.
         '''
         # make sure we're looking at an itemid
@@ -680,7 +681,7 @@
         # XXX do this
         return []
 
-    def history(self, direction='descending', dre=re.compile('\d+')):
+    def history(self, direction='descending', dre=re.compile('^\d+$')):
         self.view_check()
 
         l = ['<table class="history">'
@@ -1523,6 +1524,8 @@
     def __init__(self, *args, **kwargs):
         HTMLProperty.__init__(self, *args, **kwargs)
         if self._value:
+            self._value = lookupIds(self._db, self._prop, self._value,
+                fail_ok=1)
             sortfun = make_sort_function(self._db, self._prop.classname)
             self._value.sort(sortfun)
     
--- a/roundup/hyperdb.py	Sat May 01 08:19:00 2004 +0000
+++ b/roundup/hyperdb.py	Sun May 02 23:16:05 2004 +0000
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: hyperdb.py,v 1.96 2004-02-11 23:55:08 richard Exp $
+# $Id: hyperdb.py,v 1.97 2004-05-02 23:16:05 richard Exp $
 
 """Hyperdatabase implementation, especially field types.
 """
@@ -592,7 +592,7 @@
     ''' Error converting a raw value into a Hyperdb value '''
     pass
 
-def convertLinkValue(db, propname, prop, value, idre=re.compile('\d+')):
+def convertLinkValue(db, propname, prop, value, idre=re.compile('^\d+$')):
     ''' Convert the link value (may be id or key value) to an id value. '''
     linkcl = db.classes[prop.classname]
     if not idre.match(value):
--- a/templates/classic/dbinit.py	Sat May 01 08:19:00 2004 +0000
+++ b/templates/classic/dbinit.py	Sun May 02 23:16:05 2004 +0000
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: dbinit.py,v 1.7 2004-03-26 04:50:50 richard Exp $
+# $Id: dbinit.py,v 1.8 2004-05-02 23:16:05 richard Exp $
 
 import os
 
@@ -150,11 +150,11 @@
     return db
  
 def init(adminpw): 
-    ''' as from the roundupdb method initDB 
+    '''Invoked by the "roundup-admin initialise" command to set up the
+    initial state of the hyperdb.
  
-    Open the new database, and add new nodes - used for initialisation. You
-    can edit this before running the "roundup-admin initialise" command to
-    change the initial database entries.
+    If you wish to change the hyperdb *after* running that command, see
+    the customisation doc "Database Content" section.
     ''' 
     dbdir = os.path.join(config.DATABASE, 'files')
     if not os.path.isdir(dbdir):

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