1#----------------------------------------------------------------------
2#  Copyright (c) 1999-2001, Digital Creations, Fredericksburg, VA, USA
3#  and Andrew Kuchling. All rights reserved.
4#
5#  Redistribution and use in source and binary forms, with or without
6#  modification, are permitted provided that the following conditions are
7#  met:
8#
9#    o Redistributions of source code must retain the above copyright
10#      notice, this list of conditions, and the disclaimer that follows.
11#
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.
16#
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.
20#
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#----------------------------------------------------------------------
34
35
36"""Support for Berkeley DB 4.3 through 5.3 with a simple interface.
37
38For the full featured object oriented interface use the bsddb.db module
39instead.  It mirrors the Oracle Berkeley DB C API.
40"""
41
42import sys
43absolute_import = (sys.version_info[0] >= 3)
44
45if (sys.version_info >= (2, 6)) and (sys.version_info < (3, 0)) :
46    import warnings
47    if sys.py3kwarning and (__name__ != 'bsddb3') :
48        warnings.warnpy3k("in 3.x, the bsddb module has been removed; "
49                          "please use the pybsddb project instead",
50                          DeprecationWarning, 2)
51    warnings.filterwarnings("ignore", ".*CObject.*", DeprecationWarning,
52                            "bsddb.__init__")
53
54try:
55    if __name__ == 'bsddb3':
56        # import _pybsddb binary as it should be the more recent version from
57        # a standalone pybsddb addon package than the version included with
58        # python as bsddb._bsddb.
59        if absolute_import :
60            # Because this syntaxis is not valid before Python 2.5
61            exec("from . import _pybsddb")
62        else :
63            import _pybsddb
64        _bsddb = _pybsddb
65        from bsddb3.dbutils import DeadlockWrap as _DeadlockWrap
66    else:
67        import _bsddb
68        from bsddb.dbutils import DeadlockWrap as _DeadlockWrap
69except ImportError:
70    # Remove ourselves from sys.modules
71    import sys
72    del sys.modules[__name__]
73    raise
74
75# bsddb3 calls it db, but provide _db for backwards compatibility
76db = _db = _bsddb
77__version__ = db.__version__
78
79error = db.DBError  # So bsddb.error will mean something...
80
81#----------------------------------------------------------------------
82
83import sys, os
84
85from weakref import ref
86
87if sys.version_info < (2, 6) :
88    import UserDict
89    MutableMapping = UserDict.DictMixin
90else :
91    import collections
92    MutableMapping = collections.MutableMapping
93
94class _iter_mixin(MutableMapping):
95    def _make_iter_cursor(self):
96        cur = _DeadlockWrap(self.db.cursor)
97        key = id(cur)
98        self._cursor_refs[key] = ref(cur, self._gen_cref_cleaner(key))
99        return cur
100
101    def _gen_cref_cleaner(self, key):
102        # use generate the function for the weakref callback here
103        # to ensure that we do not hold a strict reference to cur
104        # in the callback.
105        return lambda ref: self._cursor_refs.pop(key, None)
106
107    def __iter__(self):
108        self._kill_iteration = False
109        self._in_iter += 1
110        try:
111            try:
112                cur = self._make_iter_cursor()
113
114                # FIXME-20031102-greg: race condition.  cursor could
115                # be closed by another thread before this call.
116
117                # since we're only returning keys, we call the cursor
118                # methods with flags=0, dlen=0, dofs=0
119                key = _DeadlockWrap(cur.first, 0,0,0)[0]
120                yield key
121
122                next = getattr(cur, "next")
123                while 1:
124                    try:
125                        key = _DeadlockWrap(next, 0,0,0)[0]
126                        yield key
127                    except _bsddb.DBCursorClosedError:
128                        if self._kill_iteration:
129                            raise RuntimeError('Database changed size '
130                                               'during iteration.')
131                        cur = self._make_iter_cursor()
132                        # FIXME-20031101-greg: race condition.  cursor could
133                        # be closed by another thread before this call.
134                        _DeadlockWrap(cur.set, key,0,0,0)
135                        next = getattr(cur, "next")
136            except _bsddb.DBNotFoundError:
137                pass
138            except _bsddb.DBCursorClosedError:
139                # the database was modified during iteration.  abort.
140                pass
141# When Python 2.4 not supported in bsddb3, we can change this to "finally"
142        except :
143            self._in_iter -= 1
144            raise
145
146        self._in_iter -= 1
147
148    def iteritems(self):
149        if not self.db:
150            return
151        self._kill_iteration = False
152        self._in_iter += 1
153        try:
154            try:
155                cur = self._make_iter_cursor()
156
157                # FIXME-20031102-greg: race condition.  cursor could
158                # be closed by another thread before this call.
159
160                kv = _DeadlockWrap(cur.first)
161                key = kv[0]
162                yield kv
163
164                next = getattr(cur, "next")
165                while 1:
166                    try:
167                        kv = _DeadlockWrap(next)
168                        key = kv[0]
169                        yield kv
170                    except _bsddb.DBCursorClosedError:
171                        if self._kill_iteration:
172                            raise RuntimeError('Database changed size '
173                                               'during iteration.')
174                        cur = self._make_iter_cursor()
175                        # FIXME-20031101-greg: race condition.  cursor could
176                        # be closed by another thread before this call.
177                        _DeadlockWrap(cur.set, key,0,0,0)
178                        next = getattr(cur, "next")
179            except _bsddb.DBNotFoundError:
180                pass
181            except _bsddb.DBCursorClosedError:
182                # the database was modified during iteration.  abort.
183                pass
184# When Python 2.4 not supported in bsddb3, we can change this to "finally"
185        except :
186            self._in_iter -= 1
187            raise
188
189        self._in_iter -= 1
190
191
192class _DBWithCursor(_iter_mixin):
193    """
194    A simple wrapper around DB that makes it look like the bsddbobject in
195    the old module.  It uses a cursor as needed to provide DB traversal.
196    """
197    def __init__(self, db):
198        self.db = db
199        self.db.set_get_returns_none(0)
200
201        # FIXME-20031101-greg: I believe there is still the potential
202        # for deadlocks in a multithreaded environment if someone
203        # attempts to use the any of the cursor interfaces in one
204        # thread while doing a put or delete in another thread.  The
205        # reason is that _checkCursor and _closeCursors are not atomic
206        # operations.  Doing our own locking around self.dbc,
207        # self.saved_dbc_key and self._cursor_refs could prevent this.
208        # TODO: A test case demonstrating the problem needs to be written.
209
210        # self.dbc is a DBCursor object used to implement the
211        # first/next/previous/last/set_location methods.
212        self.dbc = None
213        self.saved_dbc_key = None
214
215        # a collection of all DBCursor objects currently allocated
216        # by the _iter_mixin interface.
217        self._cursor_refs = {}
218        self._in_iter = 0
219        self._kill_iteration = False
220
221    def __del__(self):
222        self.close()
223
224    def _checkCursor(self):
225        if self.dbc is None:
226            self.dbc = _DeadlockWrap(self.db.cursor)
227            if self.saved_dbc_key is not None:
228                _DeadlockWrap(self.dbc.set, self.saved_dbc_key)
229                self.saved_dbc_key = None
230
231    # This method is needed for all non-cursor DB calls to avoid
232    # Berkeley DB deadlocks (due to being opened with DB_INIT_LOCK
233    # and DB_THREAD to be thread safe) when intermixing database
234    # operations that use the cursor internally with those that don't.
235    def _closeCursors(self, save=1):
236        if self.dbc:
237            c = self.dbc
238            self.dbc = None
239            if save:
240                try:
241                    self.saved_dbc_key = _DeadlockWrap(c.current, 0,0,0)[0]
242                except db.DBError:
243                    pass
244            _DeadlockWrap(c.close)
245            del c
246        for cref in self._cursor_refs.values():
247            c = cref()
248            if c is not None:
249                _DeadlockWrap(c.close)
250
251    def _checkOpen(self):
252        if self.db is None:
253            raise error, "BSDDB object has already been closed"
254
255    def isOpen(self):
256        return self.db is not None
257
258    def __len__(self):
259        self._checkOpen()
260        return _DeadlockWrap(lambda: len(self.db))  # len(self.db)
261
262    if sys.version_info >= (2, 6) :
263        def __repr__(self) :
264            if self.isOpen() :
265                return repr(dict(_DeadlockWrap(self.db.items)))
266            return repr(dict())
267
268    def __getitem__(self, key):
269        self._checkOpen()
270        return _DeadlockWrap(lambda: self.db[key])  # self.db[key]
271
272    def __setitem__(self, key, value):
273        self._checkOpen()
274        self._closeCursors()
275        if self._in_iter and key not in self:
276            self._kill_iteration = True
277        def wrapF():
278            self.db[key] = value
279        _DeadlockWrap(wrapF)  # self.db[key] = value
280
281    def __delitem__(self, key):
282        self._checkOpen()
283        self._closeCursors()
284        if self._in_iter and key in self:
285            self._kill_iteration = True
286        def wrapF():
287            del self.db[key]
288        _DeadlockWrap(wrapF)  # del self.db[key]
289
290    def close(self):
291        self._closeCursors(save=0)
292        if self.dbc is not None:
293            _DeadlockWrap(self.dbc.close)
294        v = 0
295        if self.db is not None:
296            v = _DeadlockWrap(self.db.close)
297        self.dbc = None
298        self.db = None
299        return v
300
301    def keys(self):
302        self._checkOpen()
303        return _DeadlockWrap(self.db.keys)
304
305    def has_key(self, key):
306        self._checkOpen()
307        return _DeadlockWrap(self.db.has_key, key)
308
309    def set_location(self, key):
310        self._checkOpen()
311        self._checkCursor()
312        return _DeadlockWrap(self.dbc.set_range, key)
313
314    def next(self):  # Renamed by "2to3"
315        self._checkOpen()
316        self._checkCursor()
317        rv = _DeadlockWrap(getattr(self.dbc, "next"))
318        return rv
319
320    if sys.version_info[0] >= 3 :  # For "2to3" conversion
321        next = __next__
322
323    def previous(self):
324        self._checkOpen()
325        self._checkCursor()
326        rv = _DeadlockWrap(self.dbc.prev)
327        return rv
328
329    def first(self):
330        self._checkOpen()
331        # fix 1725856: don't needlessly try to restore our cursor position
332        self.saved_dbc_key = None
333        self._checkCursor()
334        rv = _DeadlockWrap(self.dbc.first)
335        return rv
336
337    def last(self):
338        self._checkOpen()
339        # fix 1725856: don't needlessly try to restore our cursor position
340        self.saved_dbc_key = None
341        self._checkCursor()
342        rv = _DeadlockWrap(self.dbc.last)
343        return rv
344
345    def sync(self):
346        self._checkOpen()
347        return _DeadlockWrap(self.db.sync)
348
349
350#----------------------------------------------------------------------
351# Compatibility object factory functions
352
353def hashopen(file, flag='c', mode=0666, pgsize=None, ffactor=None, nelem=None,
354            cachesize=None, lorder=None, hflags=0):
355
356    flags = _checkflag(flag, file)
357    e = _openDBEnv(cachesize)
358    d = db.DB(e)
359    d.set_flags(hflags)
360    if pgsize is not None:    d.set_pagesize(pgsize)
361    if lorder is not None:    d.set_lorder(lorder)
362    if ffactor is not None:   d.set_h_ffactor(ffactor)
363    if nelem is not None:     d.set_h_nelem(nelem)
364    d.open(file, db.DB_HASH, flags, mode)
365    return _DBWithCursor(d)
366
367#----------------------------------------------------------------------
368
369def btopen(file, flag='c', mode=0666,
370            btflags=0, cachesize=None, maxkeypage=None, minkeypage=None,
371            pgsize=None, lorder=None):
372
373    flags = _checkflag(flag, file)
374    e = _openDBEnv(cachesize)
375    d = db.DB(e)
376    if pgsize is not None: d.set_pagesize(pgsize)
377    if lorder is not None: d.set_lorder(lorder)
378    d.set_flags(btflags)
379    if minkeypage is not None: d.set_bt_minkey(minkeypage)
380    if maxkeypage is not None: d.set_bt_maxkey(maxkeypage)
381    d.open(file, db.DB_BTREE, flags, mode)
382    return _DBWithCursor(d)
383
384#----------------------------------------------------------------------
385
386
387def rnopen(file, flag='c', mode=0666,
388            rnflags=0, cachesize=None, pgsize=None, lorder=None,
389            rlen=None, delim=None, source=None, pad=None):
390
391    flags = _checkflag(flag, file)
392    e = _openDBEnv(cachesize)
393    d = db.DB(e)
394    if pgsize is not None: d.set_pagesize(pgsize)
395    if lorder is not None: d.set_lorder(lorder)
396    d.set_flags(rnflags)
397    if delim is not None: d.set_re_delim(delim)
398    if rlen is not None: d.set_re_len(rlen)
399    if source is not None: d.set_re_source(source)
400    if pad is not None: d.set_re_pad(pad)
401    d.open(file, db.DB_RECNO, flags, mode)
402    return _DBWithCursor(d)
403
404#----------------------------------------------------------------------
405
406def _openDBEnv(cachesize):
407    e = db.DBEnv()
408    if cachesize is not None:
409        if cachesize >= 20480:
410            e.set_cachesize(0, cachesize)
411        else:
412            raise error, "cachesize must be >= 20480"
413    e.set_lk_detect(db.DB_LOCK_DEFAULT)
414    e.open('.', db.DB_PRIVATE | db.DB_CREATE | db.DB_THREAD | db.DB_INIT_LOCK | db.DB_INIT_MPOOL)
415    return e
416
417def _checkflag(flag, file):
418    if flag == 'r':
419        flags = db.DB_RDONLY
420    elif flag == 'rw':
421        flags = 0
422    elif flag == 'w':
423        flags =  db.DB_CREATE
424    elif flag == 'c':
425        flags =  db.DB_CREATE
426    elif flag == 'n':
427        flags = db.DB_CREATE
428        #flags = db.DB_CREATE | db.DB_TRUNCATE
429        # we used db.DB_TRUNCATE flag for this before but Berkeley DB
430        # 4.2.52 changed to disallowed truncate with txn environments.
431        if file is not None and os.path.isfile(file):
432            os.unlink(file)
433    else:
434        raise error, "flags should be one of 'r', 'w', 'c' or 'n'"
435    return flags | db.DB_THREAD
436
437#----------------------------------------------------------------------
438
439
440# This is a silly little hack that allows apps to continue to use the
441# DB_THREAD flag even on systems without threads without freaking out
442# Berkeley DB.
443#
444# This assumes that if Python was built with thread support then
445# Berkeley DB was too.
446
447try:
448    # 2to3 automatically changes "import thread" to "import _thread"
449    import thread as T
450    del T
451
452except ImportError:
453    db.DB_THREAD = 0
454
455#----------------------------------------------------------------------
456