Mercurial > p > roundup > code
diff roundup/backends/rdbms_common.py @ 2175:723098a10677
Export and import now include journals (incompatible with export < 0.7)
Need to check setting of activity in RDBMS imports.
Metakit import is quite possibly very busted in setjournal() - I didn't
even try to figure how to *clear the previous journal* for the journal
being imported.
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Fri, 02 Apr 2004 05:58:45 +0000 |
| parents | cd42c3c7173a |
| children | c52a931879c4 |
line wrap: on
line diff
--- a/roundup/backends/rdbms_common.py Thu Apr 01 00:36:24 2004 +0000 +++ b/roundup/backends/rdbms_common.py Fri Apr 02 05:58:45 2004 +0000 @@ -1,4 +1,4 @@ -# $Id: rdbms_common.py,v 1.87 2004-03-31 07:25:14 richard Exp $ +# $Id: rdbms_common.py,v 1.88 2004-04-02 05:58:45 richard Exp $ ''' Relational database (SQL) backend common code. Basics: @@ -679,10 +679,7 @@ self.arg, self.arg) self.sql(sql, (entry, nodeid)) - # make sure we do the commit-time extra stuff for this node - self.transactions.append((self.doSaveNode, (classname, nodeid, node))) - - def setnode(self, classname, nodeid, values, multilink_changes): + def setnode(self, classname, nodeid, values, multilink_changes={}): ''' Change the specified node. ''' if __debug__: @@ -736,7 +733,27 @@ print >>hyperdb.DEBUG, 'setnode', (self, sql, vals) self.cursor.execute(sql, vals) - # now the fun bit, updating the multilinks ;) + # we're probably coming from an import, not a change + if not multilink_changes: + for name in mls: + prop = props[name] + value = values[name] + + t = '%s_%s'%(classname, name) + + # clear out previous values for this node + # XXX numeric ids + self.sql('delete from %s where nodeid=%s'%(t, self.arg), + (nodeid,)) + + # insert the values for this node + for entry in values[name]: + sql = 'insert into %s (linkid, nodeid) values (%s,%s)'%(t, + self.arg, self.arg) + # XXX numeric ids + self.sql(sql, (entry, nodeid)) + + # we have multilink changes to apply for col, (add, remove) in multilink_changes.items(): tn = '%s_%s'%(classname, col) if add: @@ -752,9 +769,6 @@ # XXX numeric ids self.sql(sql, (int(nodeid), int(removeid))) - # make sure we do the commit-time extra stuff for this node - self.transactions.append((self.doSaveNode, (classname, nodeid, values))) - sql_to_hyperdb_value = { hyperdb.String : str, hyperdb.Date : lambda x:date.Date(str(x).replace(' ', '.')), @@ -887,11 +901,6 @@ 'link' or 'unlink' -- 'params' is (classname, nodeid, propname) 'retire' -- 'params' is None ''' - # serialise the parameters now if necessary - if isinstance(params, type({})): - if action in ('set', 'create'): - params = self.serialise(classname, params) - # handle supply of the special journalling parameters (usually # supplied on importing an existing database) if creator: @@ -904,7 +913,7 @@ journaldate = date.Date() # create the journal entry - cols = ','.join('nodeid date tag action params'.split()) + cols = 'nodeid,date,tag,action,params' if __debug__: print >>hyperdb.DEBUG, 'addjournal', (nodeid, journaldate, @@ -913,6 +922,22 @@ self.save_journal(classname, cols, nodeid, journaldate, journaltag, action, params) + def setjournal(self, classname, nodeid, journal): + '''Set the journal to the "journal" list.''' + # clear out any existing entries + self.sql('delete from %s__journal where nodeid=%s'%(classname, + self.arg), (nodeid,)) + + # create the journal entry + cols = 'nodeid,date,tag,action,params' + + for nodeid, journaldate, journaltag, action, params in journal: + if __debug__: + print >>hyperdb.DEBUG, 'setjournal', (nodeid, journaldate, + journaltag, action, params) + self.save_journal(classname, cols, nodeid, journaldate, + journaltag, action, params) + def getjournal(self, classname, nodeid): ''' get the journal for id ''' @@ -1024,12 +1049,6 @@ # clear the cache self.clearCache() - def doSaveNode(self, classname, nodeid, node): - ''' dummy that just generates a reindex event - ''' - # return the classname, nodeid so we reindex this content - return (classname, nodeid) - def sql_close(self): if __debug__: print >>hyperdb.DEBUG, '+++ close database connection +++' @@ -1253,114 +1272,6 @@ # XXX numeric ids return str(newid) - def export_list(self, propnames, nodeid): - ''' Export a node - generate a list of CSV-able data in the order - specified by propnames for the given node. - ''' - properties = self.getprops() - l = [] - for prop in propnames: - proptype = properties[prop] - value = self.get(nodeid, prop) - # "marshal" data where needed - if value is None: - pass - elif isinstance(proptype, hyperdb.Date): - value = value.get_tuple() - elif isinstance(proptype, hyperdb.Interval): - value = value.get_tuple() - elif isinstance(proptype, hyperdb.Password): - value = str(value) - l.append(repr(value)) - l.append(repr(self.is_retired(nodeid))) - return l - - def import_list(self, propnames, proplist): - ''' Import a node - all information including "id" is present and - should not be sanity checked. Triggers are not triggered. The - journal should be initialised using the "creator" and "created" - information. - - Return the nodeid of the node imported. - ''' - if self.db.journaltag is None: - raise DatabaseError, 'Database open read-only' - properties = self.getprops() - - # make the new node's property map - d = {} - retire = 0 - newid = None - for i in range(len(propnames)): - # Use eval to reverse the repr() used to output the CSV - value = eval(proplist[i]) - - # Figure the property for this column - propname = propnames[i] - - # "unmarshal" where necessary - if propname == 'id': - newid = value - continue - elif propname == 'is retired': - # is the item retired? - if int(value): - retire = 1 - continue - elif value is None: - d[propname] = None - continue - - prop = properties[propname] - if value is None: - # don't set Nones - continue - elif isinstance(prop, hyperdb.Date): - value = date.Date(value) - elif isinstance(prop, hyperdb.Interval): - value = date.Interval(value) - elif isinstance(prop, hyperdb.Password): - pwd = password.Password() - pwd.unpack(value) - value = pwd - d[propname] = value - - # get a new id if necessary - if newid is None: - newid = self.db.newid(self.classname) - - # add the node and journal - self.db.addnode(self.classname, newid, d) - - # retire? - if retire: - # use the arg for __retired__ to cope with any odd database type - # conversion (hello, sqlite) - sql = 'update _%s set __retired__=%s where id=%s'%(self.classname, - self.db.arg, self.db.arg) - if __debug__: - print >>hyperdb.DEBUG, 'retire', (self, sql, newid) - self.db.cursor.execute(sql, (1, newid)) - - # extract the extraneous journalling gumpf and nuke it - if d.has_key('creator'): - creator = d['creator'] - del d['creator'] - else: - creator = None - if d.has_key('creation'): - creation = d['creation'] - del d['creation'] - else: - creation = None - if d.has_key('activity'): - del d['activity'] - if d.has_key('actor'): - del d['actor'] - self.db.addjournal(self.classname, newid, 'create', {}, creator, - creation) - return newid - _marker = [] def get(self, nodeid, propname, default=_marker, cache=1): '''Get the value of a property on an existing node of this class. @@ -2246,6 +2157,159 @@ for react in self.reactors[action]: react(self.db, self, nodeid, oldvalues) + # + # import / export support + # + def export_list(self, propnames, nodeid): + ''' Export a node - generate a list of CSV-able data in the order + specified by propnames for the given node. + ''' + properties = self.getprops() + l = [] + for prop in propnames: + proptype = properties[prop] + value = self.get(nodeid, prop) + # "marshal" data where needed + if value is None: + pass + elif isinstance(proptype, hyperdb.Date): + value = value.get_tuple() + elif isinstance(proptype, hyperdb.Interval): + value = value.get_tuple() + elif isinstance(proptype, hyperdb.Password): + value = str(value) + l.append(repr(value)) + l.append(repr(self.is_retired(nodeid))) + return l + + def import_list(self, propnames, proplist): + ''' Import a node - all information including "id" is present and + should not be sanity checked. Triggers are not triggered. The + journal should be initialised using the "creator" and "created" + information. + + Return the nodeid of the node imported. + ''' + if self.db.journaltag is None: + raise DatabaseError, 'Database open read-only' + properties = self.getprops() + + # make the new node's property map + d = {} + retire = 0 + newid = None + for i in range(len(propnames)): + # Use eval to reverse the repr() used to output the CSV + value = eval(proplist[i]) + + # Figure the property for this column + propname = propnames[i] + + # "unmarshal" where necessary + if propname == 'id': + newid = value + continue + elif propname == 'is retired': + # is the item retired? + if int(value): + retire = 1 + continue + elif value is None: + d[propname] = None + continue + + prop = properties[propname] + if value is None: + # don't set Nones + continue + elif isinstance(prop, hyperdb.Date): + value = date.Date(value) + elif isinstance(prop, hyperdb.Interval): + value = date.Interval(value) + elif isinstance(prop, hyperdb.Password): + pwd = password.Password() + pwd.unpack(value) + value = pwd + d[propname] = value + + # get a new id if necessary + if newid is None or not self.hasnode(newid): + newid = self.db.newid(self.classname) + self.db.addnode(self.classname, newid, d) + else: + # update + self.db.setnode(self.classname, newid, d) + + # retire? + if retire: + # use the arg for __retired__ to cope with any odd database type + # conversion (hello, sqlite) + sql = 'update _%s set __retired__=%s where id=%s'%(self.classname, + self.db.arg, self.db.arg) + if __debug__: + print >>hyperdb.DEBUG, 'retire', (self, sql, newid) + self.db.cursor.execute(sql, (1, newid)) + return newid + + def export_journals(self): + '''Export a class's journal - generate a list of lists of + CSV-able data: + + nodeid, date, user, action, params + + No heading here - the columns are fixed. + ''' + properties = self.getprops() + r = [] + for nodeid in self.getnodeids(): + for nodeid, date, user, action, params in self.history(nodeid): + date = date.get_tuple() + if action == 'set': + for propname, value in params.items(): + prop = properties[propname] + # make sure the params are eval()'able + if value is None: + pass + elif isinstance(prop, Date): + value = value.get_tuple() + elif isinstance(prop, Interval): + value = value.get_tuple() + elif isinstance(prop, Password): + value = str(value) + params[propname] = value + l = [nodeid, date, user, action, params] + r.append(map(repr, l)) + return r + + def import_journals(self, entries): + '''Import a class's journal. + + Uses setjournal() to set the journal for each item.''' + properties = self.getprops() + d = {} + for l in entries: + l = map(eval, l) + nodeid, jdate, user, action, params = l + r = d.setdefault(nodeid, []) + if action == 'set': + for propname, value in params.items(): + prop = properties[propname] + if value is None: + pass + elif isinstance(prop, Date): + value = date.Date(value) + elif isinstance(prop, Interval): + value = date.Interval(value) + elif isinstance(prop, Password): + pwd = password.Password() + pwd.unpack(value) + value = pwd + params[propname] = value + r.append((nodeid, date.Date(jdate), user, action, params)) + + for nodeid, l in d.items(): + self.db.setjournal(self.classname, nodeid, l) + class FileClass(Class, hyperdb.FileClass): '''This class defines a large chunk of data. To support this, it has a mandatory String property "content" which is typically saved off
