/*
* Handwritten code to wrap version 3.x of the Berkeley DB library,
* written to replace a SWIG-generated file. It has since been updated
- * to compile with BerkeleyDB versions 3.2 through 4.1.
+ * to compile with BerkeleyDB versions 3.2 through 4.2.
*
* This module was started by Andrew Kuchling to remove the dependency
* on SWIG in a package by Gregory P. Smith <greg@electricrain.com> who
/* --------------------------------------------------------------------- */
+#include <stddef.h> /* for offsetof() */
#include <Python.h>
#include <db.h>
/* 40 = 4.0, 33 = 3.3; this will break if the second number is > 9 */
#define DBVER (DB_VERSION_MAJOR * 10 + DB_VERSION_MINOR)
+#if DB_VERSION_MINOR > 9
+#error "eek! DBVER can't handle minor versions > 9"
+#endif
-#define PY_BSDDB_VERSION "4.1.5"
-static char *rcs_id = "Id: _bsddb.c,v 1.13 2003/04/24 14:28:08 bwarsaw Exp ";
+#define PY_BSDDB_VERSION "4.2.4"
+static char *rcs_id = "$Id: _rpmdb.c,v 1.11 2003/12/16 03:41:35 jbj Exp $";
#ifdef WITH_THREAD
#endif
-
-/* What is the default behaviour when DB->get or DBCursor->get returns a
- DB_NOTFOUND error? Return None or raise an exception? */
-#define GET_RETURNS_NONE_DEFAULT 1
-
-
/* Should DB_INCOMPLETE be turned into a warning or an exception? */
#define INCOMPLETE_IS_WARNING 1
/* Exceptions */
static PyObject* DBError; /* Base class, all others derive from this */
+static PyObject* DBCursorClosedError; /* raised when trying to use a closed cursor object */
static PyObject* DBKeyEmptyError; /* DB_KEYEMPTY */
static PyObject* DBKeyExistError; /* DB_KEYEXIST */
static PyObject* DBLockDeadlockError; /* DB_LOCK_DEADLOCK */
/* --------------------------------------------------------------------- */
/* Structure definitions */
+#if PYTHON_API_VERSION >= 1010 /* python >= 2.1 support weak references */
+#define HAVE_WEAKREF
+#else
+#undef HAVE_WEAKREF
+#endif
+
+struct behaviourFlags {
+ /* What is the default behaviour when DB->get or DBCursor->get returns a
+ DB_NOTFOUND error? Return None or raise an exception? */
+ unsigned int getReturnsNone : 1;
+ /* What is the default behaviour for DBCursor.set* methods when DBCursor->get
+ * returns a DB_NOTFOUND error? Return None or raise an exception? */
+ unsigned int cursorSetReturnsNone : 1;
+};
+
+#define DEFAULT_GET_RETURNS_NONE 1
+#define DEFAULT_CURSOR_SET_RETURNS_NONE 1 /* 0 in pybsddb < 4.2, python < 2.4 */
+
typedef struct {
PyObject_HEAD
DB_ENV* db_env;
u_int32_t flags; /* saved flags from open() */
int closed;
- int getReturnsNone;
+ struct behaviourFlags moduleFlags;
} DBEnvObject;
u_int32_t flags; /* saved flags from open() */
u_int32_t setflags; /* saved flags from set_flags() */
int haveStat;
- int getReturnsNone;
+ struct behaviourFlags moduleFlags;
#if (DBVER >= 33)
PyObject* associateCallback;
int primaryDBType;
PyObject_HEAD
DBC* dbc;
DBObject* mydb;
+#ifdef HAVE_WEAKREF
+ PyObject *in_weakreflist; /* List of weak references */
+#endif
} DBCursorObject;
#define RETURN_NONE() Py_INCREF(Py_None); return Py_None;
-#define CHECK_DB_NOT_CLOSED(dbobj) \
- if (dbobj->db == NULL) { \
- PyErr_SetObject(DBError, Py_BuildValue("(is)", 0, \
- "DB object has been closed")); \
- return NULL; \
+#define _CHECK_OBJECT_NOT_CLOSED(nonNull, pyErrObj, name) \
+ if ((nonNull) == NULL) { \
+ PyObject *errTuple = NULL; \
+ errTuple = Py_BuildValue("(is)", 0, #name " object has been closed"); \
+ PyErr_SetObject((pyErrObj), errTuple); \
+ Py_DECREF(errTuple); \
+ return NULL; \
}
-#define CHECK_ENV_NOT_CLOSED(env) \
- if (env->db_env == NULL) { \
- PyErr_SetObject(DBError, Py_BuildValue("(is)", 0, \
- "DBEnv object has been closed"));\
- return NULL; \
- }
+#define CHECK_DB_NOT_CLOSED(dbobj) \
+ _CHECK_OBJECT_NOT_CLOSED(dbobj->db, DBError, DB)
-#define CHECK_CURSOR_NOT_CLOSED(curs) \
- if (curs->dbc == NULL) { \
- PyErr_SetObject(DBError, Py_BuildValue("(is)", 0, \
- "DBCursor object has been closed"));\
- return NULL; \
- }
+#define CHECK_ENV_NOT_CLOSED(env) \
+ _CHECK_OBJECT_NOT_CLOSED(env->db_env, DBError, DBEnv)
+#define CHECK_CURSOR_NOT_CLOSED(curs) \
+ _CHECK_OBJECT_NOT_CLOSED(curs->dbc, DBCursorClosedError, DBCursor)
#define CHECK_DBFLAG(mydb, flag) (((mydb)->flags & (flag)) || \
char* kwnames[] = { "flags", "dlen", "doff", NULL };
if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, kwnames,
- &flags, &dlen, &doff))
+ &flags, &dlen, &doff))
return NULL;
CHECK_CURSOR_NOT_CLOSED(self);
err = self->dbc->c_get(self->dbc, &key, &data, flags);
MYDB_END_ALLOW_THREADS;
- if ((err == DB_NOTFOUND) && self->mydb->getReturnsNone) {
+ if ((err == DB_NOTFOUND) && self->mydb->moduleFlags.getReturnsNone) {
Py_INCREF(Py_None);
retval = Py_None;
}
}
if (self->myenvobj)
- self->getReturnsNone = self->myenvobj->getReturnsNone;
+ self->moduleFlags = self->myenvobj->moduleFlags;
else
- self->getReturnsNone = GET_RETURNS_NONE_DEFAULT;
+ self->moduleFlags.getReturnsNone = DEFAULT_GET_RETURNS_NONE;
+ self->moduleFlags.cursorSetReturnsNone = DEFAULT_CURSOR_SET_RETURNS_NONE;
MYDB_BEGIN_ALLOW_THREADS;
err = db_create(&self->db, db_env, flags);
self->dbc = dbc;
self->mydb = db;
+#ifdef HAVE_WEAKREF
+ self->in_weakreflist = NULL;
+#endif
Py_INCREF(self->mydb);
return self;
}
DBCursor_dealloc(DBCursorObject* self)
{
int err;
+
+#ifdef HAVE_WEAKREF
+ if (self->in_weakreflist != NULL) {
+ PyObject_ClearWeakRefs((PyObject *) self);
+ }
+#endif
+
if (self->dbc != NULL) {
MYDB_BEGIN_ALLOW_THREADS;
- if (self->mydb->db != NULL)
+ /* If the underlying database has been closed, we don't
+ need to do anything. If the environment has been closed
+ we need to leak, as BerkeleyDB will crash trying to access
+ the environment. There was an exception when the
+ user closed the environment even though there still was
+ a database open. */
+ if (self->mydb->db && self->mydb->myenvobj &&
+ !self->mydb->myenvobj->closed)
err = self->dbc->c_close(self->dbc);
self->dbc = NULL;
MYDB_END_ALLOW_THREADS;
self->closed = 1;
self->flags = flags;
- self->getReturnsNone = GET_RETURNS_NONE_DEFAULT;
+ self->moduleFlags.getReturnsNone = DEFAULT_GET_RETURNS_NONE;
+ self->moduleFlags.cursorSetReturnsNone = DEFAULT_CURSOR_SET_RETURNS_NONE;
MYDB_BEGIN_ALLOW_THREADS;
err = db_env_create(&self->db_env, flags);
if (self->db != NULL) {
if (self->myenvobj)
CHECK_ENV_NOT_CLOSED(self->myenvobj);
- MYDB_BEGIN_ALLOW_THREADS;
err = self->db->close(self->db, flags);
- MYDB_END_ALLOW_THREADS;
self->db = NULL;
RETURN_IF_ERR();
}
err = self->db->get(self->db, txn, &key, &data, flags|consume_flag);
MYDB_END_ALLOW_THREADS;
- if ((err == DB_NOTFOUND) && self->getReturnsNone) {
+ if ((err == DB_NOTFOUND) && self->moduleFlags.getReturnsNone) {
err = 0;
Py_INCREF(Py_None);
retval = Py_None;
Py_INCREF(dfltobj);
retval = dfltobj;
}
- else if ((err == DB_NOTFOUND) && self->getReturnsNone) {
+ else if ((err == DB_NOTFOUND) && self->moduleFlags.getReturnsNone) {
err = 0;
Py_INCREF(Py_None);
retval = Py_None;
err = self->db->get(self->db, txn, &key, &data, flags);
MYDB_END_ALLOW_THREADS;
- if ((err == DB_NOTFOUND) && self->getReturnsNone) {
+ if ((err == DB_NOTFOUND) && self->moduleFlags.getReturnsNone) {
err = 0;
Py_INCREF(Py_None);
retval = Py_None;
free(cursors);
RETURN_IF_ERR();
+ /* FIXME: this is a buggy interface. The returned cursor
+ contains internal references to the passed in cursors
+ but does not hold python references to them or prevent
+ them from being closed prematurely. This can cause
+ python to crash when things are done in the wrong order. */
return (PyObject*) newDBCursorObject(dbc, self);
}
* explicitly passed) but we are in a transaction ready environment:
* add DB_AUTO_COMMIT to allow for older pybsddb apps using transactions
* to work on BerkeleyDB 4.1 without needing to modify their
- * DBEnv or DB open calls.
+ * DBEnv or DB open calls.
* TODO make this behaviour of the library configurable.
*/
flags |= DB_AUTO_COMMIT;
return NULL;
CHECK_DB_NOT_CLOSED(self);
- MYDB_BEGIN_ALLOW_THREADS;
err = self->db->remove(self->db, filename, database, flags);
- MYDB_END_ALLOW_THREADS;
+ self->db = NULL;
RETURN_IF_ERR();
RETURN_NONE();
}
MYDB_END_ALLOW_THREADS;
if (outFileName)
fclose(outFile);
+
+ /* DB.verify acts as a DB handle destructor (like close); this was
+ * documented in BerkeleyDB 4.2 but had the undocumented effect
+ * of not being safe in prior versions while still requiring an explicit
+ * DB.close call afterwards. Lets call close for the user to emulate
+ * the safe 4.2 behaviour. */
+#if (DBVER <= 41)
+ self->db->close(self->db, 0);
+#endif
+ self->db = NULL;
+
RETURN_IF_ERR();
RETURN_NONE();
}
DB_set_get_returns_none(DBObject* self, PyObject* args)
{
int flags=0;
- int oldValue;
+ int oldValue=0;
if (!PyArg_ParseTuple(args,"i:set_get_returns_none", &flags))
return NULL;
CHECK_DB_NOT_CLOSED(self);
- oldValue = self->getReturnsNone;
- self->getReturnsNone = flags;
+ if (self->moduleFlags.getReturnsNone)
+ ++oldValue;
+ if (self->moduleFlags.cursorSetReturnsNone)
+ ++oldValue;
+ self->moduleFlags.getReturnsNone = (flags >= 1);
+ self->moduleFlags.cursorSetReturnsNone = (flags >= 2);
return PyInt_FromLong(oldValue);
}
/*-------------------------------------------------------------- */
/* Mapping and Dictionary-like access routines */
-static int DB_length(DBObject* self)
+int DB_length(DBObject* self)
{
int err;
long size = 0;
}
-static PyObject* DB_subscript(DBObject* self, PyObject* keyobj)
+PyObject* DB_subscript(DBObject* self, PyObject* keyobj)
{
int err;
PyObject* retval;
{
PyErr_Clear();
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Oi|ii:get",
- &kwnames[1],
+ &kwnames[1],
&keyobj, &flags, &dlen, &doff))
{
PyErr_Clear();
MYDB_END_ALLOW_THREADS;
- if ((err == DB_NOTFOUND) && self->mydb->getReturnsNone) {
+ if ((err == DB_NOTFOUND) && self->mydb->moduleFlags.getReturnsNone) {
Py_INCREF(Py_None);
retval = Py_None;
}
MYDB_BEGIN_ALLOW_THREADS;
err = self->dbc->c_get(self->dbc, &key, &data, flags|DB_SET);
MYDB_END_ALLOW_THREADS;
- if (makeDBError(err)) {
+ if ((err == DB_NOTFOUND) && self->mydb->moduleFlags.cursorSetReturnsNone) {
+ Py_INCREF(Py_None);
+ retval = Py_None;
+ }
+ else if (makeDBError(err)) {
retval = NULL;
}
else {
MYDB_BEGIN_ALLOW_THREADS;
err = self->dbc->c_get(self->dbc, &key, &data, flags|DB_SET_RANGE);
MYDB_END_ALLOW_THREADS;
- if (makeDBError(err)) {
+ if ((err == DB_NOTFOUND) && self->mydb->moduleFlags.cursorSetReturnsNone) {
+ Py_INCREF(Py_None);
+ retval = Py_None;
+ }
+ else if (makeDBError(err)) {
retval = NULL;
}
else {
return retval;
}
-
static PyObject*
-DBC_get_both(DBCursorObject* self, PyObject* args)
+_DBC_get_set_both(DBCursorObject* self, PyObject* keyobj, PyObject* dataobj,
+ int flags, unsigned int returnsNone)
{
- int err, flags=0;
+ int err;
DBT key, data;
- PyObject* retval, *keyobj, *dataobj;
-
- if (!PyArg_ParseTuple(args, "OO|i:get_both", &keyobj, &dataobj, &flags))
- return NULL;
-
- CHECK_CURSOR_NOT_CLOSED(self);
+ PyObject* retval;
+ /* the caller did this: CHECK_CURSOR_NOT_CLOSED(self); */
if (!make_key_dbt(self->mydb, keyobj, &key, NULL))
return NULL;
if (!make_dbt(dataobj, &data))
MYDB_BEGIN_ALLOW_THREADS;
err = self->dbc->c_get(self->dbc, &key, &data, flags|DB_GET_BOTH);
MYDB_END_ALLOW_THREADS;
- if (makeDBError(err)) {
+ if ((err == DB_NOTFOUND) && returnsNone) {
+ Py_INCREF(Py_None);
+ retval = Py_None;
+ }
+ else if (makeDBError(err)) {
retval = NULL;
}
else {
return retval;
}
+static PyObject*
+DBC_get_both(DBCursorObject* self, PyObject* args)
+{
+ int flags=0;
+ PyObject *keyobj, *dataobj;
+
+ if (!PyArg_ParseTuple(args, "OO|i:get_both", &keyobj, &dataobj, &flags))
+ return NULL;
+
+ /* if the cursor is closed, self->mydb may be invalid */
+ CHECK_CURSOR_NOT_CLOSED(self);
+
+ return _DBC_get_set_both(self, keyobj, dataobj, flags,
+ self->mydb->moduleFlags.getReturnsNone);
+}
+
+/* Return size of entry */
+static PyObject*
+DBC_get_current_size(DBCursorObject* self, PyObject* args)
+{
+ int err, flags=DB_CURRENT;
+ PyObject* retval = NULL;
+ DBT key, data;
+
+ if (!PyArg_ParseTuple(args, ":get_current_size"))
+ return NULL;
+ CHECK_CURSOR_NOT_CLOSED(self);
+ CLEAR_DBT(key);
+ CLEAR_DBT(data);
+
+ /* We don't allocate any memory, forcing a ENOMEM error and thus
+ getting the record size. */
+ data.flags = DB_DBT_USERMEM;
+ data.ulen = 0;
+ MYDB_BEGIN_ALLOW_THREADS;
+ err = self->dbc->c_get(self->dbc, &key, &data, flags);
+ MYDB_END_ALLOW_THREADS;
+ if (err == ENOMEM || !err) {
+ /* ENOMEM means positive size, !err means zero length value */
+ retval = PyInt_FromLong((long)data.size);
+ err = 0;
+ }
+
+ FREE_DBT(key);
+ FREE_DBT(data);
+ RETURN_IF_ERR();
+ return retval;
+}
+
+static PyObject*
+DBC_set_both(DBCursorObject* self, PyObject* args)
+{
+ int flags=0;
+ PyObject *keyobj, *dataobj;
+
+ if (!PyArg_ParseTuple(args, "OO|i:set_both", &keyobj, &dataobj, &flags))
+ return NULL;
+
+ /* if the cursor is closed, self->mydb may be invalid */
+ CHECK_CURSOR_NOT_CLOSED(self);
+
+ return _DBC_get_set_both(self, keyobj, dataobj, flags,
+ self->mydb->moduleFlags.cursorSetReturnsNone);
+}
+
static PyObject*
DBC_set_recno(DBCursorObject* self, PyObject* args, PyObject *kwargs)
MYDB_BEGIN_ALLOW_THREADS;
err = self->dbc->c_get(self->dbc, &key, &data, flags|DB_SET_RECNO);
MYDB_END_ALLOW_THREADS;
- if (makeDBError(err)) {
+ if ((err == DB_NOTFOUND) && self->mydb->moduleFlags.cursorSetReturnsNone) {
+ Py_INCREF(Py_None);
+ retval = Py_None;
+ }
+ else if (makeDBError(err)) {
retval = NULL;
}
else { /* Can only be used for BTrees, so no need to return int key */
static PyObject*
DBC_join_item(DBCursorObject* self, PyObject* args)
{
- int err;
+ int err, flags=0;
DBT key, data;
PyObject* retval;
- if (!PyArg_ParseTuple(args, ":join_item"))
+ if (!PyArg_ParseTuple(args, "|i:join_item", &flags))
return NULL;
CHECK_CURSOR_NOT_CLOSED(self);
}
MYDB_BEGIN_ALLOW_THREADS;
- err = self->dbc->c_get(self->dbc, &key, &data, DB_JOIN_ITEM);
+ err = self->dbc->c_get(self->dbc, &key, &data, flags | DB_JOIN_ITEM);
MYDB_END_ALLOW_THREADS;
- if (makeDBError(err)) {
+ if ((err == DB_NOTFOUND) && self->mydb->moduleFlags.getReturnsNone) {
+ Py_INCREF(Py_None);
+ retval = Py_None;
+ }
+ else if (makeDBError(err)) {
retval = NULL;
}
else {
- retval = Py_BuildValue("s#s#", key.data, key.size);
+ retval = Py_BuildValue("s#", key.data, key.size);
FREE_DBT(key);
}
#endif /* DBVER >= 40 */
static PyObject*
+DBEnv_set_shm_key(DBEnvObject* self, PyObject* args)
+{
+ int err;
+ long shm_key = 0;
+
+ if (!PyArg_ParseTuple(args, "l:set_shm_key", &shm_key))
+ return NULL;
+ CHECK_ENV_NOT_CLOSED(self);
+
+ err = self->db_env->set_shm_key(self->db_env, shm_key);
+ RETURN_IF_ERR();
+ RETURN_NONE();
+}
+
+static PyObject*
DBEnv_set_cachesize(DBEnvObject* self, PyObject* args)
{
int err, gbytes=0, bytes=0, ncache=0;
DBEnv_set_get_returns_none(DBEnvObject* self, PyObject* args)
{
int flags=0;
- int oldValue;
+ int oldValue=0;
if (!PyArg_ParseTuple(args,"i:set_get_returns_none", &flags))
return NULL;
CHECK_ENV_NOT_CLOSED(self);
- oldValue = self->getReturnsNone;
- self->getReturnsNone = flags;
+ if (self->moduleFlags.getReturnsNone)
+ ++oldValue;
+ if (self->moduleFlags.cursorSetReturnsNone)
+ ++oldValue;
+ self->moduleFlags.getReturnsNone = (flags >= 1);
+ self->moduleFlags.cursorSetReturnsNone = (flags >= 2);
return PyInt_FromLong(oldValue);
}
{"set", (PyCFunction)DBC_set, METH_VARARGS|METH_KEYWORDS},
{"set_range", (PyCFunction)DBC_set_range, METH_VARARGS|METH_KEYWORDS},
{"get_both", (PyCFunction)DBC_get_both, METH_VARARGS},
- {"set_both", (PyCFunction)DBC_get_both, METH_VARARGS},
+ {"get_current_size",(PyCFunction)DBC_get_current_size, METH_VARARGS},
+ {"set_both", (PyCFunction)DBC_set_both, METH_VARARGS},
{"set_recno", (PyCFunction)DBC_set_recno, METH_VARARGS|METH_KEYWORDS},
{"consume", (PyCFunction)DBC_consume, METH_VARARGS|METH_KEYWORDS},
{"next_dup", (PyCFunction)DBC_next_dup, METH_VARARGS|METH_KEYWORDS},
#if (DBVER >= 40)
{"set_timeout", (PyCFunction)DBEnv_set_timeout, METH_VARARGS|METH_KEYWORDS},
#endif
+ {"set_shm_key", (PyCFunction)DBEnv_set_shm_key, METH_VARARGS},
{"set_cachesize", (PyCFunction)DBEnv_set_cachesize, METH_VARARGS},
{"set_data_dir", (PyCFunction)DBEnv_set_data_dir, METH_VARARGS},
#if (DBVER >= 32)
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
+#ifdef HAVE_WEAKREF
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_WEAKREFS, /* tp_flags */
+ 0, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ offsetof(DBCursorObject, in_weakreflist), /* tp_weaklistoffset */
+#endif
};
*/
#define ADD_INT(dict, NAME) _addIntToDict(dict, #NAME, NAME)
+#define MODULE_NAME_MAX_LEN 11
+static char _bsddbModuleName[MODULE_NAME_MAX_LEN+1] = "_bsddb";
-
-void init_rpmdb(void); /* XXX eliminate gcc warning */
-DL_EXPORT(void) init_rpmdb(void)
+DL_EXPORT(void) init_bsddb(void)
{
PyObject* m;
PyObject* d;
#endif
/* Create the module and add the functions */
- m = Py_InitModule("_rpmdb", bsddb_methods);
+ m = Py_InitModule(_bsddbModuleName, bsddb_methods);
/* Add some symbolic constants to the module */
d = PyModule_GetDict(m);
ADD_INT(d, DB_MAX_PAGES);
ADD_INT(d, DB_MAX_RECORDS);
+#if (DBVER >= 42)
+ ADD_INT(d, DB_RPCCLIENT);
+#else
ADD_INT(d, DB_CLIENT);
+ /* allow apps to be written using DB_RPCCLIENT on older BerkeleyDB */
+ _addIntToDict(d, "DB_RPCCLIENT", DB_CLIENT);
+#endif
ADD_INT(d, DB_XA_CREATE);
ADD_INT(d, DB_CREATE);
ADD_INT(d, DB_CHECKPOINT);
ADD_INT(d, DB_CURLSN);
#endif
-#if (DBVER >= 33)
+#if ((DBVER >= 33) && (DBVER <= 41))
ADD_INT(d, DB_COMMIT);
#endif
ADD_INT(d, DB_CONSUME);
ADD_INT(d, DB_NOPANIC);
#endif
+#if (DBVER >= 42)
+ ADD_INT(d, DB_TIME_NOTGRANTED);
+ ADD_INT(d, DB_TXN_NOT_DURABLE);
+ ADD_INT(d, DB_TXN_WRITE_NOSYNC);
+ ADD_INT(d, DB_LOG_AUTOREMOVE);
+ ADD_INT(d, DB_DIRECT_LOG);
+ ADD_INT(d, DB_DIRECT_DB);
+ ADD_INT(d, DB_INIT_REP);
+ ADD_INT(d, DB_ENCRYPT);
+ ADD_INT(d, DB_CHKSUM);
+#endif
+
#if (DBVER >= 41)
ADD_INT(d, DB_ENCRYPT_AES);
ADD_INT(d, DB_AUTO_COMMIT);
#if !INCOMPLETE_IS_WARNING
MAKE_EX(DBIncompleteError);
#endif
+ MAKE_EX(DBCursorClosedError);
MAKE_EX(DBKeyEmptyError);
MAKE_EX(DBKeyExistError);
MAKE_EX(DBLockDeadlockError);
/* Check for errors */
if (PyErr_Occurred()) {
PyErr_Print();
- Py_FatalError("can't initialize module _rpmdb");
+ Py_FatalError("can't initialize module _bsddb");
}
}
+
+/* allow this module to be named _pybsddb so that it can be installed
+ * and imported on top of python >= 2.3 that includes its own older
+ * copy of the library named _bsddb without importing the old version. */
+DL_EXPORT(void) init_pybsddb(void)
+{
+ strncpy(_bsddbModuleName, "_pybsddb", MODULE_NAME_MAX_LEN);
+ init_bsddb();
+}
+
+/* allow this module to be named _rpmdb too. */
+DL_EXPORT(void) init_rpmdb(void)
+{
+ strncpy(_bsddbModuleName, "_rpmdb", MODULE_NAME_MAX_LEN);
+ init_bsddb();
+}
#----------------------------------------------------------------------
-"""Support for BerkeleyDB 3.1 through 4.1.
+"""Support for BerkeleyDB 3.2 through 4.2.
"""
try:
#----------------------------------------------------------------------
+import sys
+
+# for backwards compatibility with python versions older than 2.3, the
+# iterator interface is dynamically defined and added using a mixin
+# class. old python can't tokenize it due to the yield keyword.
+if sys.version >= '2.3':
+ exec """
+import UserDict
+from weakref import ref
+class _iter_mixin(UserDict.DictMixin):
+ def _make_iter_cursor(self):
+ cur = 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 = cur.first(0,0,0)[0]
+ yield key
+
+ next = cur.next
+ while 1:
+ try:
+ key = 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.
+ 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):
+ try:
+ cur = self._make_iter_cursor()
+
+ # FIXME-20031102-greg: race condition. cursor could
+ # be closed by another thread before this call.
+
+ kv = cur.first()
+ key = kv[0]
+ yield kv
+
+ next = cur.next
+ while 1:
+ try:
+ kv = 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.
+ cur.set(key,0,0,0)
+ next = cur.next
+ except _bsddb.DBNotFoundError:
+ return
+ except _bsddb.DBCursorClosedError:
+ # the database was modified during iteration. abort.
+ return
+"""
+else:
+ class _iter_mixin: pass
+
-class _DBWithCursor:
+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.dbc = None
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 = self.db.cursor()
+ if self.saved_dbc_key is not None:
+ 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=True):
+ if self.dbc:
+ c = self.dbc
+ self.dbc = None
+ if save:
+ self.saved_dbc_key = c.current(0,0,0)[0]
+ c.close()
+ del c
+ for cref in self._cursor_refs.values():
+ c = cref()
+ if c is not None:
+ c.close()
def _checkOpen(self):
if self.db is None:
def __setitem__(self, key, value):
self._checkOpen()
+ self._closeCursors()
self.db[key] = value
def __delitem__(self, key):
self._checkOpen()
+ self._closeCursors()
del self.db[key]
def close(self):
+ self._closeCursors(save=False)
if self.dbc is not None:
self.dbc.close()
v = 0
cachesize=None, lorder=None, hflags=0):
flags = _checkflag(flag)
- d = db.DB()
+ e = _openDBEnv()
+ d = db.DB(e)
d.set_flags(hflags)
if cachesize is not None: d.set_cachesize(0, cachesize)
if pgsize is not None: d.set_pagesize(pgsize)
pgsize=None, lorder=None):
flags = _checkflag(flag)
- d = db.DB()
+ e = _openDBEnv()
+ d = db.DB(e)
if cachesize is not None: d.set_cachesize(0, cachesize)
if pgsize is not None: d.set_pagesize(pgsize)
if lorder is not None: d.set_lorder(lorder)
rlen=None, delim=None, source=None, pad=None):
flags = _checkflag(flag)
- d = db.DB()
+ e = _openDBEnv()
+ d = db.DB(e)
if cachesize is not None: d.set_cachesize(0, cachesize)
if pgsize is not None: d.set_pagesize(pgsize)
if lorder is not None: d.set_lorder(lorder)
#----------------------------------------------------------------------
+def _openDBEnv():
+ e = db.DBEnv()
+ e.open('.', db.DB_PRIVATE | db.DB_CREATE | db.DB_THREAD | db.DB_INIT_LOCK | db.DB_INIT_MPOOL)
+ return e
def _checkflag(flag):
if flag == 'r':
envflags = 0
envsetflags = 0
+ _numKeys = 1002 # PRIVATE. NOTE: must be an even value
+
def setUp(self):
if self.useEnv:
homeDir = os.path.join(os.path.dirname(sys.argv[0]), 'db_home')
- def populateDB(self):
+ def populateDB(self, _txn=None):
d = self.d
- for x in range(500):
- key = '%04d' % (1000 - x) # insert keys in reverse order
+
+ for x in range(self._numKeys/2):
+ key = '%04d' % (self._numKeys - x) # insert keys in reverse order
data = self.makeData(key)
- d.put(key, data)
+ d.put(key, data, _txn)
- for x in range(500):
+ d.put('empty value', '', _txn)
+
+ for x in range(self._numKeys/2-1):
key = '%04d' % x # and now some in forward order
data = self.makeData(key)
- d.put(key, data)
+ d.put(key, data, _txn)
+
+ if _txn:
+ _txn.commit()
num = len(d)
if verbose:
if verbose:
print data
- assert len(d) == 1000
+ assert len(d) == self._numKeys
keys = d.keys()
- assert len(keys) == 1000
+ assert len(keys) == self._numKeys
assert type(keys) == type([])
d['new record'] = 'a new record'
- assert len(d) == 1001
+ assert len(d) == self._numKeys+1
keys = d.keys()
- assert len(keys) == 1001
+ assert len(keys) == self._numKeys+1
d['new record'] = 'a replacement record'
- assert len(d) == 1001
+ assert len(d) == self._numKeys+1
keys = d.keys()
- assert len(keys) == 1001
+ assert len(keys) == self._numKeys+1
if verbose:
print "the first 10 keys are:"
assert d.has_key('spam') == 0
items = d.items()
- assert len(items) == 1001
+ assert len(items) == self._numKeys+1
assert type(items) == type([])
assert type(items[0]) == type(())
assert len(items[0]) == 2
pprint(items[:10])
values = d.values()
- assert len(values) == 1001
+ assert len(values) == self._numKeys+1
assert type(values) == type([])
if verbose:
#----------------------------------------
- def test03_SimpleCursorStuff(self):
+ def test03_SimpleCursorStuff(self, get_raises_error=0, set_raises_error=1):
if verbose:
print '\n', '-=' * 30
- print "Running %s.test03_SimpleCursorStuff..." % \
- self.__class__.__name__
+ print "Running %s.test03_SimpleCursorStuff (get_error %s, set_error %s)..." % \
+ (self.__class__.__name__, get_raises_error, set_raises_error)
if self.env and self.dbopenflags & db.DB_AUTO_COMMIT:
txn = self.env.txn_begin()
else:
txn = None
c = self.d.cursor(txn=txn)
-
+
rec = c.first()
count = 0
while rec is not None:
count = count + 1
if verbose and count % 100 == 0:
print rec
- rec = c.next()
-
- assert count == 1000
+ try:
+ rec = c.next()
+ except db.DBNotFoundError, val:
+ if get_raises_error:
+ assert val[0] == db.DB_NOTFOUND
+ if verbose: print val
+ rec = None
+ else:
+ self.fail("unexpected DBNotFoundError")
+ assert c.get_current_size() == len(c.current()[1]), "%s != len(%r)" % (c.get_current_size(), c.current()[1])
+
+ assert count == self._numKeys
rec = c.last()
count = count + 1
if verbose and count % 100 == 0:
print rec
- rec = c.prev()
+ try:
+ rec = c.prev()
+ except db.DBNotFoundError, val:
+ if get_raises_error:
+ assert val[0] == db.DB_NOTFOUND
+ if verbose: print val
+ rec = None
+ else:
+ self.fail("unexpected DBNotFoundError")
- assert count == 1000
+ assert count == self._numKeys
rec = c.set('0505')
rec2 = c.current()
assert rec == rec2
assert rec[0] == '0505'
assert rec[1] == self.makeData('0505')
+ assert c.get_current_size() == len(rec[1])
+ # make sure we get empty values properly
+ rec = c.set('empty value')
+ assert rec[1] == ''
+ assert c.get_current_size() == 0
+
try:
- c.set('bad key')
+ n = c.set('bad key')
except db.DBNotFoundError, val:
assert val[0] == db.DB_NOTFOUND
if verbose: print val
else:
- self.fail("expected exception")
+ if set_raises_error:
+ self.fail("expected exception")
+ if n != None:
+ self.fail("expected None: "+`n`)
rec = c.get_both('0404', self.makeData('0404'))
assert rec == ('0404', self.makeData('0404'))
try:
- c.get_both('0404', 'bad data')
+ n = c.get_both('0404', 'bad data')
except db.DBNotFoundError, val:
assert val[0] == db.DB_NOTFOUND
if verbose: print val
else:
- self.fail("expected exception")
+ if get_raises_error:
+ self.fail("expected exception")
+ if n != None:
+ self.fail("expected None: "+`n`)
if self.d.get_type() == db.DB_BTREE:
rec = c.set_range('011')
# SF pybsddb bug id 667343
del oldcursor
+ def test03b_SimpleCursorWithoutGetReturnsNone0(self):
+ # same test but raise exceptions instead of returning None
+ if verbose:
+ print '\n', '-=' * 30
+ print "Running %s.test03b_SimpleCursorStuffWithoutGetReturnsNone..." % \
+ self.__class__.__name__
+
+ old = self.d.set_get_returns_none(0)
+ assert old == 1
+ self.test03_SimpleCursorStuff(get_raises_error=1, set_raises_error=1)
+
+ def test03c_SimpleCursorGetReturnsNone2(self):
+ # same test but raise exceptions instead of returning None
+ if verbose:
+ print '\n', '-=' * 30
+ print "Running %s.test03c_SimpleCursorStuffWithoutSetReturnsNone..." % \
+ self.__class__.__name__
+
+ old = self.d.set_get_returns_none(2)
+ assert old == 1
+ old = self.d.set_get_returns_none(2)
+ assert old == 2
+ self.test03_SimpleCursorStuff(get_raises_error=0, set_raises_error=0)
#----------------------------------------
def populateDB(self):
- d = self.d
txn = self.env.txn_begin()
- for x in range(500):
- key = '%04d' % (1000 - x) # insert keys in reverse order
- data = self.makeData(key)
- d.put(key, data, txn)
-
- for x in range(500):
- key = '%04d' % x # and now some in forward order
- data = self.makeData(key)
- d.put(key, data, txn)
-
- txn.commit()
-
- num = len(d)
- if verbose:
- print "created %d records" % num
+ BasicTestCase.populateDB(self, _txn=txn)
self.txn = self.env.txn_begin()
if verbose and count % 100 == 0:
print rec
rec = c.next()
- assert count == 1001
+ assert count == self._numKeys+1
c.close() # Cursors *MUST* be closed before commit!
self.txn.commit()
if verbose and (count % 50) == 0:
print rec
rec = c1.next()
- assert count == 1000
+ assert count == self._numKeys
count = 0
rec = c2.first()