Skip to content

Commit f6b5db4

Browse files
author
Richard Jones
committed
Well whadya know, bsddb3 works again.
The backend is implemented _exactly_ the same as bsddb - so there's no using its transaction or locking support. It'd be nice to use those some day I suppose.
1 parent e1b5923 commit f6b5db4

File tree

3 files changed

+69
-142
lines changed

3 files changed

+69
-142
lines changed

CHANGES.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Fixed:
4444
. fixed some sorting issues that were breaking some unit tests under py2.2
4545
. mailgw test output dir was confusing the init test (but only on 2.2 *shrug*)
4646
. node caching now works, and gives a small boost in performance
47-
47+
. bsddb3 backend now works, reinstating
4848

4949
2002-03-25 - 0.4.1
5050
Feature:

roundup/backends/back_bsddb3.py

Lines changed: 55 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -15,70 +15,28 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
#$Id: back_bsddb3.py,v 1.11 2002-01-14 02:20:15 richard Exp $
18+
#$Id: back_bsddb3.py,v 1.12 2002-05-21 05:52:11 richard Exp $
1919

2020
import bsddb3, os, marshal
21-
from roundup import hyperdb, date, password
21+
from roundup import hyperdb, date
22+
23+
# these classes are so similar, we just use the anydbm methods
24+
import back_anydbm
2225

2326
#
2427
# Now the database
2528
#
26-
class Database(hyperdb.Database):
29+
class Database(back_anydbm.Database):
2730
"""A database for storing records containing flexible data types."""
28-
29-
def __init__(self, config, journaltag=None):
30-
"""Open a hyperdatabase given a specifier to some storage.
31-
32-
The 'storagelocator' is obtained from config.DATABASE.
33-
The meaning of 'storagelocator' depends on the particular
34-
implementation of the hyperdatabase. It could be a file name,
35-
a directory path, a socket descriptor for a connection to a
36-
database over the network, etc.
37-
38-
The 'journaltag' is a token that will be attached to the journal
39-
entries for any edits done on the database. If 'journaltag' is
40-
None, the database is opened in read-only mode: the Class.create(),
41-
Class.set(), and Class.retire() methods are disabled.
42-
"""
43-
self.config, self.journaltag = config, journaltag
44-
self.dir = config.DATABASE
45-
self.classes = {}
46-
47-
#
48-
# Classes
49-
#
50-
def __getattr__(self, classname):
51-
"""A convenient way of calling self.getclass(classname)."""
52-
return self.classes[classname]
53-
54-
def addclass(self, cl):
55-
cn = cl.classname
56-
if self.classes.has_key(cn):
57-
raise ValueError, cn
58-
self.classes[cn] = cl
59-
60-
def getclasses(self):
61-
"""Return a list of the names of all existing classes."""
62-
l = self.classes.keys()
63-
l.sort()
64-
return l
65-
66-
def getclass(self, classname):
67-
"""Get the Class object representing a particular class.
68-
69-
If 'classname' is not a valid class name, a KeyError is raised.
70-
"""
71-
return self.classes[classname]
72-
7331
#
7432
# Class DBs
7533
#
7634
def clear(self):
7735
for cn in self.classes.keys():
7836
db = os.path.join(self.dir, 'nodes.%s'%cn)
79-
bsddb3.btopen(db, 'c')
37+
bsddb3.btopen(db, 'n')
8038
db = os.path.join(self.dir, 'journals.%s'%cn)
81-
bsddb3.btopen(db, 'c')
39+
bsddb3.btopen(db, 'n')
8240

8341
def getclassdb(self, classname, mode='r'):
8442
''' grab a connection to the class db that will be used for
@@ -88,73 +46,29 @@ def getclassdb(self, classname, mode='r'):
8846
if os.path.exists(path):
8947
return bsddb3.btopen(path, mode)
9048
else:
91-
return bsddb3.btopen(path, 'c')
49+
return bsddb3.btopen(path, 'n')
9250

93-
#
94-
# Nodes
95-
#
96-
def addnode(self, classname, nodeid, node):
97-
''' add the specified node to its class's db
51+
def _opendb(self, name, mode):
52+
'''Low-level database opener that gets around anydbm/dbm
53+
eccentricities.
9854
'''
99-
db = self.getclassdb(classname, 'c')
100-
# now save the marshalled data
101-
db[nodeid] = marshal.dumps(node)
102-
db.close()
103-
setnode = addnode
104-
105-
def getnode(self, classname, nodeid, cldb=None):
106-
''' add the specified node to its class's db
107-
'''
108-
db = cldb or self.getclassdb(classname)
109-
if not db.has_key(nodeid):
110-
raise IndexError, nodeid
111-
res = marshal.loads(db[nodeid])
112-
if not cldb: db.close()
113-
return res
114-
115-
def hasnode(self, classname, nodeid, cldb=None):
116-
''' add the specified node to its class's db
117-
'''
118-
db = cldb or self.getclassdb(classname)
119-
res = db.has_key(nodeid)
120-
if not cldb: db.close()
121-
return res
122-
123-
def countnodes(self, classname, cldb=None):
124-
db = cldb or self.getclassdb(classname)
125-
return len(db.keys())
126-
if not cldb: db.close()
127-
return res
128-
129-
def getnodeids(self, classname, cldb=None):
130-
db = cldb or self.getclassdb(classname)
131-
res = db.keys()
132-
if not cldb: db.close()
133-
return res
55+
if __debug__:
56+
print >>hyperdb.DEBUG, self, '_opendb', (self, name, mode)
57+
# determine which DB wrote the class file
58+
path = os.path.join(os.getcwd(), self.dir, name)
59+
if not os.path.exists(path):
60+
if __debug__:
61+
print >>hyperdb.DEBUG, "_opendb bsddb3.open(%r, 'n')"%path
62+
return bsddb3.btopen(path, 'n')
63+
64+
# open the database with the correct module
65+
if __debug__:
66+
print >>hyperdb.DEBUG, "_opendb bsddb3.open(%r, %r)"%(path, mode)
67+
return bsddb3.btopen(path, mode)
13468

13569
#
13670
# Journal
13771
#
138-
def addjournal(self, classname, nodeid, action, params):
139-
''' Journal the Action
140-
'action' may be:
141-
142-
'create' or 'set' -- 'params' is a dictionary of property values
143-
'link' or 'unlink' -- 'params' is (classname, nodeid, propname)
144-
'retire' -- 'params' is None
145-
'''
146-
entry = (nodeid, date.Date().get_tuple(), self.journaltag, action,
147-
params)
148-
db = bsddb3.btopen(os.path.join(self.dir, 'journals.%s'%classname), 'c')
149-
if db.has_key(nodeid):
150-
s = db[nodeid]
151-
l = marshal.loads(db[nodeid])
152-
l.append(entry)
153-
else:
154-
l = [entry]
155-
db[nodeid] = marshal.dumps(l)
156-
db.close()
157-
15872
def getjournal(self, classname, nodeid):
15973
''' get the journal for id
16074
'''
@@ -163,46 +77,53 @@ def getjournal(self, classname, nodeid):
16377
try:
16478
db = bsddb3.btopen(os.path.join(self.dir, 'journals.%s'%classname),
16579
'r')
166-
except bsddb3.error, error:
167-
if error.args[0] != 2: raise
80+
except bsddb3.NoSuchFileError:
16881
return []
16982
# mor handling of bad journals
17083
if not db.has_key(nodeid): return []
17184
journal = marshal.loads(db[nodeid])
17285
res = []
17386
for entry in journal:
174-
(nodeid, date_stamp, self.journaltag, action, params) = entry
87+
(nodeid, date_stamp, user, action, params) = entry
17588
date_obj = date.Date(date_stamp)
176-
res.append((nodeid, date_obj, self.journaltag, action, params))
89+
res.append((nodeid, date_obj, user, action, params))
17790
db.close()
17891
return res
17992

180-
def close(self):
181-
''' Close the Database - we must release the circular refs so that
182-
we can be del'ed and the underlying bsddb connections closed
183-
cleanly.
184-
'''
185-
self.classes = {}
93+
def _doSaveJournal(self, classname, nodeid, action, params):
94+
# serialise first
95+
if action in ('set', 'create'):
96+
params = self.serialise(classname, params)
18697

98+
entry = (nodeid, date.Date().get_tuple(), self.journaltag, action,
99+
params)
187100

188-
#
189-
# Basic transaction support
190-
#
191-
# TODO: well, write these methods (and then use them in other code)
192-
def register_action(self):
193-
''' Register an action to the transaction undo log
194-
'''
101+
if __debug__:
102+
print >>hyperdb.DEBUG, '_doSaveJournal', entry
195103

196-
def commit(self):
197-
''' Commit the current transaction, start a new one
198-
'''
104+
db = bsddb3.btopen(os.path.join(self.dir, 'journals.%s'%classname), 'c')
199105

200-
def rollback(self):
201-
''' Reverse all actions from the current transaction
202-
'''
106+
if db.has_key(nodeid):
107+
s = db[nodeid]
108+
l = marshal.loads(s)
109+
l.append(entry)
110+
else:
111+
l = [entry]
112+
113+
db[nodeid] = marshal.dumps(l)
114+
db.close()
203115

204116
#
205117
#$Log: not supported by cvs2svn $
118+
#Revision 1.11 2002/01/14 02:20:15 richard
119+
# . changed all config accesses so they access either the instance or the
120+
# config attriubute on the db. This means that all config is obtained from
121+
# instance_config instead of the mish-mash of classes. This will make
122+
# switching to a ConfigParser setup easier too, I hope.
123+
#
124+
#At a minimum, this makes migration a _little_ easier (a lot easier in the
125+
#0.5.0 switch, I hope!)
126+
#
206127
#Revision 1.10 2001/11/21 02:34:18 richard
207128
#Added a target version field to the extended issue schema
208129
#

test/test_db.py

Lines changed: 13 additions & 7 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: test_db.py,v 1.21 2002-04-15 23:25:15 richard Exp $
18+
# $Id: test_db.py,v 1.22 2002-05-21 05:52:11 richard Exp $
1919

2020
import unittest, os, shutil
2121

@@ -366,17 +366,23 @@ def suite():
366366
except:
367367
print 'bsddb module not found, skipping bsddb DBTestCase'
368368

369-
# try:
370-
# import bsddb3
371-
# l.append(unittest.makeSuite(bsddb3DBTestCase, 'test'))
372-
# l.append(unittest.makeSuite(bsddb3ReadOnlyDBTestCase, 'test'))
373-
# except:
374-
# print 'bsddb3 module not found, skipping bsddb3 DBTestCase'
369+
try:
370+
import bsddb3
371+
l.append(unittest.makeSuite(bsddb3DBTestCase, 'test'))
372+
l.append(unittest.makeSuite(bsddb3ReadOnlyDBTestCase, 'test'))
373+
except:
374+
print 'bsddb3 module not found, skipping bsddb3 DBTestCase'
375375

376376
return unittest.TestSuite(l)
377377

378378
#
379379
# $Log: not supported by cvs2svn $
380+
# Revision 1.21 2002/04/15 23:25:15 richard
381+
# . node ids are now generated from a lockable store - no more race conditions
382+
#
383+
# We're using the portalocker code by Jonathan Feinberg that was contributed
384+
# to the ASPN Python cookbook. This gives us locking across Unix and Windows.
385+
#
380386
# Revision 1.20 2002/04/03 05:54:31 richard
381387
# Fixed serialisation problem by moving the serialisation step out of the
382388
# hyperdb.Class (get, set) into the hyperdb.Database.

0 commit comments

Comments
 (0)