index 36eb94bf195bc4fec40d0cecc3f2154451b59427..d9e2d582b349ce80246c6fabf125a0caa81c3358 100644 (file)
-# $Id: rdbms_common.py,v 1.67 2003-11-11 11:19:18 richard Exp $
+# $Id: rdbms_common.py,v 1.73 2004-01-20 03:58:38 richard Exp $
''' Relational database (SQL) backend common code.
Basics:
self.lockfile = None
# open a connection to the database, creating the "conn" attribute
- self.open_connection()
+ self.sql_open_connection()
def clearCache(self):
self.cache = {}
self.cache_lru = []
- def open_connection(self):
+ def sql_open_connection(self):
''' Open a connection to the database, creating it if necessary
'''
raise NotImplemented
def sql_fetchone(self):
''' Fetch a single row. If there's nothing to fetch, return None.
'''
- raise NotImplemented
+ return self.cursor.fetchone()
+
+ def sql_fetchall(self):
+ ''' Fetch all rows. If there's nothing to fetch, return [].
+ '''
+ return self.cursor.fetchall()
def sql_stringquote(self, value):
''' Quote the string so it's safe to put in the 'sql quotes'
def save_dbschema(self, schema):
''' Save the schema definition that the database currently implements
'''
- raise NotImplemented
+ s = repr(self.database_schema)
+ self.sql('insert into schema values (%s)', (s,))
def load_dbschema(self):
''' Load the schema definition that the database currently implements
'''
- raise NotImplemented
+ self.cursor.execute('select schema from schema')
+ return eval(self.cursor.fetchone()[0])
def post_init(self):
''' Called once the schema initialisation has finished.
self.conn.commit()
def refresh_database(self):
- # now detect changes in the schema
- for classname, spec in self.classes.items():
- dbspec = self.database_schema[classname]
- self.update_class(spec, dbspec, force=1)
- self.database_schema[classname] = spec.schema()
- # update the database version of the schema
- self.sql('delete from schema')
- self.save_dbschema(self.database_schema)
- # reindex the db
- self.reindex()
- # commit
- self.conn.commit()
-
+ self.post_init()
def reindex(self):
for klass in self.classes.values():
p = password.Password()
p.unpack(v)
d[k] = p
- elif (isinstance(prop, Boolean) or isinstance(prop, Number)) and v is not None:
- d[k]=float(v)
+ elif isinstance(prop, Boolean) and v is not None:
+ d[k] = int(v)
+ elif isinstance(prop, Number) and v is not None:
+ # try int first, then assume it's a float
+ try:
+ d[k] = int(v)
+ except ValueError:
+ d[k] = float(v)
else:
d[k] = v
return d
self.save_journal(classname, cols, nodeid, journaldate,
journaltag, action, params)
- def save_journal(self, classname, cols, nodeid, journaldate,
- journaltag, action, params):
- ''' Save the journal entry to the database
- '''
- raise NotImplemented
-
def getjournal(self, classname, nodeid):
''' get the journal for id
'''
cols = ','.join('nodeid date tag action params'.split())
return self.load_journal(classname, cols, nodeid)
+ 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)
+ entry = (nodeid, journaldate, journaltag, action, params)
+
+ # do the insert
+ a = self.arg
+ sql = 'insert into %s__journal (%s) values (%s,%s,%s,%s,%s)'%(classname,
+ cols, a, a, a, a, a)
+ if __debug__:
+ print >>hyperdb.DEBUG, 'addjournal', (self, sql, entry)
+ self.cursor.execute(sql, entry)
+
def load_journal(self, classname, cols, nodeid):
''' Load the journal from the database
'''
- raise NotImplemented
+ # now get the journal entries
+ sql = 'select %s from %s__journal where nodeid=%s'%(cols, classname,
+ self.arg)
+ if __debug__:
+ print >>hyperdb.DEBUG, 'load_journal', (self, sql, nodeid)
+ self.cursor.execute(sql, (nodeid,))
+ res = []
+ for nodeid, date_stamp, user, action, params in self.cursor.fetchall():
+ params = eval(params)
+ res.append((nodeid, date.Date(date_stamp), user, action, params))
+ return res
def pack(self, pack_before):
''' Delete all journal entries except "create" before 'pack_before'.
# clear out the transactions
self.transactions = []
+ def sql_rollback(self):
+ self.conn.rollback()
+
def rollback(self):
''' Reverse all actions from the current transaction.
if __debug__:
print >>hyperdb.DEBUG, 'rollback', (self,)
- # roll back
- self.conn.rollback()
+ self.sql_rollback()
# roll back "other" transaction stuff
for method, args in self.transactions:
# return the classname, nodeid so we reindex this content
return (classname, nodeid)
+ def sql_close(self):
+ self.conn.close()
+
def close(self):
''' Close off the connection.
'''
- self.conn.close()
+ self.sql_close()
if self.lockfile is not None:
locking.release_lock(self.lockfile)
if self.lockfile is not None:
elif isinstance(proptype, hyperdb.Password):
value = str(value)
l.append(repr(value))
- l.append(self.is_retired(nodeid))
+ l.append(repr(self.is_retired(nodeid)))
return l
def import_list(self, propnames, proplist):
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
print >>hyperdb.DEBUG, 'retire', (self, sql, newid)
self.db.cursor.execute(sql, (1, newid))
- # add the node and journal
- self.db.addnode(self.classname, newid, d)
-
# extract the extraneous journalling gumpf and nuke it
if d.has_key('creator'):
creator = d['creator']
return d[propname]
- def getnode(self, nodeid, cache=1):
- ''' Return a convenience wrapper for the node.
-
- 'nodeid' must be the id of an existing node of this class or an
- IndexError is raised.
-
- 'cache' exists for backwards compatibility, and is not used.
- '''
- return Node(self, nodeid)
-
def set(self, nodeid, **propvalues):
'''Modify a property on an existing node of this class.
'propspec' consists of keyword args propname=nodeid or
propname={nodeid:1, }
'propname' must be the name of a property in this class, or a
- KeyError is raised. That property must be a Link or Multilink
- property, or a TypeError is raised.
+ KeyError is raised. That property must be a Link or
+ Multilink property, or a TypeError is raised.
Any node in this class whose 'propname' property links to any of the
nodeids will be returned. Used by the full text indexing, which knows
raise TypeError, "'%s' not a Link/Multilink property"%propname
# first, links
- where = []
- allvalues = ()
a = self.db.arg
+ where = ['__retired__ <> %s'%a]
+ allvalues = (1,)
for prop, values in propspec:
if not isinstance(props[prop], hyperdb.Link):
continue
+ if type(values) is type({}) and len(values) == 1:
+ values = values.keys()[0]
if type(values) is type(''):
allvalues += (values,)
where.append('_%s = %s'%(prop, a))
s = ','.join([a]*len(values))
tables.append('select nodeid from %s_%s where linkid in (%s)'%(
self.classname, prop, s))
- sql = '\nunion\n'.join(tables)
+
+ sql = '\nintersect\n'.join(tables)
self.db.sql(sql, allvalues)
l = [x[0] for x in self.db.sql_fetchall()]
if __debug__:
args = []
for propname in requirements.keys():
prop = self.properties[propname]
- if isinstance(not prop, String):
+ if not isinstance(prop, String):
raise TypeError, "'%s' not a String property"%propname
where.append(propname)
args.append(requirements[propname].lower())
# generate the where clause
s = ' and '.join(['lower(_%s)=%s'%(col, self.db.arg) for col in where])
- sql = 'select id from _%s where %s'%(self.classname, s)
+ sql = 'select id from _%s where %s and __retired__=%s'%(self.classname,
+ s, self.db.arg)
+ args.append(0)
self.db.sql(sql, tuple(args))
l = [x[0] for x in self.db.sql_fetchall()]
if __debug__:
else:
# psycopg doesn't like empty args
self.db.cursor.execute(sql)
- l = self.db.cursor.fetchall()
+ l = self.db.sql_fetchall()
# return the IDs (the first column)
- # XXX The filter(None, l) bit is sqlite-specific... if there's _NO_
- # XXX matches to a fetch, it returns NULL instead of nothing!?!
- return filter(None, [row[0] for row in l])
+ return [row[0] for row in l]
def count(self):
'''Get the number of nodes in this class.