Skip to content

Commit a966edf

Browse files
author
Richard Jones
committed
Implemented the destroy() method needed by the session database...
(and possibly others). At the same time, I removed the leading underscores from the hyperdb methods that Really Didn't Need Them. The journal also raises IndexError now for all situations where there is a request for the journal of a node that doesn't have one. It used to return [] in _some_ situations, but not all. This _may_ break code, but the tests pass...
1 parent 77e58ca commit a966edf

File tree

6 files changed

+271
-76
lines changed

6 files changed

+271
-76
lines changed

roundup/backends/back_anydbm.py

Lines changed: 150 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
#$Id: back_anydbm.py,v 1.51 2002-07-18 23:07:08 richard Exp $
18+
#$Id: back_anydbm.py,v 1.52 2002-07-19 03:36:34 richard Exp $
1919
'''
2020
This module defines a backend that saves the hyperdatabase in a database
2121
chosen by anydbm. It is guaranteed to always be available in python
@@ -63,6 +63,7 @@ def __init__(self, config, journaltag=None):
6363
self.cache = {} # cache of nodes loaded or created
6464
self.dirtynodes = {} # keep track of the dirty nodes by class
6565
self.newnodes = {} # keep track of the new nodes by class
66+
self.destroyednodes = {}# keep track of the destroyed nodes by class
6667
self.transactions = []
6768
self.indexer = Indexer(self.dir)
6869
# ensure files are group readable and writable
@@ -141,7 +142,7 @@ def getclassdb(self, classname, mode='r'):
141142
'''
142143
if __debug__:
143144
print >>hyperdb.DEBUG, 'getclassdb', (self, classname, mode)
144-
return self._opendb('nodes.%s'%classname, mode)
145+
return self.opendb('nodes.%s'%classname, mode)
145146

146147
def determine_db_type(self, path):
147148
''' determine which DB wrote the class file
@@ -157,12 +158,12 @@ def determine_db_type(self, path):
157158
db_type = 'dbm'
158159
return db_type
159160

160-
def _opendb(self, name, mode):
161+
def opendb(self, name, mode):
161162
'''Low-level database opener that gets around anydbm/dbm
162163
eccentricities.
163164
'''
164165
if __debug__:
165-
print >>hyperdb.DEBUG, '_opendb', (self, name, mode)
166+
print >>hyperdb.DEBUG, 'opendb', (self, name, mode)
166167

167168
# figure the class db type
168169
path = os.path.join(os.getcwd(), self.dir, name)
@@ -171,7 +172,7 @@ def _opendb(self, name, mode):
171172
# new database? let anydbm pick the best dbm
172173
if not db_type:
173174
if __debug__:
174-
print >>hyperdb.DEBUG, "_opendb anydbm.open(%r, 'n')"%path
175+
print >>hyperdb.DEBUG, "opendb anydbm.open(%r, 'n')"%path
175176
return anydbm.open(path, 'n')
176177

177178
# open the database with the correct module
@@ -182,11 +183,11 @@ def _opendb(self, name, mode):
182183
"Couldn't open database - the required module '%s'"\
183184
" is not available"%db_type
184185
if __debug__:
185-
print >>hyperdb.DEBUG, "_opendb %r.open(%r, %r)"%(db_type, path,
186+
print >>hyperdb.DEBUG, "opendb %r.open(%r, %r)"%(db_type, path,
186187
mode)
187188
return dbm.open(path, mode)
188189

189-
def _lockdb(self, name):
190+
def lockdb(self, name):
190191
''' Lock a database file
191192
'''
192193
path = os.path.join(os.getcwd(), self.dir, '%s.lock'%name)
@@ -199,8 +200,8 @@ def newid(self, classname):
199200
''' Generate a new id for the given class
200201
'''
201202
# open the ids DB - create if if doesn't exist
202-
lock = self._lockdb('_ids')
203-
db = self._opendb('_ids', 'c')
203+
lock = self.lockdb('_ids')
204+
db = self.opendb('_ids', 'c')
204205
if db.has_key(classname):
205206
newid = db[classname] = str(int(db[classname]) + 1)
206207
else:
@@ -239,7 +240,7 @@ def savenode(self, classname, nodeid, node):
239240
'''
240241
if __debug__:
241242
print >>hyperdb.DEBUG, 'savenode', (self, classname, nodeid, node)
242-
self.transactions.append((self._doSaveNode, (classname, nodeid, node)))
243+
self.transactions.append((self.doSaveNode, (classname, nodeid, node)))
243244

244245
def getnode(self, classname, nodeid, db=None, cache=1):
245246
''' get a node from the database
@@ -264,6 +265,11 @@ def getnode(self, classname, nodeid, db=None, cache=1):
264265
if not db.has_key(nodeid):
265266
raise IndexError, "no such %s %s"%(classname, nodeid)
266267

268+
# check the uncommitted, destroyed nodes
269+
if (self.destroyednodes.has_key(classname) and
270+
self.destroyednodes[classname].has_key(nodeid)):
271+
raise IndexError, "no such %s %s"%(classname, nodeid)
272+
267273
# decode
268274
res = marshal.loads(db[nodeid])
269275

@@ -276,6 +282,32 @@ def getnode(self, classname, nodeid, db=None, cache=1):
276282

277283
return res
278284

285+
def destroynode(self, classname, nodeid):
286+
'''Remove a node from the database. Called exclusively by the
287+
destroy() method on Class.
288+
'''
289+
if __debug__:
290+
print >>hyperdb.DEBUG, 'destroynode', (self, classname, nodeid)
291+
292+
# remove from cache and newnodes if it's there
293+
if (self.cache.has_key(classname) and
294+
self.cache[classname].has_key(nodeid)):
295+
del self.cache[classname][nodeid]
296+
if (self.newnodes.has_key(classname) and
297+
self.newnodes[classname].has_key(nodeid)):
298+
del self.newnodes[classname][nodeid]
299+
300+
# see if there's any obvious commit actions that we should get rid of
301+
for entry in self.transactions[:]:
302+
if entry[1][:2] == (classname, nodeid):
303+
self.transactions.remove(entry)
304+
305+
# add to the destroyednodes map
306+
self.destroyednodes.setdefault(classname, {})[nodeid] = 1
307+
308+
# add the destroy commit action
309+
self.transactions.append((self.doDestroyNode, (classname, nodeid)))
310+
279311
def serialise(self, classname, node):
280312
'''Copy the node contents, converting non-marshallable data into
281313
marshallable data.
@@ -357,8 +389,14 @@ def hasnode(self, classname, nodeid, db=None):
357389
def countnodes(self, classname, db=None):
358390
if __debug__:
359391
print >>hyperdb.DEBUG, 'countnodes', (self, classname, db)
360-
# include the new nodes not saved to the DB yet
361-
count = len(self.newnodes.get(classname, {}))
392+
393+
count = 0
394+
395+
# include the uncommitted nodes
396+
if self.newnodes.has_key(classname):
397+
count += len(self.newnodes[classname])
398+
if self.destroyednodes.has_key(classname):
399+
count -= len(self.destroyednodes[classname])
362400

363401
# and count those in the DB
364402
if db is None:
@@ -369,12 +407,23 @@ def countnodes(self, classname, db=None):
369407
def getnodeids(self, classname, db=None):
370408
if __debug__:
371409
print >>hyperdb.DEBUG, 'getnodeids', (self, classname, db)
410+
411+
res = []
412+
372413
# start off with the new nodes
373-
res = self.newnodes.get(classname, {}).keys()
414+
if self.newnodes.has_key(classname):
415+
res += self.newnodes[classname].keys()
374416

375417
if db is None:
376418
db = self.getclassdb(classname)
377419
res = res + db.keys()
420+
421+
# remove the uncommitted, destroyed nodes
422+
if self.destroyednodes.has_key(classname):
423+
for nodeid in self.destroyednodes[classname].keys():
424+
if db.has_key(nodeid):
425+
res.remove(nodeid)
426+
378427
return res
379428

380429

@@ -396,33 +445,36 @@ def addjournal(self, classname, nodeid, action, params):
396445
if __debug__:
397446
print >>hyperdb.DEBUG, 'addjournal', (self, classname, nodeid,
398447
action, params)
399-
self.transactions.append((self._doSaveJournal, (classname, nodeid,
448+
self.transactions.append((self.doSaveJournal, (classname, nodeid,
400449
action, params)))
401450

402451
def getjournal(self, classname, nodeid):
403452
''' get the journal for id
453+
454+
Raise IndexError if the node doesn't exist (as per history()'s
455+
API)
404456
'''
405457
if __debug__:
406458
print >>hyperdb.DEBUG, 'getjournal', (self, classname, nodeid)
407459
# attempt to open the journal - in some rare cases, the journal may
408460
# not exist
409461
try:
410-
db = self._opendb('journals.%s'%classname, 'r')
462+
db = self.opendb('journals.%s'%classname, 'r')
411463
except anydbm.error, error:
412-
if str(error) == "need 'c' or 'n' flag to open new db": return []
413-
elif error.args[0] != 2: raise
414-
return []
464+
if str(error) == "need 'c' or 'n' flag to open new db":
465+
raise IndexError, 'no such %s %s'%(classname, nodeid)
466+
elif error.args[0] != 2:
467+
raise
468+
raise IndexError, 'no such %s %s'%(classname, nodeid)
415469
try:
416470
journal = marshal.loads(db[nodeid])
417471
except KeyError:
418472
db.close()
419-
raise KeyError, 'no such %s %s'%(classname, nodeid)
473+
raise IndexError, 'no such %s %s'%(classname, nodeid)
420474
db.close()
421475
res = []
422-
for entry in journal:
423-
(nodeid, date_stamp, user, action, params) = entry
424-
date_obj = date.Date(date_stamp)
425-
res.append((nodeid, date_obj, user, action, params))
476+
for nodeid, date_stamp, user, action, params in journal:
477+
res.append((nodeid, date.Date(date_stamp), user, action, params))
426478
return res
427479

428480
def pack(self, pack_before):
@@ -440,7 +492,7 @@ def pack(self, pack_before):
440492
db_name = 'journals.%s'%classname
441493
path = os.path.join(os.getcwd(), self.dir, classname)
442494
db_type = self.determine_db_type(path)
443-
db = self._opendb(db_name, 'w')
495+
db = self.opendb(db_name, 'w')
444496

445497
for key in db.keys():
446498
journal = marshal.loads(db[key])
@@ -503,27 +555,41 @@ def commit(self):
503555
self.cache = {}
504556
self.dirtynodes = {}
505557
self.newnodes = {}
558+
self.destroyednodes = {}
506559
self.transactions = []
507560

508-
def _doSaveNode(self, classname, nodeid, node):
561+
def getCachedClassDB(self, classname):
562+
''' get the class db, looking in our cache of databases for commit
563+
'''
564+
# get the database handle
565+
db_name = 'nodes.%s'%classname
566+
if not self.databases.has_key(db_name):
567+
self.databases[db_name] = self.getclassdb(classname, 'c')
568+
return self.databases[db_name]
569+
570+
def doSaveNode(self, classname, nodeid, node):
509571
if __debug__:
510-
print >>hyperdb.DEBUG, '_doSaveNode', (self, classname, nodeid,
572+
print >>hyperdb.DEBUG, 'doSaveNode', (self, classname, nodeid,
511573
node)
512574

513-
# get the database handle
514-
db_name = 'nodes.%s'%classname
515-
if self.databases.has_key(db_name):
516-
db = self.databases[db_name]
517-
else:
518-
db = self.databases[db_name] = self.getclassdb(classname, 'c')
575+
db = self.getCachedClassDB(classname)
519576

520577
# now save the marshalled data
521578
db[nodeid] = marshal.dumps(self.serialise(classname, node))
522579

523580
# return the classname, nodeid so we reindex this content
524581
return (classname, nodeid)
525582

526-
def _doSaveJournal(self, classname, nodeid, action, params):
583+
def getCachedJournalDB(self, classname):
584+
''' get the journal db, looking in our cache of databases for commit
585+
'''
586+
# get the database handle
587+
db_name = 'journals.%s'%classname
588+
if not self.databases.has_key(db_name):
589+
self.databases[db_name] = self.opendb(db_name, 'c')
590+
return self.databases[db_name]
591+
592+
def doSaveJournal(self, classname, nodeid, action, params):
527593
# serialise first
528594
if action in ('set', 'create'):
529595
params = self.serialise(classname, params)
@@ -533,14 +599,9 @@ def _doSaveJournal(self, classname, nodeid, action, params):
533599
params)
534600

535601
if __debug__:
536-
print >>hyperdb.DEBUG, '_doSaveJournal', entry
602+
print >>hyperdb.DEBUG, 'doSaveJournal', entry
537603

538-
# get the database handle
539-
db_name = 'journals.%s'%classname
540-
if self.databases.has_key(db_name):
541-
db = self.databases[db_name]
542-
else:
543-
db = self.databases[db_name] = self._opendb(db_name, 'c')
604+
db = self.getCachedJournalDB(classname)
544605

545606
# now insert the journal entry
546607
if db.has_key(nodeid):
@@ -553,18 +614,36 @@ def _doSaveJournal(self, classname, nodeid, action, params):
553614

554615
db[nodeid] = marshal.dumps(l)
555616

617+
def doDestroyNode(self, classname, nodeid):
618+
if __debug__:
619+
print >>hyperdb.DEBUG, 'doDestroyNode', (self, classname, nodeid)
620+
621+
# delete from the class database
622+
db = self.getCachedClassDB(classname)
623+
if db.has_key(nodeid):
624+
del db[nodeid]
625+
626+
# delete from the database
627+
db = self.getCachedJournalDB(classname)
628+
if db.has_key(nodeid):
629+
del db[nodeid]
630+
631+
# return the classname, nodeid so we reindex this content
632+
return (classname, nodeid)
633+
556634
def rollback(self):
557635
''' Reverse all actions from the current transaction.
558636
'''
559637
if __debug__:
560638
print >>hyperdb.DEBUG, 'rollback', (self, )
561639
for method, args in self.transactions:
562640
# delete temporary files
563-
if method == self._doStoreFile:
564-
self._rollbackStoreFile(*args)
641+
if method == self.doStoreFile:
642+
self.rollbackStoreFile(*args)
565643
self.cache = {}
566644
self.dirtynodes = {}
567645
self.newnodes = {}
646+
self.destroyednodes = {}
568647
self.transactions = []
569648

570649
_marker = []
@@ -1075,6 +1154,23 @@ def retire(self, nodeid):
10751154

10761155
self.fireReactors('retire', nodeid, None)
10771156

1157+
def destroy(self, nodeid):
1158+
"""Destroy a node.
1159+
1160+
WARNING: this method should never be used except in extremely rare
1161+
situations where there could never be links to the node being
1162+
deleted
1163+
WARNING: use retire() instead
1164+
WARNING: the properties of this node will not be available ever again
1165+
WARNING: really, use retire() instead
1166+
1167+
Well, I think that's enough warnings. This method exists mostly to
1168+
support the session storage of the cgi interface.
1169+
"""
1170+
if self.db.journaltag is None:
1171+
raise DatabaseError, 'Database open read-only'
1172+
self.db.destroynode(self.classname, nodeid)
1173+
10781174
def history(self, nodeid):
10791175
"""Retrieve the journal of edits on a particular node.
10801176
@@ -1550,9 +1646,15 @@ def index(self, nodeid):
15501646
# find all the String properties that have indexme
15511647
for prop, propclass in self.getprops().items():
15521648
if isinstance(propclass, String) and propclass.indexme:
1553-
# and index them under (classname, nodeid, property)
1554-
self.db.indexer.add_text((self.classname, nodeid, prop),
1555-
str(self.get(nodeid, prop)))
1649+
try:
1650+
value = str(self.get(nodeid, prop))
1651+
except IndexError:
1652+
# node no longer exists - entry should be removed
1653+
self.db.indexer.purge_entry((self.classname, nodeid, prop))
1654+
else:
1655+
# and index them under (classname, nodeid, property)
1656+
self.db.indexer.add_text((self.classname, nodeid, prop),
1657+
value)
15561658

15571659
#
15581660
# Detector interface
@@ -1676,6 +1778,9 @@ def __init__(self, db, classname, **properties):
16761778

16771779
#
16781780
#$Log: not supported by cvs2svn $
1781+
#Revision 1.51 2002/07/18 23:07:08 richard
1782+
#Unit tests and a few fixes.
1783+
#
16791784
#Revision 1.50 2002/07/18 11:50:58 richard
16801785
#added tests for number type too
16811786
#

0 commit comments

Comments
 (0)