| #---------------------------------------------------------------------- |
| # 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 |
| # met: |
| # |
| # 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 |
| # distribution. |
| # |
| # 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 |
| # DAMAGE. |
| #---------------------------------------------------------------------- |
| |
| |
| """Support for BerkeleyDB 3.3 through 4.4 with a simple interface. |
| |
| For the full featured object oriented interface use the bsddb.db module |
| instead. It mirrors the Sleepycat BerkeleyDB C API. |
| """ |
| |
| try: |
| if __name__ == 'bsddb3': |
| # 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. |
| import _pybsddb |
| _bsddb = _pybsddb |
| from bsddb3.dbutils import DeadlockWrap as _DeadlockWrap |
| else: |
| import _bsddb |
| from bsddb.dbutils import DeadlockWrap as _DeadlockWrap |
| except ImportError: |
| # Remove ourselves from sys.modules |
| import sys |
| del sys.modules[__name__] |
| raise |
| |
| # bsddb3 calls it db, but provide _db for backwards compatibility |
| db = _db = _bsddb |
| __version__ = db.__version__ |
| |
| error = db.DBError # So bsddb.error will mean something... |
| |
| #---------------------------------------------------------------------- |
| |
| import sys, os, UserDict |
| from weakref import ref |
| |
| class _iter_mixin(UserDict.DictMixin): |
| def _make_iter_cursor(self): |
| cur = _DeadlockWrap(self.db.cursor) |
| key = id(cur) |
| self._cursor_refs[key] = ref(cur, self._gen_cref_cleaner(key)) |
| return cur |
| |
| 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 |
| # in the callback. |
| return lambda ref: self._cursor_refs.pop(key, None) |
| |
| def __iter__(self): |
| try: |
| 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] |
| yield key |
| |
| next = cur.next |
| while 1: |
| try: |
| key = _DeadlockWrap(next, 0,0,0)[0] |
| yield key |
| except _bsddb.DBCursorClosedError: |
| 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 = cur.next |
| except _bsddb.DBNotFoundError: |
| return |
| except _bsddb.DBCursorClosedError: |
| # the database was modified during iteration. abort. |
| return |
| |
| def iteritems(self): |
| if not self.db: |
| return |
| try: |
| cur = self._make_iter_cursor() |
| |
| # FIXME-20031102-greg: race condition. cursor could |
| # be closed by another thread before this call. |
| |
| kv = _DeadlockWrap(cur.first) |
| key = kv[0] |
| yield kv |
| |
| next = cur.next |
| while 1: |
| try: |
| kv = _DeadlockWrap(next) |
| key = kv[0] |
| yield kv |
| except _bsddb.DBCursorClosedError: |
| 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 = cur.next |
| except _bsddb.DBNotFoundError: |
| return |
| except _bsddb.DBCursorClosedError: |
| # the database was modified during iteration. abort. |
| return |
| |
| 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. |
| """ |
| def __init__(self, db): |
| self.db = db |
| 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.dbc = None |
| self.saved_dbc_key = None |
| |
| # a collection of all DBCursor objects currently allocated |
| # by the _iter_mixin interface. |
| self._cursor_refs = {} |
| |
| def __del__(self): |
| self.close() |
| |
| def _checkCursor(self): |
| if self.dbc is None: |
| 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 |
| # BerkeleyDB 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): |
| if self.dbc: |
| c = self.dbc |
| self.dbc = None |
| if save: |
| try: |
| self.saved_dbc_key = _DeadlockWrap(c.current, 0,0,0)[0] |
| except db.DBError: |
| pass |
| _DeadlockWrap(c.close) |
| del c |
| for cref in self._cursor_refs.values(): |
| c = cref() |
| if c is not None: |
| _DeadlockWrap(c.close) |
| |
| def _checkOpen(self): |
| if self.db is None: |
| raise error, "BSDDB object has already been closed" |
| |
| def isOpen(self): |
| return self.db is not None |
| |
| def __len__(self): |
| self._checkOpen() |
| return _DeadlockWrap(lambda: len(self.db)) # len(self.db) |
| |
| def __getitem__(self, key): |
| self._checkOpen() |
| return _DeadlockWrap(lambda: self.db[key]) # self.db[key] |
| |
| def __setitem__(self, key, value): |
| self._checkOpen() |
| self._closeCursors() |
| def wrapF(): |
| self.db[key] = value |
| _DeadlockWrap(wrapF) # self.db[key] = value |
| |
| def __delitem__(self, key): |
| self._checkOpen() |
| self._closeCursors() |
| def wrapF(): |
| del self.db[key] |
| _DeadlockWrap(wrapF) # del self.db[key] |
| |
| def close(self): |
| self._closeCursors(save=0) |
| if self.dbc is not None: |
| _DeadlockWrap(self.dbc.close) |
| v = 0 |
| if self.db is not None: |
| v = _DeadlockWrap(self.db.close) |
| self.dbc = None |
| self.db = None |
| return v |
| |
| def keys(self): |
| self._checkOpen() |
| return _DeadlockWrap(self.db.keys) |
| |
| def has_key(self, key): |
| self._checkOpen() |
| return _DeadlockWrap(self.db.has_key, key) |
| |
| __contains__ = has_key |
| |
| def set_location(self, key): |
| self._checkOpen() |
| self._checkCursor() |
| return _DeadlockWrap(self.dbc.set_range, key) |
| |
| def next(self): |
| self._checkOpen() |
| self._checkCursor() |
| rv = _DeadlockWrap(self.dbc.next) |
| return rv |
| |
| def previous(self): |
| self._checkOpen() |
| self._checkCursor() |
| rv = _DeadlockWrap(self.dbc.prev) |
| return rv |
| |
| def first(self): |
| self._checkOpen() |
| self._checkCursor() |
| rv = _DeadlockWrap(self.dbc.first) |
| return rv |
| |
| def last(self): |
| self._checkOpen() |
| self._checkCursor() |
| rv = _DeadlockWrap(self.dbc.last) |
| return rv |
| |
| def sync(self): |
| self._checkOpen() |
| return _DeadlockWrap(self.db.sync) |
| |
| class _ExposedProperties: |
| @property |
| def _cursor_refs(self): |
| return self.db._cursor_refs |
| |
| class StringKeys(UserDict.DictMixin, _ExposedProperties): |
| """Wrapper around DB object that automatically encodes |
| all keys as UTF-8; the keys must be strings.""" |
| |
| def __init__(self, db): |
| self.db = db |
| |
| def __len__(self): |
| return len(self.db) |
| |
| def __getitem__(self, key): |
| return self.db[key.encode("utf-8")] |
| |
| def __setitem__(self, key, value): |
| self.db[key.encode("utf-8")] = value |
| |
| def __delitem__(self, key): |
| del self.db[key.encode("utf-8")] |
| |
| def __iter__(self): |
| for k in self.db: |
| yield k.decode("utf-8") |
| |
| def close(self): |
| self.db.close() |
| |
| def keys(self): |
| for k in self.db.keys(): |
| yield k.decode("utf-8") |
| |
| def has_key(self, key): |
| return self.db.has_key(key.encode("utf-8")) |
| |
| __contains__ = has_key |
| |
| def values(self): |
| return self.db.values() |
| |
| def items(self): |
| for k,v in self.db.items(): |
| yield k.decode("utf-8"), v |
| |
| def set_location(self, key): |
| return self.db.set_location(key.encode("utf-8")) |
| |
| def next(self): |
| key, value = self.db.next() |
| return key.decode("utf-8"), value |
| |
| def previous(self): |
| key, value = self.db.previous() |
| return key.decode("utf-8"), value |
| |
| def first(self): |
| key, value = self.db.first() |
| return key.decode("utf-8"), value |
| |
| def last(self): |
| key, value = self.db.last() |
| return key.decode("utf-8"), value |
| |
| def set_location(self, key): |
| key, value = self.db.set_location(key.encode("utf-8")) |
| return key.decode("utf-8"), value |
| |
| def sync(self): |
| return self.db.sync() |
| |
| class StringValues(UserDict.DictMixin, _ExposedProperties): |
| """Wrapper around DB object that automatically encodes |
| all keys as UTF-8; the keys must be strings.""" |
| |
| def __init__(self, db): |
| self.db = db |
| |
| def __len__(self): |
| return len(self.db) |
| |
| def __getitem__(self, key): |
| return self.db[key].decode("utf-8") |
| |
| def __setitem__(self, key, value): |
| self.db[key] = value.encode("utf-8") |
| |
| def __delitem__(self, key): |
| del self.db[key] |
| |
| def __iter__(self): |
| return iter(self.db) |
| |
| def close(self): |
| self.db.close() |
| |
| def keys(self): |
| return self.db.keys() |
| |
| def has_key(self, key): |
| return self.db.has_key(key) |
| |
| __contains__ = has_key |
| |
| def values(self): |
| for v in self.db.values(): |
| yield v.decode("utf-8") |
| |
| def items(self): |
| for k,v in self.db.items(): |
| yield k, v.decode("utf-8") |
| |
| def set_location(self, key): |
| return self.db.set_location(key) |
| |
| def next(self): |
| key, value = self.db.next() |
| return key, value.decode("utf-8") |
| |
| def previous(self): |
| key, value = self.db.previous() |
| return key, value.decode("utf-8") |
| |
| def first(self): |
| key, value = self.db.first() |
| return key, value.decode("utf-8") |
| |
| def last(self): |
| key, value = self.db.last() |
| return key, value.decode("utf-8") |
| |
| def set_location(self, key): |
| key, value = self.db.set_location(key) |
| return key, value.decode("utf-8") |
| |
| def sync(self): |
| return self.db.sync() |
| |
| |
| #---------------------------------------------------------------------- |
| # Compatibility object factory functions |
| |
| def hashopen(file, flag='c', mode=0o666, pgsize=None, ffactor=None, nelem=None, |
| cachesize=None, lorder=None, hflags=0): |
| |
| flags = _checkflag(flag, file) |
| e = _openDBEnv(cachesize) |
| d = db.DB(e) |
| d.set_flags(hflags) |
| 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) |
| return _DBWithCursor(d) |
| |
| #---------------------------------------------------------------------- |
| |
| def btopen(file, flag='c', mode=0o666, |
| btflags=0, cachesize=None, maxkeypage=None, minkeypage=None, |
| pgsize=None, lorder=None): |
| |
| flags = _checkflag(flag, file) |
| e = _openDBEnv(cachesize) |
| d = db.DB(e) |
| if pgsize is not None: d.set_pagesize(pgsize) |
| if lorder is not None: d.set_lorder(lorder) |
| d.set_flags(btflags) |
| 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) |
| return _DBWithCursor(d) |
| |
| #---------------------------------------------------------------------- |
| |
| |
| def rnopen(file, flag='c', mode=0o666, |
| rnflags=0, cachesize=None, pgsize=None, lorder=None, |
| rlen=None, delim=None, source=None, pad=None): |
| |
| flags = _checkflag(flag, file) |
| e = _openDBEnv(cachesize) |
| d = db.DB(e) |
| if pgsize is not None: d.set_pagesize(pgsize) |
| if lorder is not None: d.set_lorder(lorder) |
| d.set_flags(rnflags) |
| 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) |
| return _DBWithCursor(d) |
| |
| #---------------------------------------------------------------------- |
| |
| def _openDBEnv(cachesize): |
| e = db.DBEnv() |
| if cachesize is not None: |
| if cachesize >= 20480: |
| e.set_cachesize(0, cachesize) |
| else: |
| 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) |
| return e |
| |
| def _checkflag(flag, file): |
| if flag == 'r': |
| flags = db.DB_RDONLY |
| elif flag == 'rw': |
| flags = 0 |
| elif flag == 'w': |
| flags = db.DB_CREATE |
| elif flag == 'c': |
| flags = db.DB_CREATE |
| elif flag == 'n': |
| flags = db.DB_CREATE |
| #flags = db.DB_CREATE | db.DB_TRUNCATE |
| # we used db.DB_TRUNCATE flag for this before but BerkeleyDB |
| # 4.2.52 changed to disallowed truncate with txn environments. |
| if file is not None and os.path.isfile(file): |
| os.unlink(file) |
| else: |
| raise error, "flags should be one of 'r', 'w', 'c' or 'n', not "+repr(flag) |
| 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 |
| # BerkeleyDB. |
| # |
| # This assumes that if Python was built with thread support then |
| # BerkeleyDB was too. |
| |
| try: |
| import thread |
| del thread |
| if db.version() < (3, 3, 0): |
| db.DB_THREAD = 0 |
| except ImportError: |
| db.DB_THREAD = 0 |
| |
| #---------------------------------------------------------------------- |