2c948c656cd26b22edc64aa95f4ca3d1a06e48b0
1 #$Id: back_bsddb.py,v 1.3 2001-07-23 08:20:44 richard Exp $
3 import bsddb, os, marshal
4 from roundup import hyperdb, date
6 #
7 # Now the database
8 #
9 class Database(hyperdb.Database):
10 """A database for storing records containing flexible data types."""
12 def __init__(self, storagelocator, journaltag=None):
13 """Open a hyperdatabase given a specifier to some storage.
15 The meaning of 'storagelocator' depends on the particular
16 implementation of the hyperdatabase. It could be a file name,
17 a directory path, a socket descriptor for a connection to a
18 database over the network, etc.
20 The 'journaltag' is a token that will be attached to the journal
21 entries for any edits done on the database. If 'journaltag' is
22 None, the database is opened in read-only mode: the Class.create(),
23 Class.set(), and Class.retire() methods are disabled.
24 """
25 self.dir, self.journaltag = storagelocator, journaltag
26 self.classes = {}
28 #
29 # Classes
30 #
31 def __getattr__(self, classname):
32 """A convenient way of calling self.getclass(classname)."""
33 return self.classes[classname]
35 def addclass(self, cl):
36 cn = cl.classname
37 if self.classes.has_key(cn):
38 raise ValueError, cn
39 self.classes[cn] = cl
41 def getclasses(self):
42 """Return a list of the names of all existing classes."""
43 l = self.classes.keys()
44 l.sort()
45 return l
47 def getclass(self, classname):
48 """Get the Class object representing a particular class.
50 If 'classname' is not a valid class name, a KeyError is raised.
51 """
52 return self.classes[classname]
54 #
55 # Class DBs
56 #
57 def clear(self):
58 for cn in self.classes.keys():
59 db = os.path.join(self.dir, 'nodes.%s'%cn)
60 bsddb.btopen(db, 'n')
61 db = os.path.join(self.dir, 'journals.%s'%cn)
62 bsddb.btopen(db, 'n')
64 def getclassdb(self, classname, mode='r'):
65 ''' grab a connection to the class db that will be used for
66 multiple actions
67 '''
68 path = os.path.join(os.getcwd(), self.dir, 'nodes.%s'%classname)
69 return bsddb.btopen(path, mode)
71 #
72 # Nodes
73 #
74 def addnode(self, classname, nodeid, node):
75 ''' add the specified node to its class's db
76 '''
77 db = self.getclassdb(classname, 'c')
79 # convert the instance data to builtin types
80 properties = self.classes[classname].properties
81 for key in properties.keys():
82 if properties[key].isDateType:
83 node[key] = node[key].get_tuple()
84 elif properties[key].isIntervalType:
85 node[key] = node[key].get_tuple()
87 # now save the marshalled data
88 db[nodeid] = marshal.dumps(node)
89 db.close()
90 setnode = addnode
92 def getnode(self, classname, nodeid, cldb=None):
93 ''' add the specified node to its class's db
94 '''
95 db = cldb or self.getclassdb(classname)
96 if not db.has_key(nodeid):
97 raise IndexError, nodeid
98 res = marshal.loads(db[nodeid])
100 # convert the marshalled data to instances
101 properties = self.classes[classname].properties
102 for key in res.keys():
103 if properties[key].isDateType:
104 res[key] = date.Date(res[key])
105 elif properties[key].isIntervalType:
106 res[key] = date.Interval(res[key])
108 if not cldb: db.close()
109 return res
111 def hasnode(self, classname, nodeid, cldb=None):
112 ''' add the specified node to its class's db
113 '''
114 db = cldb or self.getclassdb(classname)
115 res = db.has_key(nodeid)
116 if not cldb: db.close()
117 return res
119 def countnodes(self, classname, cldb=None):
120 db = cldb or self.getclassdb(classname)
121 return len(db.keys())
122 if not cldb: db.close()
123 return res
125 def getnodeids(self, classname, cldb=None):
126 db = cldb or self.getclassdb(classname)
127 res = db.keys()
128 if not cldb: db.close()
129 return res
131 #
132 # Journal
133 #
134 def addjournal(self, classname, nodeid, action, params):
135 ''' Journal the Action
136 'action' may be:
138 'create' or 'set' -- 'params' is a dictionary of property values
139 'link' or 'unlink' -- 'params' is (classname, nodeid, propname)
140 'retire' -- 'params' is None
141 '''
142 entry = (nodeid, date.Date().get_tuple(), self.journaltag, action,
143 params)
144 db = bsddb.btopen(os.path.join(self.dir, 'journals.%s'%classname), 'c')
145 if db.has_key(nodeid):
146 s = db[nodeid]
147 l = marshal.loads(db[nodeid])
148 l.append(entry)
149 else:
150 l = [entry]
151 db[nodeid] = marshal.dumps(l)
152 db.close()
154 def getjournal(self, classname, nodeid):
155 ''' get the journal for id
156 '''
157 # attempt to open the journal - in some rare cases, the journal may
158 # not exist
159 try:
160 db = bsddb.btopen(os.path.join(self.dir, 'journals.%s'%classname),
161 'r')
162 except bsddb.error, error:
163 if error.args[0] != 2: raise
164 return []
165 journal = marshal.loads(db[nodeid])
166 res = []
167 for entry in journal:
168 (nodeid, date_stamp, self.journaltag, action, params) = entry
169 date_obj = date.Date(date_stamp)
170 res.append((nodeid, date_obj, self.journaltag, action, params))
171 db.close()
172 return res
174 def close(self):
175 ''' Close the Database - we must release the circular refs so that
176 we can be del'ed and the underlying bsddb connections closed
177 cleanly.
178 '''
179 self.classes = None
182 #
183 # Basic transaction support
184 #
185 # TODO: well, write these methods (and then use them in other code)
186 def register_action(self):
187 ''' Register an action to the transaction undo log
188 '''
190 def commit(self):
191 ''' Commit the current transaction, start a new one
192 '''
194 def rollback(self):
195 ''' Reverse all actions from the current transaction
196 '''
198 #
199 #$Log: not supported by cvs2svn $
200 #Revision 1.2 2001/07/23 07:56:05 richard
201 #Storing only marshallable data in the db - no nasty pickled class references.
202 #
203 #Revision 1.1 2001/07/23 07:22:13 richard
204 #*sigh* some databases have _foo.so as their underlying implementation.
205 #This time for sure, Rocky.
206 #
207 #Revision 1.1 2001/07/23 07:15:57 richard
208 #Moved the backends into the backends package. Anydbm hasn't been tested at all.
209 #
210 #Revision 1.1 2001/07/23 06:23:41 richard
211 #moved hyper_bsddb.py to the new backends package as bsddb.py
212 #
213 #Revision 1.2 2001/07/22 12:09:32 richard
214 #Final commit of Grande Splite
215 #
216 #Revision 1.1 2001/07/22 11:58:35 richard
217 #More Grande Splite
218 #