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
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
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
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.
43 absolute_import
= (sys
.version_info
[0] >= 3)
47 warnings
.warnpy3k("in 3.x, bsddb has been removed; "
48 "please use the pybsddb project instead",
49 DeprecationWarning, 2)
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.
57 # Because this syntaxis is not valid before Python 2.5
58 exec("from . import _pybsddb")
62 from bsddb3
.dbutils
import DeadlockWrap
as _DeadlockWrap
65 from bsddb
.dbutils
import DeadlockWrap
as _DeadlockWrap
67 # Remove ourselves from sys.modules
69 del sys
.modules
[__name__
]
72 # bsddb3 calls it db, but provide _db for backwards compatibility
74 __version__
= db
.__version
__
76 error
= db
.DBError
# So bsddb.error will mean something...
78 #----------------------------------------------------------------------
82 from weakref
import ref
84 if sys
.version_info
[0:2] <= (2, 5) :
86 MutableMapping
= UserDict
.DictMixin
89 MutableMapping
= collections
.MutableMapping
91 class _iter_mixin(MutableMapping
):
92 def _make_iter_cursor(self
):
93 cur
= _DeadlockWrap(self
.db
.cursor
)
95 self
._cursor
_refs
[key
] = ref(cur
, self
._gen
_cref
_cleaner
(key
))
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
102 return lambda ref
: self
._cursor
_refs
.pop(key
, None)
105 self
._kill
_iteration
= False
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]
119 next
= getattr(cur
, "next")
122 key
= _DeadlockWrap(next
, 0,0,0)[0]
124 except _bsddb
.DBCursorClosedError
:
125 if self
._kill
_iteration
:
126 raise RuntimeError('Database changed size '
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
:
135 except _bsddb
.DBCursorClosedError
:
136 # the database was modified during iteration. abort.
138 # When Python 2.3 not supported in bsddb3, we can change this to "finally"
148 self
._kill
_iteration
= False
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
)
161 next
= getattr(cur
, "next")
164 kv
= _DeadlockWrap(next
)
167 except _bsddb
.DBCursorClosedError
:
168 if self
._kill
_iteration
:
169 raise RuntimeError('Database changed size '
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
:
178 except _bsddb
.DBCursorClosedError
:
179 # the database was modified during iteration. abort.
181 # When Python 2.3 not supported in bsddb3, we can change this to "finally"
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
):
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.
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
= {}
216 self
._kill
_iteration
= False
221 def _checkCursor(self
):
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):
238 self
.saved_dbc_key
= _DeadlockWrap(c
.current
, 0,0,0)[0]
241 _DeadlockWrap(c
.close
)
243 for cref
in self
._cursor
_refs
.values():
246 _DeadlockWrap(c
.close
)
248 def _checkOpen(self
):
250 raise error
, "BSDDB object has already been closed"
253 return self
.db
is not None
257 return _DeadlockWrap(lambda: len(self
.db
)) # len(self.db)
259 if sys
.version_info
[0:2] >= (2, 6) :
262 return repr(dict(_DeadlockWrap(self
.db
.items
)))
265 def __getitem__(self
, key
):
267 return _DeadlockWrap(lambda: self
.db
[key
]) # self.db[key]
269 def __setitem__(self
, key
, value
):
272 if self
._in
_iter
and key
not in self
:
273 self
._kill
_iteration
= True
276 _DeadlockWrap(wrapF
) # self.db[key] = value
278 def __delitem__(self
, key
):
281 if self
._in
_iter
and key
in self
:
282 self
._kill
_iteration
= True
285 _DeadlockWrap(wrapF
) # del self.db[key]
288 self
._closeCursors
(save
=0)
289 if self
.dbc
is not None:
290 _DeadlockWrap(self
.dbc
.close
)
292 if self
.db
is not None:
293 v
= _DeadlockWrap(self
.db
.close
)
300 return _DeadlockWrap(self
.db
.keys
)
302 def has_key(self
, key
):
304 return _DeadlockWrap(self
.db
.has_key
, key
)
306 def set_location(self
, key
):
309 return _DeadlockWrap(self
.dbc
.set_range
, key
)
311 def next(self
): # Renamed by "2to3"
314 rv
= _DeadlockWrap(getattr(self
.dbc
, "next"))
317 if sys
.version_info
[0] >= 3 : # For "2to3" conversion
323 rv
= _DeadlockWrap(self
.dbc
.prev
)
328 # fix 1725856: don't needlessly try to restore our cursor position
329 self
.saved_dbc_key
= None
331 rv
= _DeadlockWrap(self
.dbc
.first
)
336 # fix 1725856: don't needlessly try to restore our cursor position
337 self
.saved_dbc_key
= None
339 rv
= _DeadlockWrap(self
.dbc
.last
)
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
)
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
)
373 if pgsize
is not None: d
.set_pagesize(pgsize
)
374 if lorder
is not None: d
.set_lorder(lorder
)
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
)
391 if pgsize
is not None: d
.set_pagesize(pgsize
)
392 if lorder
is not None: d
.set_lorder(lorder
)
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
):
405 if cachesize
is not None:
406 if cachesize
>= 20480:
407 e
.set_cachesize(0, cachesize
)
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
)
414 def _checkflag(flag
, file):
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):
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
441 # This assumes that if Python was built with thread support then
442 # Berkeley DB was too.
450 #----------------------------------------------------------------------