Deprecate bsddb for removal in Python 3.0.
[python.git] / Lib / bsddb / __init__.py
blob0af679f730ff19be931a0d94b49bf3e4dac3fc75
1 #----------------------------------------------------------------------
2 # Copyright (c) 1999-2001, Digital Creations, Fredericksburg, VA, USA
3 # and Andrew Kuchling. All rights reserved.
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are
7 # met:
9 # o Redistributions of source code must retain the above copyright
10 # notice, this list of conditions, and the disclaimer that follows.
12 # o Redistributions in binary form must reproduce the above copyright
13 # notice, this list of conditions, and the following disclaimer in
14 # the documentation and/or other materials provided with the
15 # distribution.
17 # o Neither the name of Digital Creations nor the names of its
18 # contributors may be used to endorse or promote products derived
19 # from this software without specific prior written permission.
21 # THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS AND CONTRIBUTORS *AS
22 # IS* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24 # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL
25 # CREATIONS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
28 # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
30 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
31 # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
32 # DAMAGE.
33 #----------------------------------------------------------------------
36 """Support for Berkeley DB 4.0 through 4.7 with a simple interface.
38 For the full featured object oriented interface use the bsddb.db module
39 instead. It mirrors the Oracle Berkeley DB C API.
40 """
42 import sys
43 absolute_import = (sys.version_info[0] >= 3)
45 if sys.py3kwarning:
46 import warnings
47 warnings.warnpy3k("in 3.x, bsddb has been removed; "
48 "please use the pybsddb project instead",
49 DeprecationWarning, 2)
51 try:
52 if __name__ == 'bsddb3':
53 # import _pybsddb binary as it should be the more recent version from
54 # a standalone pybsddb addon package than the version included with
55 # python as bsddb._bsddb.
56 if absolute_import :
57 # Because this syntaxis is not valid before Python 2.5
58 exec("from . import _pybsddb")
59 else :
60 import _pybsddb
61 _bsddb = _pybsddb
62 from bsddb3.dbutils import DeadlockWrap as _DeadlockWrap
63 else:
64 import _bsddb
65 from bsddb.dbutils import DeadlockWrap as _DeadlockWrap
66 except ImportError:
67 # Remove ourselves from sys.modules
68 import sys
69 del sys.modules[__name__]
70 raise
72 # bsddb3 calls it db, but provide _db for backwards compatibility
73 db = _db = _bsddb
74 __version__ = db.__version__
76 error = db.DBError # So bsddb.error will mean something...
78 #----------------------------------------------------------------------
80 import sys, os
82 from weakref import ref
84 if sys.version_info[0:2] <= (2, 5) :
85 import UserDict
86 MutableMapping = UserDict.DictMixin
87 else :
88 import collections
89 MutableMapping = collections.MutableMapping
91 class _iter_mixin(MutableMapping):
92 def _make_iter_cursor(self):
93 cur = _DeadlockWrap(self.db.cursor)
94 key = id(cur)
95 self._cursor_refs[key] = ref(cur, self._gen_cref_cleaner(key))
96 return cur
98 def _gen_cref_cleaner(self, key):
99 # use generate the function for the weakref callback here
100 # to ensure that we do not hold a strict reference to cur
101 # in the callback.
102 return lambda ref: self._cursor_refs.pop(key, None)
104 def __iter__(self):
105 self._kill_iteration = False
106 self._in_iter += 1
107 try:
108 try:
109 cur = self._make_iter_cursor()
111 # FIXME-20031102-greg: race condition. cursor could
112 # be closed by another thread before this call.
114 # since we're only returning keys, we call the cursor
115 # methods with flags=0, dlen=0, dofs=0
116 key = _DeadlockWrap(cur.first, 0,0,0)[0]
117 yield key
119 next = getattr(cur, "next")
120 while 1:
121 try:
122 key = _DeadlockWrap(next, 0,0,0)[0]
123 yield key
124 except _bsddb.DBCursorClosedError:
125 if self._kill_iteration:
126 raise RuntimeError('Database changed size '
127 'during iteration.')
128 cur = self._make_iter_cursor()
129 # FIXME-20031101-greg: race condition. cursor could
130 # be closed by another thread before this call.
131 _DeadlockWrap(cur.set, key,0,0,0)
132 next = getattr(cur, "next")
133 except _bsddb.DBNotFoundError:
134 pass
135 except _bsddb.DBCursorClosedError:
136 # the database was modified during iteration. abort.
137 pass
138 # When Python 2.3 not supported in bsddb3, we can change this to "finally"
139 except :
140 self._in_iter -= 1
141 raise
143 self._in_iter -= 1
145 def iteritems(self):
146 if not self.db:
147 return
148 self._kill_iteration = False
149 self._in_iter += 1
150 try:
151 try:
152 cur = self._make_iter_cursor()
154 # FIXME-20031102-greg: race condition. cursor could
155 # be closed by another thread before this call.
157 kv = _DeadlockWrap(cur.first)
158 key = kv[0]
159 yield kv
161 next = getattr(cur, "next")
162 while 1:
163 try:
164 kv = _DeadlockWrap(next)
165 key = kv[0]
166 yield kv
167 except _bsddb.DBCursorClosedError:
168 if self._kill_iteration:
169 raise RuntimeError('Database changed size '
170 'during iteration.')
171 cur = self._make_iter_cursor()
172 # FIXME-20031101-greg: race condition. cursor could
173 # be closed by another thread before this call.
174 _DeadlockWrap(cur.set, key,0,0,0)
175 next = getattr(cur, "next")
176 except _bsddb.DBNotFoundError:
177 pass
178 except _bsddb.DBCursorClosedError:
179 # the database was modified during iteration. abort.
180 pass
181 # When Python 2.3 not supported in bsddb3, we can change this to "finally"
182 except :
183 self._in_iter -= 1
184 raise
186 self._in_iter -= 1
189 class _DBWithCursor(_iter_mixin):
191 A simple wrapper around DB that makes it look like the bsddbobject in
192 the old module. It uses a cursor as needed to provide DB traversal.
194 def __init__(self, db):
195 self.db = db
196 self.db.set_get_returns_none(0)
198 # FIXME-20031101-greg: I believe there is still the potential
199 # for deadlocks in a multithreaded environment if someone
200 # attempts to use the any of the cursor interfaces in one
201 # thread while doing a put or delete in another thread. The
202 # reason is that _checkCursor and _closeCursors are not atomic
203 # operations. Doing our own locking around self.dbc,
204 # self.saved_dbc_key and self._cursor_refs could prevent this.
205 # TODO: A test case demonstrating the problem needs to be written.
207 # self.dbc is a DBCursor object used to implement the
208 # first/next/previous/last/set_location methods.
209 self.dbc = None
210 self.saved_dbc_key = None
212 # a collection of all DBCursor objects currently allocated
213 # by the _iter_mixin interface.
214 self._cursor_refs = {}
215 self._in_iter = 0
216 self._kill_iteration = False
218 def __del__(self):
219 self.close()
221 def _checkCursor(self):
222 if self.dbc is None:
223 self.dbc = _DeadlockWrap(self.db.cursor)
224 if self.saved_dbc_key is not None:
225 _DeadlockWrap(self.dbc.set, self.saved_dbc_key)
226 self.saved_dbc_key = None
228 # This method is needed for all non-cursor DB calls to avoid
229 # Berkeley DB deadlocks (due to being opened with DB_INIT_LOCK
230 # and DB_THREAD to be thread safe) when intermixing database
231 # operations that use the cursor internally with those that don't.
232 def _closeCursors(self, save=1):
233 if self.dbc:
234 c = self.dbc
235 self.dbc = None
236 if save:
237 try:
238 self.saved_dbc_key = _DeadlockWrap(c.current, 0,0,0)[0]
239 except db.DBError:
240 pass
241 _DeadlockWrap(c.close)
242 del c
243 for cref in self._cursor_refs.values():
244 c = cref()
245 if c is not None:
246 _DeadlockWrap(c.close)
248 def _checkOpen(self):
249 if self.db is None:
250 raise error, "BSDDB object has already been closed"
252 def isOpen(self):
253 return self.db is not None
255 def __len__(self):
256 self._checkOpen()
257 return _DeadlockWrap(lambda: len(self.db)) # len(self.db)
259 if sys.version_info[0:2] >= (2, 6) :
260 def __repr__(self) :
261 if self.isOpen() :
262 return repr(dict(_DeadlockWrap(self.db.items)))
263 return repr(dict())
265 def __getitem__(self, key):
266 self._checkOpen()
267 return _DeadlockWrap(lambda: self.db[key]) # self.db[key]
269 def __setitem__(self, key, value):
270 self._checkOpen()
271 self._closeCursors()
272 if self._in_iter and key not in self:
273 self._kill_iteration = True
274 def wrapF():
275 self.db[key] = value
276 _DeadlockWrap(wrapF) # self.db[key] = value
278 def __delitem__(self, key):
279 self._checkOpen()
280 self._closeCursors()
281 if self._in_iter and key in self:
282 self._kill_iteration = True
283 def wrapF():
284 del self.db[key]
285 _DeadlockWrap(wrapF) # del self.db[key]
287 def close(self):
288 self._closeCursors(save=0)
289 if self.dbc is not None:
290 _DeadlockWrap(self.dbc.close)
291 v = 0
292 if self.db is not None:
293 v = _DeadlockWrap(self.db.close)
294 self.dbc = None
295 self.db = None
296 return v
298 def keys(self):
299 self._checkOpen()
300 return _DeadlockWrap(self.db.keys)
302 def has_key(self, key):
303 self._checkOpen()
304 return _DeadlockWrap(self.db.has_key, key)
306 def set_location(self, key):
307 self._checkOpen()
308 self._checkCursor()
309 return _DeadlockWrap(self.dbc.set_range, key)
311 def next(self): # Renamed by "2to3"
312 self._checkOpen()
313 self._checkCursor()
314 rv = _DeadlockWrap(getattr(self.dbc, "next"))
315 return rv
317 if sys.version_info[0] >= 3 : # For "2to3" conversion
318 next = __next__
320 def previous(self):
321 self._checkOpen()
322 self._checkCursor()
323 rv = _DeadlockWrap(self.dbc.prev)
324 return rv
326 def first(self):
327 self._checkOpen()
328 # fix 1725856: don't needlessly try to restore our cursor position
329 self.saved_dbc_key = None
330 self._checkCursor()
331 rv = _DeadlockWrap(self.dbc.first)
332 return rv
334 def last(self):
335 self._checkOpen()
336 # fix 1725856: don't needlessly try to restore our cursor position
337 self.saved_dbc_key = None
338 self._checkCursor()
339 rv = _DeadlockWrap(self.dbc.last)
340 return rv
342 def sync(self):
343 self._checkOpen()
344 return _DeadlockWrap(self.db.sync)
347 #----------------------------------------------------------------------
348 # Compatibility object factory functions
350 def hashopen(file, flag='c', mode=0666, pgsize=None, ffactor=None, nelem=None,
351 cachesize=None, lorder=None, hflags=0):
353 flags = _checkflag(flag, file)
354 e = _openDBEnv(cachesize)
355 d = db.DB(e)
356 d.set_flags(hflags)
357 if pgsize is not None: d.set_pagesize(pgsize)
358 if lorder is not None: d.set_lorder(lorder)
359 if ffactor is not None: d.set_h_ffactor(ffactor)
360 if nelem is not None: d.set_h_nelem(nelem)
361 d.open(file, db.DB_HASH, flags, mode)
362 return _DBWithCursor(d)
364 #----------------------------------------------------------------------
366 def btopen(file, flag='c', mode=0666,
367 btflags=0, cachesize=None, maxkeypage=None, minkeypage=None,
368 pgsize=None, lorder=None):
370 flags = _checkflag(flag, file)
371 e = _openDBEnv(cachesize)
372 d = db.DB(e)
373 if pgsize is not None: d.set_pagesize(pgsize)
374 if lorder is not None: d.set_lorder(lorder)
375 d.set_flags(btflags)
376 if minkeypage is not None: d.set_bt_minkey(minkeypage)
377 if maxkeypage is not None: d.set_bt_maxkey(maxkeypage)
378 d.open(file, db.DB_BTREE, flags, mode)
379 return _DBWithCursor(d)
381 #----------------------------------------------------------------------
384 def rnopen(file, flag='c', mode=0666,
385 rnflags=0, cachesize=None, pgsize=None, lorder=None,
386 rlen=None, delim=None, source=None, pad=None):
388 flags = _checkflag(flag, file)
389 e = _openDBEnv(cachesize)
390 d = db.DB(e)
391 if pgsize is not None: d.set_pagesize(pgsize)
392 if lorder is not None: d.set_lorder(lorder)
393 d.set_flags(rnflags)
394 if delim is not None: d.set_re_delim(delim)
395 if rlen is not None: d.set_re_len(rlen)
396 if source is not None: d.set_re_source(source)
397 if pad is not None: d.set_re_pad(pad)
398 d.open(file, db.DB_RECNO, flags, mode)
399 return _DBWithCursor(d)
401 #----------------------------------------------------------------------
403 def _openDBEnv(cachesize):
404 e = db.DBEnv()
405 if cachesize is not None:
406 if cachesize >= 20480:
407 e.set_cachesize(0, cachesize)
408 else:
409 raise error, "cachesize must be >= 20480"
410 e.set_lk_detect(db.DB_LOCK_DEFAULT)
411 e.open('.', db.DB_PRIVATE | db.DB_CREATE | db.DB_THREAD | db.DB_INIT_LOCK | db.DB_INIT_MPOOL)
412 return e
414 def _checkflag(flag, file):
415 if flag == 'r':
416 flags = db.DB_RDONLY
417 elif flag == 'rw':
418 flags = 0
419 elif flag == 'w':
420 flags = db.DB_CREATE
421 elif flag == 'c':
422 flags = db.DB_CREATE
423 elif flag == 'n':
424 flags = db.DB_CREATE
425 #flags = db.DB_CREATE | db.DB_TRUNCATE
426 # we used db.DB_TRUNCATE flag for this before but Berkeley DB
427 # 4.2.52 changed to disallowed truncate with txn environments.
428 if file is not None and os.path.isfile(file):
429 os.unlink(file)
430 else:
431 raise error, "flags should be one of 'r', 'w', 'c' or 'n'"
432 return flags | db.DB_THREAD
434 #----------------------------------------------------------------------
437 # This is a silly little hack that allows apps to continue to use the
438 # DB_THREAD flag even on systems without threads without freaking out
439 # Berkeley DB.
441 # This assumes that if Python was built with thread support then
442 # Berkeley DB was too.
444 try:
445 import thread
446 del thread
447 except ImportError:
448 db.DB_THREAD = 0
450 #----------------------------------------------------------------------