7b4f96e47df01d1ad089d19eb3f15cf56432e3af
1 #$Id: back_bsddb.py,v 1.2 2001-07-23 07:56:05 richard Exp $
3 import bsddb, os, marshal
4 # handle the older cPickle'd data
5 import cPickle
6 from roundup import hyperdb, date
8 #
9 # Now the database
10 #
11 class Database(hyperdb.Database):
12 """A database for storing records containing flexible data types."""
14 def __init__(self, storagelocator, journaltag=None):
15 """Open a hyperdatabase given a specifier to some storage.
17 The meaning of 'storagelocator' depends on the particular
18 implementation of the hyperdatabase. It could be a file name,
19 a directory path, a socket descriptor for a connection to a
20 database over the network, etc.
22 The 'journaltag' is a token that will be attached to the journal
23 entries for any edits done on the database. If 'journaltag' is
24 None, the database is opened in read-only mode: the Class.create(),
25 Class.set(), and Class.retire() methods are disabled.
26 """
27 self.dir, self.journaltag = storagelocator, journaltag
28 self.classes = {}
30 #
31 # Classes
32 #
33 def __getattr__(self, classname):
34 """A convenient way of calling self.getclass(classname)."""
35 return self.classes[classname]
37 def addclass(self, cl):
38 cn = cl.classname
39 if self.classes.has_key(cn):
40 raise ValueError, cn
41 self.classes[cn] = cl
43 def getclasses(self):
44 """Return a list of the names of all existing classes."""
45 l = self.classes.keys()
46 l.sort()
47 return l
49 def getclass(self, classname):
50 """Get the Class object representing a particular class.
52 If 'classname' is not a valid class name, a KeyError is raised.
53 """
54 return self.classes[classname]
56 #
57 # Class DBs
58 #
59 def clear(self):
60 for cn in self.classes.keys():
61 db = os.path.join(self.dir, 'nodes.%s'%cn)
62 bsddb.btopen(db, 'n')
63 db = os.path.join(self.dir, 'journals.%s'%cn)
64 bsddb.btopen(db, 'n')
66 def getclassdb(self, classname, mode='r'):
67 ''' grab a connection to the class db that will be used for
68 multiple actions
69 '''
70 path = os.path.join(os.getcwd(), self.dir, 'nodes.%s'%classname)
71 return bsddb.btopen(path, mode)
73 #
74 # Nodes
75 #
76 def addnode(self, classname, nodeid, node):
77 ''' add the specified node to its class's db
78 '''
79 db = self.getclassdb(classname, 'c')
80 # convert the instance data to builtin types
81 properties = self.classes[classname].properties
82 for key in res.keys():
83 if properties[key].isDateType:
84 res[key] = res[key].get_tuple()
85 elif properties[key].isIntervalType:
86 res[key] = res[key].get_tuple()
87 db[nodeid] = marshal.dumps(node, 1)
88 db.close()
89 setnode = addnode
91 def getnode(self, classname, nodeid, cldb=None):
92 ''' add the specified node to its class's db
93 '''
94 db = cldb or self.getclassdb(classname)
95 if not db.has_key(nodeid):
96 raise IndexError, nodeid
97 try:
98 res = marshal.loads(db[nodeid])
99 # convert the marshalled data to instances
100 properties = self.classes[classname].properties
101 for key in res.keys():
102 if properties[key].isDateType:
103 res[key] = date.Date(res[key])
104 elif properties[key].isIntervalType:
105 res[key] = date.Interval(res[key])
106 except ValueError, message:
107 if str(message) != 'bad marshal data':
108 raise
109 # handle the older cPickle'd data
110 res = cPickle.loads(db[nodeid])
112 if not cldb: db.close()
113 return res
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
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
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
135 #
136 # Journal
137 #
138 def addjournal(self, classname, nodeid, action, params):
139 ''' Journal the Action
140 'action' may be:
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().journal_tuple(), self.journaltag, action,
147 params)
148 db = bsddb.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()
158 def getjournal(self, classname, nodeid):
159 ''' get the journal for id
160 '''
161 # attempt to open the journal - in some rare cases, the journal may
162 # not exist
163 try:
164 db = bsddb.btopen(os.path.join(self.dir, 'journals.%s'%classname),
165 'r')
166 except bsddb.error, error:
167 if error.args[0] != 2: raise
168 return []
169 journal = marshal.loads(db[nodeid])
170 res = []
171 for entry in journal:
172 (nodeid, date_stamp, self.journaltag, action, params) = entry
173 date_obj = date.Date(set=date_stamp)
174 res.append((nodeid, date_obj, self.journaltag, action, params))
175 db.close()
176 return res
178 def close(self):
179 ''' Close the Database - we must release the circular refs so that
180 we can be del'ed and the underlying bsddb connections closed
181 cleanly.
182 '''
183 self.classes = None
186 #
187 # Basic transaction support
188 #
189 # TODO: well, write these methods (and then use them in other code)
190 def register_action(self):
191 ''' Register an action to the transaction undo log
192 '''
194 def commit(self):
195 ''' Commit the current transaction, start a new one
196 '''
198 def rollback(self):
199 ''' Reverse all actions from the current transaction
200 '''
202 #
203 #$Log: not supported by cvs2svn $
204 #Revision 1.1 2001/07/23 07:22:13 richard
205 #*sigh* some databases have _foo.so as their underlying implementation.
206 #This time for sure, Rocky.
207 #
208 #Revision 1.1 2001/07/23 07:15:57 richard
209 #Moved the backends into the backends package. Anydbm hasn't been tested at all.
210 #
211 #Revision 1.1 2001/07/23 06:23:41 richard
212 #moved hyper_bsddb.py to the new backends package as bsddb.py
213 #
214 #Revision 1.2 2001/07/22 12:09:32 richard
215 #Final commit of Grande Splite
216 #
217 #Revision 1.1 2001/07/22 11:58:35 richard
218 #More Grande Splite
219 #