#----------------------------------------------------------------------
# Copyright (c) 1999-2001, Digital Creations, Fredericksburg, VA, USA
# and Andrew Kuchling. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# o Redistributions of source code must retain the above copyright
# notice, this list of conditions, and the disclaimer that follows.
# o Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# o Neither the name of Digital Creations nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS AND CONTRIBUTORS *AS
# IS* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL
# CREATIONS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
#----------------------------------------------------------------------
"""Support for Berkeley DB 4.3 through 5.3 with a simple interface.
For the full featured object oriented interface use the bsddb.db module
instead. It mirrors the Oracle Berkeley DB C API.
absolute_import = (sys.version_info[0] >= 3)
if (sys.version_info >= (2, 6)) and (sys.version_info < (3, 0)) :
if sys.py3kwarning and (__name__ != 'bsddb3') :
warnings.warnpy3k("in 3.x, the bsddb module has been removed; "
"please use the pybsddb project instead",
warnings.filterwarnings("ignore", ".*CObject.*", DeprecationWarning,
# import _pybsddb binary as it should be the more recent version from
# a standalone pybsddb addon package than the version included with
# python as bsddb._bsddb.
# Because this syntaxis is not valid before Python 2.5
exec("from . import _pybsddb")
from bsddb3.dbutils import DeadlockWrap as _DeadlockWrap
from bsddb.dbutils import DeadlockWrap as _DeadlockWrap
# Remove ourselves from sys.modules
del sys.modules[__name__]
# bsddb3 calls it db, but provide _db for backwards compatibility
__version__ = db.__version__
error = db.DBError # So bsddb.error will mean something...
#----------------------------------------------------------------------
if sys.version_info < (2, 6) :
MutableMapping = UserDict.DictMixin
MutableMapping = collections.MutableMapping
class _iter_mixin(MutableMapping):
def _make_iter_cursor(self):
cur = _DeadlockWrap(self.db.cursor)
self._cursor_refs[key] = ref(cur, self._gen_cref_cleaner(key))
def _gen_cref_cleaner(self, key):
# use generate the function for the weakref callback here
# to ensure that we do not hold a strict reference to cur
return lambda ref: self._cursor_refs.pop(key, None)
self._kill_iteration = False
cur = self._make_iter_cursor()
# FIXME-20031102-greg: race condition. cursor could
# be closed by another thread before this call.
# since we're only returning keys, we call the cursor
# methods with flags=0, dlen=0, dofs=0
key = _DeadlockWrap(cur.first, 0,0,0)[0]
next = getattr(cur, "next")
key = _DeadlockWrap(next, 0,0,0)[0]
except _bsddb.DBCursorClosedError:
raise RuntimeError('Database changed size '
cur = self._make_iter_cursor()
# FIXME-20031101-greg: race condition. cursor could
# be closed by another thread before this call.
_DeadlockWrap(cur.set, key,0,0,0)
next = getattr(cur, "next")
except _bsddb.DBNotFoundError:
except _bsddb.DBCursorClosedError:
# the database was modified during iteration. abort.
# When Python 2.4 not supported in bsddb3, we can change this to "finally"
self._kill_iteration = False
cur = self._make_iter_cursor()
# FIXME-20031102-greg: race condition. cursor could
# be closed by another thread before this call.
kv = _DeadlockWrap(cur.first)
next = getattr(cur, "next")
except _bsddb.DBCursorClosedError:
raise RuntimeError('Database changed size '
cur = self._make_iter_cursor()
# FIXME-20031101-greg: race condition. cursor could
# be closed by another thread before this call.
_DeadlockWrap(cur.set, key,0,0,0)
next = getattr(cur, "next")
except _bsddb.DBNotFoundError:
except _bsddb.DBCursorClosedError:
# the database was modified during iteration. abort.
# When Python 2.4 not supported in bsddb3, we can change this to "finally"
class _DBWithCursor(_iter_mixin):
A simple wrapper around DB that makes it look like the bsddbobject in
the old module. It uses a cursor as needed to provide DB traversal.
self.db.set_get_returns_none(0)
# FIXME-20031101-greg: I believe there is still the potential
# for deadlocks in a multithreaded environment if someone
# attempts to use the any of the cursor interfaces in one
# thread while doing a put or delete in another thread. The
# reason is that _checkCursor and _closeCursors are not atomic
# operations. Doing our own locking around self.dbc,
# self.saved_dbc_key and self._cursor_refs could prevent this.
# TODO: A test case demonstrating the problem needs to be written.
# self.dbc is a DBCursor object used to implement the
# first/next/previous/last/set_location methods.
self.saved_dbc_key = None
# a collection of all DBCursor objects currently allocated
# by the _iter_mixin interface.
self._kill_iteration = False
self.dbc = _DeadlockWrap(self.db.cursor)
if self.saved_dbc_key is not None:
_DeadlockWrap(self.dbc.set, self.saved_dbc_key)
self.saved_dbc_key = None
# This method is needed for all non-cursor DB calls to avoid
# Berkeley DB deadlocks (due to being opened with DB_INIT_LOCK
# and DB_THREAD to be thread safe) when intermixing database
# operations that use the cursor internally with those that don't.
def _closeCursors(self, save=1):
self.saved_dbc_key = _DeadlockWrap(c.current, 0,0,0)[0]
for cref in self._cursor_refs.values():
raise error, "BSDDB object has already been closed"
return self.db is not None
return _DeadlockWrap(lambda: len(self.db)) # len(self.db)
if sys.version_info >= (2, 6) :
return repr(dict(_DeadlockWrap(self.db.items)))
def __getitem__(self, key):
return _DeadlockWrap(lambda: self.db[key]) # self.db[key]
def __setitem__(self, key, value):
if self._in_iter and key not in self:
self._kill_iteration = True
_DeadlockWrap(wrapF) # self.db[key] = value
def __delitem__(self, key):
if self._in_iter and key in self:
self._kill_iteration = True
_DeadlockWrap(wrapF) # del self.db[key]
self._closeCursors(save=0)
_DeadlockWrap(self.dbc.close)
v = _DeadlockWrap(self.db.close)
return _DeadlockWrap(self.db.keys)
return _DeadlockWrap(self.db.has_key, key)
def set_location(self, key):
return _DeadlockWrap(self.dbc.set_range, key)
def next(self): # Renamed by "2to3"
rv = _DeadlockWrap(getattr(self.dbc, "next"))
if sys.version_info[0] >= 3 : # For "2to3" conversion
rv = _DeadlockWrap(self.dbc.prev)
# fix 1725856: don't needlessly try to restore our cursor position
self.saved_dbc_key = None
rv = _DeadlockWrap(self.dbc.first)
# fix 1725856: don't needlessly try to restore our cursor position
self.saved_dbc_key = None
rv = _DeadlockWrap(self.dbc.last)
return _DeadlockWrap(self.db.sync)
#----------------------------------------------------------------------
# Compatibility object factory functions
def hashopen(file, flag='c', mode=0666, pgsize=None, ffactor=None, nelem=None,
cachesize=None, lorder=None, hflags=0):
flags = _checkflag(flag, file)
e = _openDBEnv(cachesize)
if pgsize is not None: d.set_pagesize(pgsize)
if lorder is not None: d.set_lorder(lorder)
if ffactor is not None: d.set_h_ffactor(ffactor)
if nelem is not None: d.set_h_nelem(nelem)
d.open(file, db.DB_HASH, flags, mode)
#----------------------------------------------------------------------
def btopen(file, flag='c', mode=0666,
btflags=0, cachesize=None, maxkeypage=None, minkeypage=None,
pgsize=None, lorder=None):
flags = _checkflag(flag, file)
e = _openDBEnv(cachesize)
if pgsize is not None: d.set_pagesize(pgsize)
if lorder is not None: d.set_lorder(lorder)
if minkeypage is not None: d.set_bt_minkey(minkeypage)
if maxkeypage is not None: d.set_bt_maxkey(maxkeypage)
d.open(file, db.DB_BTREE, flags, mode)
#----------------------------------------------------------------------
def rnopen(file, flag='c', mode=0666,
rnflags=0, cachesize=None, pgsize=None, lorder=None,
rlen=None, delim=None, source=None, pad=None):
flags = _checkflag(flag, file)
e = _openDBEnv(cachesize)
if pgsize is not None: d.set_pagesize(pgsize)
if lorder is not None: d.set_lorder(lorder)
if delim is not None: d.set_re_delim(delim)
if rlen is not None: d.set_re_len(rlen)
if source is not None: d.set_re_source(source)
if pad is not None: d.set_re_pad(pad)
d.open(file, db.DB_RECNO, flags, mode)
#----------------------------------------------------------------------
def _openDBEnv(cachesize):
if cachesize is not None:
e.set_cachesize(0, cachesize)
raise error, "cachesize must be >= 20480"
e.set_lk_detect(db.DB_LOCK_DEFAULT)
e.open('.', db.DB_PRIVATE | db.DB_CREATE | db.DB_THREAD | db.DB_INIT_LOCK | db.DB_INIT_MPOOL)
def _checkflag(flag, file):
#flags = db.DB_CREATE | db.DB_TRUNCATE
# we used db.DB_TRUNCATE flag for this before but Berkeley DB
# 4.2.52 changed to disallowed truncate with txn environments.
if file is not None and os.path.isfile(file):
raise error, "flags should be one of 'r', 'w', 'c' or 'n'"
return flags | db.DB_THREAD
#----------------------------------------------------------------------
# This is a silly little hack that allows apps to continue to use the
# DB_THREAD flag even on systems without threads without freaking out
# This assumes that if Python was built with thread support then
# 2to3 automatically changes "import thread" to "import _thread"
#----------------------------------------------------------------------