Commit 2a1ae51f authored by Jim Fulton's avatar Jim Fulton

Merged changes from Catalog-BTrees-Integration branch.

parent 0b031bf1
......@@ -237,76 +237,89 @@ BTreeItems_seek(BTreeItems *self, int i)
/* Whew, we got here so we have a valid offset! */
while ((delta = i - pseudoindex) != 0)
{
if (delta < 0)
{
/* First, would we drop below zero? */
if (pseudoindex >= 0 && pseudoindex + delta < 0) goto no_match;
delta = i - pseudoindex;
if (delta)
while (delta)
{
if (delta < 0)
{
/* First, would we drop below zero? */
if (pseudoindex >= 0 && pseudoindex + delta < 0) goto no_match;
/* Next, do we have to backup a bucket? */
if (currentoffset + delta < 0)
{
if (currentbucket == self->firstbucket) goto no_match;
/* Next, do we have to backup a bucket? */
if (currentoffset + delta < 0)
{
if (currentbucket == self->firstbucket) goto no_match;
b=PreviousBucket(currentbucket, self->firstbucket, i);
if (b==NULL) goto no_match;
b=PreviousBucket(currentbucket, self->firstbucket, i);
if (b==NULL) goto no_match;
PER_ALLOW_DEACTIVATION(currentbucket);
ASSIGNB(currentbucket, b);
UNLESS (PER_USE(currentbucket)) goto err;
delta += currentoffset;
pseudoindex -= currentoffset + 1;
if ((currentoffset = currentbucket->len - 1) < 0)
/* We backed into an empty bucket. Fix the psuedo index */
if (++pseudoindex == 0) goto no_match;
}
else
{ /* Local adjustment */
pseudoindex += delta;
currentoffset += delta;
}
if (currentbucket == self->firstbucket &&
currentoffset < self->first) goto no_match;
}
else if (delta > 0)
{
PER_ALLOW_DEACTIVATION(currentbucket);
ASSIGNB(currentbucket, b);
UNLESS (PER_USE(currentbucket)) goto err;
delta += currentoffset;
pseudoindex -= currentoffset + 1;
if ((currentoffset = currentbucket->len - 1) < 0)
/* We backed into an empty bucket. Fix the psuedo index */
if (++pseudoindex == 0) goto no_match;
}
else
{ /* Local adjustment */
pseudoindex += delta;
currentoffset += delta;
}
if (currentbucket == self->firstbucket &&
currentoffset < self->first) goto no_match;
}
else if (delta > 0)
{
/* Simple backwards range check */
if (pseudoindex < 0 && pseudoindex + delta >= 0)
goto no_match;
/* Simple backwards range check */
if (pseudoindex < 0 && pseudoindex + delta >= 0)
goto no_match;
/* Next, do we go forward a bucket? */
if (currentoffset + delta >= currentbucket->len)
{
while (1)
{
if ((b=currentbucket->next) == NULL) goto no_match;
delta -= currentbucket->len - currentoffset;
pseudoindex += (currentbucket->len - currentoffset);
Py_INCREF(b);
PER_ALLOW_DEACTIVATION(currentbucket);
ASSIGNB(currentbucket, b);
UNLESS (PER_USE(currentbucket)) goto err;
currentoffset = 0;
if (currentbucket->len) break;
}
}
else
{ /* Local adjustment */
pseudoindex += delta;
currentoffset += delta;
}
if (currentbucket == self->lastbucket &&
currentoffset > self->last) goto no_match;
/* Next, do we go forward a bucket? */
if (currentoffset + delta >= currentbucket->len)
{
while (1)
{
if (currentbucket == self->lastbucket) goto no_match;
if ((b=currentbucket->next) == NULL) goto no_match;
delta -= currentbucket->len - currentoffset;
pseudoindex += (currentbucket->len - currentoffset);
Py_INCREF(b);
PER_ALLOW_DEACTIVATION(currentbucket);
ASSIGNB(currentbucket, b);
UNLESS (PER_USE(currentbucket)) goto err;
currentoffset = 0;
if (currentbucket->len) break;
}
}
else
{ /* Local adjustment */
pseudoindex += delta;
currentoffset += delta;
}
if (currentbucket == self->lastbucket &&
currentoffset > self->last) goto no_match;
}
}
delta = i - pseudoindex;
}
else
{ /* Sanity check current bucket/offset */
if (currentbucket == self->firstbucket &&currentoffset < self->first)
goto no_match;
if (currentbucket == self->lastbucket && currentoffset > self->last)
goto no_match;
}
PER_ALLOW_DEACTIVATION(currentbucket);
if (currentbucket==self->currentbucket) Py_DECREF(currentbucket);
......@@ -472,14 +485,26 @@ newBTreeItems(char kind,
UNLESS (self = PyObject_NEW(BTreeItems, &BTreeItemsType)) return NULL;
self->kind=kind;
self->first=lowoffset;
self->last=highoffset;
Py_XINCREF(lowbucket);
self->firstbucket = lowbucket;
Py_XINCREF(highbucket);
self->lastbucket = highbucket;
Py_XINCREF(lowbucket);
self->currentbucket = lowbucket;
if (! lowbucket || (lowbucket==highbucket && lowoffset > highoffset))
{
self->firstbucket = 0;
self->lastbucket = 0;
self->currentbucket = 0;
}
else
{
Py_INCREF(lowbucket);
self->firstbucket = lowbucket;
Py_XINCREF(highbucket);
self->lastbucket = highbucket;
Py_XINCREF(lowbucket);
self->currentbucket = lowbucket;
}
self->currentoffset = lowoffset;
self->pseudoindex = 0;
......@@ -525,6 +550,7 @@ nextBTreeItems(SetIteration *i)
PyErr_Clear();
}
}
return 0;
}
static int
......@@ -558,4 +584,5 @@ nextTreeSetItems(SetIteration *i)
PyErr_Clear();
}
}
return 0;
}
......@@ -85,7 +85,7 @@
static char BTree_module_documentation[] =
""
"\n$Id: BTreeModuleTemplate.c,v 1.4 2001/02/19 18:15:10 jim Exp $"
"\n$Id: BTreeModuleTemplate.c,v 1.5 2001/03/15 13:16:22 jim Exp $"
;
#ifdef PERSISTENT
......@@ -113,7 +113,7 @@ static char BTree_module_documentation[] =
#define PER_CHANGED(O) 0
#endif
PyObject *sort_str, *reverse_str;
static PyObject *sort_str, *reverse_str, *items_str, *__setstate___str;
static void PyVar_Assign(PyObject **v, PyObject *e) { Py_XDECREF(*v); *v=e;}
#define ASSIGN(V,E) PyVar_Assign(&(V),(E))
......@@ -129,6 +129,9 @@ static void PyVar_Assign(PyObject **v, PyObject *e) { Py_XDECREF(*v); *v=e;}
#define SameType_Check(O1, O2) ((O1)->ob_type==(O2)->ob_type)
#define ASSERT(C, S, R) if (! (C)) { \
PyErr_SetString(PyExc_AssertionError, (S)); return (R); }
typedef struct BTreeItemStruct {
KEY_TYPE key;
PyObject *value;
......@@ -163,6 +166,9 @@ typedef struct {
BTreeItem *data;
} BTree;
staticforward PyExtensionClass BTreeType;
#define BTREE(O) ((BTree*)(O))
typedef struct SetIteration_s
......@@ -267,6 +273,8 @@ PyMalloc(size_t sz)
{
void *r;
ASSERT(sz > 0, "non-positive size malloc", NULL);
if (r=malloc(sz)) return r;
PyErr_NoMemory();
......@@ -278,10 +286,14 @@ PyRealloc(void *p, size_t sz)
{
void *r;
if (r=realloc(p,sz)) return r;
ASSERT(sz > 0, "non-positive size realloc", NULL);
PyErr_NoMemory();
return NULL;
if (p) r=realloc(p,sz);
else r=malloc(sz);
UNLESS (r) PyErr_NoMemory();
return r;
}
#include "BTreeItemsTemplate.c"
......@@ -290,6 +302,7 @@ PyRealloc(void *p, size_t sz)
#include "BTreeTemplate.c"
#include "TreeSetTemplate.c"
#include "SetOpTemplate.c"
#include "MergeTemplate.c"
static struct PyMethodDef module_methods[] = {
{"difference", (PyCFunction) difference_m, METH_VARARGS,
......@@ -324,6 +337,8 @@ INITMODULE ()
UNLESS (sort_str=PyString_FromString("sort")) return;
UNLESS (reverse_str=PyString_FromString("reverse")) return;
UNLESS (items_str=PyString_FromString("items")) return;
UNLESS (__setstate___str=PyString_FromString("__setstate__")) return;
UNLESS (PyExtensionClassCAPI=PyCObject_Import("ExtensionClass","CAPI"))
return;
......@@ -372,7 +387,7 @@ INITMODULE ()
d = PyModule_GetDict(m);
PyDict_SetItemString(d, "__version__",
PyString_FromString("$Revision: 1.4 $"));
PyString_FromString("$Revision: 1.5 $"));
PyExtensionClass_Export(d,PREFIX "Bucket", BucketType);
PyExtensionClass_Export(d,PREFIX "BTree", BTreeType);
......
......@@ -114,9 +114,11 @@ _BTree_get(BTree *self, PyObject *keyarg, int has_key)
}
if (SameType_Check(self, self->data[min].value))
r=_BTree_get( BTREE(self->data[min].value), keyarg, has_key);
r=_BTree_get( BTREE(self->data[min].value), keyarg,
has_key ? has_key + 1: 0);
else
r=_bucket_get(BUCKET(self->data[min].value), keyarg, has_key);
r=_bucket_get(BUCKET(self->data[min].value), keyarg,
has_key ? has_key + 1: 0);
}
else
{ /* No data */
......@@ -140,27 +142,23 @@ BTree_get(BTree *self, PyObject *key)
}
/*
** BTree_split
**
** Splits a BTree at a given index
**
** Arguments: self The original BTree
** index The index to split at (if out of bounds use midpoint)
** next The BTree to split into
**
** Returns: 0 on success
** -1 on failure
Copy data from the current BTree to the newly created BTree, next.
Reset length to reflect the fact that we've given up some data.
*/
static int
BTree_split(BTree *self, int index, BTree *next)
{
int next_size;
ASSERT(self->len > 1, "split of empty tree", -1);
if (index < 0 || index >= self->len) index=self->len/2;
UNLESS (next->data=PyMalloc(sizeof(BTreeItem)*(self->len-index)))
return -1;
next->len=self->len-index;
next->size=next->len;
memcpy(next->data, self->data+index, sizeof(BTreeItem)*next->size);
next_size=self->len-index;
ASSERT(next_size > 0, "split creates empty tree", -1);
UNLESS (next->data=PyMalloc(sizeof(BTreeItem)*next_size)) return -1;
memcpy(next->data, self->data+index, sizeof(BTreeItem)*next_size);
next->size=next->len=next_size;
self->len = index;
......@@ -168,28 +166,20 @@ BTree_split(BTree *self, int index, BTree *next)
{
PER_USE_OR_RETURN(BTREE(next->data->value), -1);
next->firstbucket = BTREE(next->data->value)->firstbucket;
Py_INCREF(self->firstbucket);
Py_XINCREF(next->firstbucket);
PER_ALLOW_DEACTIVATION(BTREE(next->data->value));
}
else
{
next->firstbucket = BUCKET(next->data->value);
Py_INCREF(next->firstbucket);
Py_XINCREF(next->firstbucket);
}
return 0;
}
/*
** BTree_clone
**
** Split a BTree node into two children, leaving the original node the
** parent.
**
** Arguments: self The BTree
**
** Returns: 0 on success
** -1 on failure
/* Split out data among two newly created BTrees, which become
out children.
*/
static int
BTree_clone(BTree *self)
......@@ -217,7 +207,7 @@ BTree_clone(BTree *self)
n1->len=self->len;
n1->data=self->data;
n1->firstbucket = self->firstbucket;
Py_INCREF(n1->firstbucket);
Py_XINCREF(n1->firstbucket);
/* Initialize our data to hold split data */
self->data=d;
......@@ -225,7 +215,10 @@ BTree_clone(BTree *self)
self->size=2;
self->data->value=OBJECT(n1);
COPY_KEY(self->data[1].key, n2->data->key);
INCREF_KEY(self->data[1].key);
/* We take the unused reference from n2, so there's no reason to INCREF! */
/* INCREF_KEY(self->data[1].key); */
self->data[1].value=OBJECT(n2);
return 0;
......@@ -233,7 +226,7 @@ BTree_clone(BTree *self)
err:
Py_XDECREF(n1);
Py_XDECREF(n2);
free(d);
if (d) free(d);
return -1;
}
......@@ -249,7 +242,7 @@ err:
** -1 on failure
*/
static int
BTree_grow(BTree *self, int index)
BTree_grow(BTree *self, int index, int noval)
{
int i;
PyObject *v, *e=0;
......@@ -309,12 +302,17 @@ BTree_grow(BTree *self, int index)
if (SameType_Check(self, v))
{
COPY_KEY(d->key, BTREE(e)->data->key);
/* We take the unused reference from e, so there's no
reason to INCREF!
*/
/* INCREF_KEY(self->data[1].key); */
}
else
{
COPY_KEY(d->key, BUCKET(e)->keys[0]);
INCREF_KEY(d->key);
}
INCREF_KEY(d->key);
d->value=e;
self->len++;
......@@ -323,8 +321,16 @@ BTree_grow(BTree *self, int index)
}
else
{
UNLESS (d->value=PyObject_CallObject(OBJECT(&BucketType), NULL))
return -1;
if (noval)
{
UNLESS (d->value=PyObject_CallObject(OBJECT(&SetType), NULL))
return -1;
}
else
{
UNLESS (d->value=PyObject_CallObject(OBJECT(&BucketType), NULL))
return -1;
}
self->len=1;
Py_INCREF(d->value);
self->firstbucket = BUCKET(d->value);
......@@ -376,24 +382,21 @@ BTree_deleteNextBucket(BTree *self)
}
/*
** _BTree_set
**
** inserts a key/value pair into the tree
**
** Arguments: self The BTree
** key The key of the item to insert
** value The object to insert
** unique We are inserting a unique key
**
** Returns: -1 on failure
** 0 on successful replacement
** 1 on successful insert with growth
Set (value != 0) or delete (value=0) a tree item.
If unique is non-zero, then only change if the key is
new.
If noval is non-zero, then don't set a value (the tree
is a set).
Return 1 on successful change, 0 is no change, -1 on error.
*/
static int
_BTree_set(BTree *self, PyObject *keyarg, PyObject *value,
int unique, int noval)
{
int i, min, max, cmp, grew, copied=1;
int i, min, max, cmp, grew, copied=1, changed=0, bchanged=0;
BTreeItem *d;
KEY_TYPE key;
......@@ -406,7 +409,7 @@ _BTree_set(BTree *self, PyObject *keyarg, PyObject *value,
{
if (value)
{
if (BTree_grow(self, 0) < 0) return -1;
if (BTree_grow(self, 0, noval) < 0) return -1;
}
else
{
......@@ -433,73 +436,113 @@ _BTree_set(BTree *self, PyObject *keyarg, PyObject *value,
if (SameType_Check(self, d->value))
grew= _BTree_set( BTREE(d->value), keyarg, value, unique, noval);
else
grew=_bucket_set(BUCKET(d->value), keyarg, value, unique, noval);
grew=_bucket_set(BUCKET(d->value), keyarg, value, unique, noval,
&bchanged);
if (grew < 0) goto err;
if (grew)
{
bchanged=1; /* A bucket changed size */
if (value) /* got bigger */
{
if (SameType_Check(self, d->value))
{
if ( BTREE(d->value)->len > MAX_BTREE_SIZE(d->value)
&& BTree_grow(self,min) < 0)
goto err;
if (BTREE(d->value)->len > MAX_BTREE_SIZE(d->value))
{
if (BTree_grow(self, min, noval) < 0) goto err;
changed=1;
}
}
else
{
if ( BUCKET(d->value)->len > MAX_BUCKET_SIZE(d->value)
&& BTree_grow(self,min) < 0)
goto err;
if (BUCKET(d->value)->len > MAX_BUCKET_SIZE(d->value))
{
if (BTree_grow(self, min, noval) < 0) goto err;
changed=1;
}
}
}
else /* got smaller */
{
if (min && grew > 1)
{ /* Somebody below us deleted their first bucket and */
/* and an intermediate tree couldn't handle it. */
if (BTree_deleteNextBucket(BTREE(d[-1].value)) < 0)
goto err;
grew=1; /* Reset flag, since we handled it */
}
if (BUCKET(d->value)->len == 0)
{
if (min)
{
/* Not the first subtree, we can delete it because
we have the previous subtree handy.
*/
if (SameType_Check(self, d->value))
{
if (0 && BTree_deleteNextBucket(BTREE(d[-1].value)) < 0)
{ /* Got empty */
if (! SameType_Check(self, d->value))
{ /* We are about to delete a bucket. */
if (min)
{ /*If it's not our first bucket, we can tell the
previous bucket to adjust it's reference to
it. */
if (Bucket_deleteNextBucket(BUCKET(d[-1].value)) < 0)
goto err;
}
else
{
if (Bucket_deleteNextBucket(BUCKET(d[-1].value)) < 0)
goto err;
{ /* If it's the first bucket, we can't adjust the
reference to it ourselves, so we'll just
increment the grew flag to indicate to a
parent node that it's last bucket should
adjust its reference. If there is no parent,
then there's nothing to do. */
grew++;
}
self->len--;
Py_DECREF(d->value);
DECREF_KEY(d->key);
if (min < self->len)
memmove(d, d+1, (self->len-min)*sizeof(BTreeItem));
}
if (self->len==1 && BUCKET(self->data->value)->len == 0)
{
/* Our last subtree is empty, woo hoo, we can delete it! */
Py_DECREF(self->data->value);
/* Ah hah! I bet you are wondering why we don't
decref the first key. We don't decref it because
we don't initialize it in the first place. So
there!
DECREF_KEY(self->data->key);
*/
self->len=0;
Py_DECREF(self->firstbucket);
self->firstbucket=NULL;
}
self->len--;
Py_DECREF(d->value);
if (min) DECREF_KEY(d->key);
if (min < self->len)
memmove(d, d+1, (self->len-min)*sizeof(BTreeItem));
if (! min)
if (self->len)
{ /* We just deleted our first child, so we need to
adjust our first bucket. */
if (SameType_Check(self, self->data->value))
{
UNLESS (PER_USE(BTREE(self->data->value))) goto err;
ASSIGNB(self->firstbucket,
BTREE(self->data->value)->firstbucket);
Py_XINCREF(self->firstbucket);
PER_ALLOW_DEACTIVATION(BTREE(self->data->value));
}
else
{
ASSIGNB(self->firstbucket, BUCKET(self->data->value));
Py_INCREF(self->firstbucket);
}
/* We can toss our first key now */
DECREF_KEY(self->data->key);
}
else
{
Py_XDECREF(self->firstbucket);
self->firstbucket = 0;
}
changed=1;
}
}
if (PER_CHANGED(self) < 0) goto err;
}
#ifdef PERSISTENT
if (changed
|| (bchanged /* The bucket changed */
&& self->len == 1 /* We have only one */
&& ! SameType_Check(self, self->data->value) /* It's our child */
&& BUCKET(self->data->value)->oid == NULL /* It's in our record */
)
)
if (PER_CHANGED(self) < 0)
goto err;
#endif
PER_ALLOW_DEACTIVATION(self);
return grew;
......@@ -540,18 +583,35 @@ BTree_setitem(BTree *self, PyObject *key, PyObject *v)
static int
_BTree_clear(BTree *self)
{
int i;
int i, l;
for (i=self->len; --i >= 0; )
/* The order in which we dealocate, from "top to bottom" is critical
to prevent memory memory errors when the deallocation stack
becomes huge when dealocating use linked lists of buckets.
*/
if (self->firstbucket)
{
ASSERT(self->firstbucket->ob_refcnt > 1,
"Invalid firstbucket pointer", -1);
Py_DECREF(self->firstbucket);
self->firstbucket=NULL;
}
for (l=self->len, i=0; i < l; i++)
{
if (i) DECREF_KEY(self->data[i].key);
Py_DECREF(self->data[i].value);
}
Py_XDECREF(self->firstbucket);
self->firstbucket=NULL;
self->len=0;
if (self->data)
{
free(self->data);
self->data=0;
self->size=0;
}
return 0;
}
......@@ -559,7 +619,7 @@ _BTree_clear(BTree *self)
static PyObject *
BTree__p_deactivate(BTree *self, PyObject *args)
{
if (self->state==cPersistent_UPTODATE_STATE)
if (self->state==cPersistent_UPTODATE_STATE && self->jar)
{
if (_BTree_clear(self) < 0) return NULL;
self->state=cPersistent_GHOST_STATE;
......@@ -570,17 +630,6 @@ BTree__p_deactivate(BTree *self, PyObject *args)
}
#endif
/*
** BTree_clear
**
** Wrapper for _BTree_clear
**
** Arguments: self the BTree
** args (unused)
**
** Returns: None on success
** NULL on failure
*/
static PyObject *
BTree_clear(BTree *self, PyObject *args)
{
......@@ -602,12 +651,6 @@ err:
return NULL;
}
/*
** BTree_getstate
**
** Get a tuple of all objects in a BTree
**
*/
static PyObject *
BTree_getstate(BTree *self, PyObject *args)
{
......@@ -619,20 +662,37 @@ BTree_getstate(BTree *self, PyObject *args)
if (self->len)
{
UNLESS (r=PyTuple_New(self->len*2-1)) goto err;
for (i=self->len, l=0; --i >= 0; )
if (self->len == 1
&& self->data->value->ob_type != self->ob_type
#ifdef PERSISTENT
&& BUCKET(self->data->value)->oid == NULL
#endif
)
{
/* We have just one bucket. Save it's data directly. */
UNLESS(o=bucket_getstate(BUCKET(self->data->value), NULL)) goto err;
PyTuple_SET_ITEM(r,0,o);
ASSIGN(r, Py_BuildValue("(O)", r));
}
else
{
if (i)
for (i=0, l=0; i < self->len; i++)
{
COPY_KEY_TO_OBJECT(o, self->data[i].key);
if (i)
{
COPY_KEY_TO_OBJECT(o, self->data[i].key);
PyTuple_SET_ITEM(r,l,o);
l++;
}
o=self->data[i].value;
Py_INCREF(o);
PyTuple_SET_ITEM(r,l,o);
l++;
}
o=self->data[i].value;
Py_INCREF(o);
PyTuple_SET_ITEM(r,l,o);
l++;
ASSIGN(r, Py_BuildValue("OO", r, self->firstbucket));
}
ASSIGN(r, Py_BuildValue("OO", r, self->firstbucket));
}
else
{
......@@ -649,41 +709,27 @@ err:
return NULL;
}
/*
** BTree_setstate
**
** Bulk set all objects in a BTree from a tuple
*/
static PyObject *
BTree_setstate(BTree *self, PyObject *args)
static int
_BTree_setstate(BTree *self, PyObject *state, int noval)
{
PyObject *state, *k, *v=0, *items;
PyObject *items, *o, *firstbucket=0;
BTreeItem *d;
Bucket *firstbucket;
int len, l, i, r, copied=1;
if (!PyArg_ParseTuple(args,"O",&state)) return NULL;
PER_PREVENT_DEACTIVATION(self);
if (_BTree_clear(self) < 0) goto err;
if (_BTree_clear(self) < 0) return -1;
if (state != Py_None)
{
if (!PyArg_ParseTuple(state,"O|O",&items, &firstbucket))
goto err;
return -1;
if ((len=PyTuple_Size(items)) < 0) goto err;
if ((len=PyTuple_Size(items)) < 0) return -1;
len=(len+1)/2;
self->firstbucket = firstbucket;
Py_INCREF(firstbucket);
if (len > self->size)
{
UNLESS (d=PyRealloc(self->data, sizeof(BTreeItem)*len)) goto err;
UNLESS (d=PyRealloc(self->data, sizeof(BTreeItem)*len)) return -1;
self->data=d;
self->size=len;
}
......@@ -692,28 +738,108 @@ BTree_setstate(BTree *self, PyObject *args)
{
if (i)
{
COPY_KEY_FROM_ARG(d->key, PyTuple_GET_ITEM(state,l), &copied);
COPY_KEY_FROM_ARG(d->key, PyTuple_GET_ITEM(items,l), &copied);
l++;
UNLESS (&copied) return NULL;
UNLESS (&copied) return -1;
INCREF_KEY(d->key);
}
d->value=PyTuple_GET_ITEM(state,l);
d->value=PyTuple_GET_ITEM(items,l);
if (PyTuple_Check(d->value))
{
if (noval)
{
UNLESS (d->value=PyObject_CallObject(OBJECT(&SetType),
NULL))
return -1;
if (_set_setstate(BUCKET(d->value),
PyTuple_GET_ITEM(items,l))
< 0) return -1;
}
else
{
UNLESS (d->value=PyObject_CallObject(OBJECT(&BucketType),
NULL))
return -1;
if (_bucket_setstate(BUCKET(d->value),
PyTuple_GET_ITEM(items,l))
< 0) return -1;
}
}
else
{
Py_INCREF(d->value);
}
l++;
Py_INCREF(d->value);
}
self->len=l;
if (len)
{
if (! firstbucket) firstbucket=self->data->value;
UNLESS (ExtensionClassSubclassInstance_Check(
firstbucket,
noval ? &SetType : &BucketType))
{
PyErr_SetString(PyExc_TypeError,
"No firstbucket in non-empty BTree");
return -1;
}
self->firstbucket = BUCKET(firstbucket);
Py_INCREF(firstbucket);
}
self->len=len;
}
return 0;
}
static PyObject *
BTree_setstate(BTree *self, PyObject *args)
{
int r;
if (!PyArg_ParseTuple(args,"O",&args)) return NULL;
PER_PREVENT_DEACTIVATION(self);
r=_BTree_setstate(self, args, 0);
PER_ALLOW_DEACTIVATION(self);
if (r < 0) return NULL;
Py_INCREF(Py_None);
return Py_None;
err:
PER_ALLOW_DEACTIVATION(self);
return NULL;
}
#ifdef PERSISTENT
static PyObject *
BTree__p_resolveConflict(BTree *self, PyObject *args)
{
PyObject *s[3], *r;
int i;
UNLESS (PyArg_ParseTuple(args, "OOO", s, s+1, s+2)) return NULL;
/* for each state, detuplefy it twice */
for (i=0; i < 3; i++)
UNLESS (s[i]==Py_None || PyArg_ParseTuple(s[i], "O", s+i)) return NULL;
for (i=0; i < 3; i++)
UNLESS (s[i]==Py_None || PyArg_ParseTuple(s[i], "O", s+i)) return NULL;
for (i=0; i < 3; i++) /* Now make sure detupled thing is a tuple */
UNLESS (s[i]==Py_None || PyTuple_Check(s[i]))
return merge_error(-100, -100, -100, -100);
if (ExtensionClassSubclassInstance_Check(self, &BTreeType))
r = _bucket__p_resolveConflict(OBJECT(&BucketType), s);
else
r = _bucket__p_resolveConflict(OBJECT(&SetType), s);
if (r) ASSIGN(r, Py_BuildValue("((O))", r));
return r;
}
#endif
/*
BTree_findRangeEnd -- Find one end, expressed as a bucket and
......@@ -910,7 +1036,9 @@ BTree_rangeSearch(BTree *self, PyObject *args, char type)
else
{
highbucket = BTree_lastBucket(self);
UNLESS (PER_USE(highbucket)) goto err;
highoffset = highbucket->len - 1;
PER_ALLOW_DEACTIVATION(highbucket);
}
PER_ALLOW_DEACTIVATION(self);
......@@ -1046,7 +1174,8 @@ BTree_has_key(BTree *self, PyObject *args)
{
PyObject *key;
UNLESS (PyArg_ParseTuple(args,"O",&key)) return NULL; return _BTree_get(self, key, 1);
UNLESS (PyArg_ParseTuple(args,"O",&key)) return NULL;
return _BTree_get(self, key, 1);
}
static PyObject *
......@@ -1095,7 +1224,13 @@ static struct PyMethodDef BTree_methods[] = {
"insert(key, value) -- Add an item if the key is not already used.\n\n"
"Return 1 if the item was added, or 0 otherwise"
},
{"update", (PyCFunction) Mapping_update, METH_VARARGS,
"update(collection) -- Add the items from the given collection"},
{"__init__", (PyCFunction) Mapping_update, METH_VARARGS,
"__init__(collection) -- Initialize with items from the given collection"},
#ifdef PERSISTENT
{"_p_resolveConflict", (PyCFunction) BTree__p_resolveConflict, METH_VARARGS,
"_p_resolveConflict() -- Reinitialize from a newly created copy"},
{"_p_deactivate", (PyCFunction) BTree__p_deactivate, METH_VARARGS,
"_p_deactivate() -- Reinitialize from a newly created copy"},
#endif
......@@ -1105,16 +1240,7 @@ static struct PyMethodDef BTree_methods[] = {
static void
BTree_dealloc(BTree *self)
{
int i;
for (i=self->len; --i >= 0; )
{
if (i) DECREF_KEY(self->data[i].key);
Py_DECREF(self->data[i].value);
}
if (self->data) free(self->data);
Py_XDECREF(self->firstbucket);
_BTree_clear(self);
PER_DEL(self);
......
......@@ -111,7 +111,7 @@ _bucket_get(Bucket *self, PyObject *keyarg, int has_key)
if (cmp < 0) min=i;
else if (cmp == 0)
{
if (has_key) r=PyInt_FromLong(1);
if (has_key) r=PyInt_FromLong(has_key);
else
{
COPY_VALUE_TO_OBJECT(r, self->values[i]);
......@@ -184,7 +184,8 @@ Bucket_grow(Bucket *self, int noval)
** 1 on success with a new value (growth)
*/
static int
_bucket_set(Bucket *self, PyObject *keyarg, PyObject *v, int unique, int noval)
_bucket_set(Bucket *self, PyObject *keyarg, PyObject *v,
int unique, int noval, int *changed)
{
int min, max, i, l, cmp, copied=1;
KEY_TYPE key;
......@@ -203,11 +204,21 @@ _bucket_set(Bucket *self, PyObject *keyarg, PyObject *v, int unique, int noval)
{
if (! unique && ! noval && self->values)
{
DECREF_VALUE(self->values[i]);
VALUE_TYPE value;
COPY_VALUE_FROM_ARG(self->values[i], v, &copied);
COPY_VALUE_FROM_ARG(value, v, &copied);
UNLESS(copied) return -1;
#ifdef VALUE_SAME
if (VALUE_SAME(self->values[i], value))
{ /* short-circuit if no change */
PER_ALLOW_DEACTIVATION(self);
return 0;
}
#endif
if (changed) *changed=1;
DECREF_VALUE(self->values[i]);
COPY_VALUE(self->values[i], value);
INCREF_VALUE(self->values[i]);
if (PER_CHANGED(self) < 0) goto err;
}
......@@ -308,10 +319,67 @@ err:
static int
bucket_setitem(Bucket *self, PyObject *key, PyObject *v)
{
if (_bucket_set(self, key, v, 0, 0) < 0) return -1;
if (_bucket_set(self, key, v, 0, 0, 0) < 0) return -1;
return 0;
}
static PyObject *
Mapping_update(PyObject *self, PyObject *args)
{
PyObject *seq=0, *o, *t, *v, *tb, *k;
int i, n=0, ind;
UNLESS(PyArg_ParseTuple(args, "|O:update", &seq)) return NULL;
if (seq)
{
if (PySequence_Check(seq))
{
Py_INCREF(seq);
}
else
{
seq=PyObject_GetAttr(seq, items_str);
UNLESS(seq) return NULL;
ASSIGN(seq, PyObject_CallObject(seq, NULL));
UNLESS(seq) return NULL;
}
for (i=0; ; i++)
{
UNLESS (o=PySequence_GetItem(seq, i))
{
PyErr_Fetch(&t, &v, &tb);
if (t != PyExc_IndexError)
{
PyErr_Restore(t, v, tb);
goto err;
}
Py_XDECREF(t);
Py_XDECREF(v);
Py_XDECREF(tb);
break;
}
ind=PyArg_ParseTuple(o, "OO;items must be 2-item tuples", &k, &v);
if (ind)
ind = PyObject_SetItem(self, k, v);
else
ind=-1;
Py_DECREF(o);
if (ind < 0) goto err;
}
}
Py_INCREF(Py_None);
return Py_None;
err:
Py_DECREF(seq);
return NULL;
}
/*
** bucket_split
**
......@@ -329,6 +397,8 @@ bucket_split(Bucket *self, int index, Bucket *next)
{
int next_size;
ASSERT(self->len > 1, "split of empty bucket", -1);
if (index < 0 || index >= self->len) index=self->len/2;
next_size=self->len-index;
......@@ -726,21 +796,45 @@ bucket_byValue(Bucket *self, PyObject *args)
return NULL;
}
static int
_bucket_clear(Bucket *self)
{
int i;
if (self->next)
{
Py_DECREF(self->next);
self->next=0;
}
for (i=self->len; --i >= 0; )
{
DECREF_KEY(self->keys[i]);
if (self->values) DECREF_VALUE(self->values[i]);
}
self->len=0;
if (self->values)
{
free(self->values);
self->values=0;
}
if (self->keys)
{
free(self->keys);
self->keys=0;
}
self->size=0;
return 0;
}
#ifdef PERSISTENT
static PyObject *
bucket__p_deactivate(Bucket *self, PyObject *args)
{
if (self->state==cPersistent_UPTODATE_STATE)
if (self->state==cPersistent_UPTODATE_STATE && self->jar)
{
int i;
for (i=self->len; --i >= 0; )
{
DECREF_KEY(self->keys[i]);
if (self->values) DECREF_VALUE(self->values[i]);
}
Py_XDECREF(self->next);
self->len=0;
if (_bucket_clear(self) < 0) return NULL;
self->state=cPersistent_GHOST_STATE;
}
......@@ -756,13 +850,11 @@ bucket_clear(Bucket *self, PyObject *args)
PER_USE_OR_RETURN(self, NULL);
for (i=self->len; --i >= 0; )
if (self->len)
{
DECREF_KEY(self->keys[i]);
if (self->values) DECREF_VALUE(self->values[i]);
if (_bucket_clear(self) < 0) return NULL;
if (PER_CHANGED(self) < 0) goto err;
}
self->len=0;
if (PER_CHANGED(self) < 0) goto err;
PER_ALLOW_DEACTIVATION(self);
Py_INCREF(Py_None);
return Py_None;
......@@ -778,22 +870,37 @@ bucket_getstate(Bucket *self, PyObject *args)
PyObject *o=0, *items=0;
int i, len, l;
if (args && ! PyArg_ParseTuple(args, "")) return NULL;
PER_USE_OR_RETURN(self, NULL);
len=self->len;
UNLESS (items=PyTuple_New(len*2)) goto err;
for (i=0, l=0; i < len; i++)
{
COPY_KEY_TO_OBJECT(o, self->keys[i]);
UNLESS (o) goto err;
PyTuple_SET_ITEM(items, l, o);
l++;
COPY_VALUE_TO_OBJECT(o, self->values[i]);
UNLESS (o) goto err;
PyTuple_SET_ITEM(items, l, o);
l++;
if (self->values)
{ /* Bucket */
UNLESS (items=PyTuple_New(len*2)) goto err;
for (i=0, l=0; i < len; i++)
{
COPY_KEY_TO_OBJECT(o, self->keys[i]);
UNLESS (o) goto err;
PyTuple_SET_ITEM(items, l, o);
l++;
COPY_VALUE_TO_OBJECT(o, self->values[i]);
UNLESS (o) goto err;
PyTuple_SET_ITEM(items, l, o);
l++;
}
}
else
{ /* Set */
UNLESS (items=PyTuple_New(len)) goto err;
for (i=0; i < len; i++)
{
COPY_KEY_TO_OBJECT(o, self->keys[i]);
UNLESS (o) goto err;
PyTuple_SET_ITEM(items, i, o);
}
}
if (self->next)
......@@ -811,19 +918,8 @@ err:
return NULL;
}
/*
** bucket_setstate
**
** bulk set of all items in bucket
**
** Arguments: self The Bucket
** args The object pointng to the two lists of tuples
**
** Returns: None on success
** NULL on error
*/
static PyObject *
bucket_setstate(Bucket *self, PyObject *args)
static int
_bucket_setstate(Bucket *self, PyObject *args)
{
PyObject *k, *v, *r, *items;
Bucket *next=0;
......@@ -831,14 +927,11 @@ bucket_setstate(Bucket *self, PyObject *args)
KEY_TYPE *keys;
VALUE_TYPE *values;
PER_PREVENT_DEACTIVATION(self);
UNLESS (PyArg_ParseTuple(args, "O", &args)) goto err;
UNLESS (PyArg_ParseTuple(args, "O|O!", &items, self->ob_type, &next))
goto err;
UNLESS (PyArg_ParseTuple(args, "O|O", &items, &next))
return -1;
if ((len=PyTuple_Size(items)) < 0) goto err;
if ((len=PyTuple_Size(items)) < 0) return -1;
len /= 2;
for (i=self->len; --i >= 0; )
{
......@@ -855,8 +948,10 @@ bucket_setstate(Bucket *self, PyObject *args)
if (len > self->size)
{
UNLESS (keys=PyRealloc(self->keys, sizeof(KEY_TYPE)*len)) goto err;
UNLESS (values=PyRealloc(self->values, sizeof(KEY_TYPE)*len)) goto err;
UNLESS (keys=PyRealloc(self->keys, sizeof(KEY_TYPE)*len))
return -1;
UNLESS (values=PyRealloc(self->values, sizeof(KEY_TYPE)*len))
return -1;
self->keys=keys;
self->values=values;
self->size=len;
......@@ -870,24 +965,40 @@ bucket_setstate(Bucket *self, PyObject *args)
l++;
COPY_KEY_FROM_ARG(self->keys[i], k, &copied);
UNLESS (copied) return NULL;
UNLESS (copied) return -1;
COPY_VALUE_FROM_ARG(self->values[i], v, &copied);
UNLESS (copied) return NULL;
INCREF_KEY(k);
INCREF_VALUE(v);
UNLESS (copied) return -1;
INCREF_KEY(self->keys[i]);
INCREF_VALUE(self->values[i]);
}
self->len=len;
if (next)
{
self->next=next;
Py_INCREF(next);
}
self->len=l;
PER_ALLOW_DEACTIVATION(self);
return 0;
}
static PyObject *
bucket_setstate(Bucket *self, PyObject *args)
{
int r;
UNLESS (PyArg_ParseTuple(args, "O", &args)) return NULL;
PER_PREVENT_DEACTIVATION(self);
r=_bucket_setstate(self, args);
PER_ALLOW_DEACTIVATION(self);
if (r < 0) return NULL;
Py_INCREF(Py_None);
return Py_None;
perr:
self->len=i;
err:
PER_ALLOW_DEACTIVATION(self);
return NULL;
}
/*
......@@ -919,6 +1030,73 @@ bucket_getm(Bucket *self, PyObject *args)
return d;
}
#ifdef PERSISTENT
static PyObject *merge_error(int p1, int p2, int p3, int reason);
static PyObject *bucket_merge(Bucket *s1, Bucket *s2, Bucket *s3);
static PyObject *
_bucket__p_resolveConflict(PyObject *ob_type, PyObject *s[3])
{
PyObject *r=0, *a;
Bucket *b[3];
int i;
for (i=0; i < 3; i++)
{
if (b[i]=(Bucket*)PyObject_CallObject(OBJECT(ob_type), NULL))
{
if (s[i] == Py_None) /* None is equivalent to empty, for BTrees */
continue;
ASSIGN(r, PyObject_GetAttr(OBJECT(b[i]), __setstate___str));
if (a=PyTuple_New(1))
{
if (r)
{
PyTuple_SET_ITEM(a, 0, s[i]);
Py_INCREF(s[i]);
ASSIGN(r, PyObject_CallObject(r, a));
}
Py_DECREF(a);
if (r) continue;
}
}
Py_XDECREF(r);
while (--i >= 0)
{
Py_DECREF(b[i]);
}
return NULL;
}
Py_DECREF(r);
r=NULL;
if (b[0]->next != b[1]->next || b[0]->next != b[2]->next)
{
merge_error(-1, -1, -1, -1);
goto err;
}
r=bucket_merge(b[0], b[1], b[2]);
err:
Py_DECREF(b[0]);
Py_DECREF(b[1]);
Py_DECREF(b[2]);
return r;
}
static PyObject *
bucket__p_resolveConflict(Bucket *self, PyObject *args)
{
PyObject *s[3];
UNLESS(PyArg_ParseTuple(args, "OOO", &s[0], &s[1], &s[2])) return NULL;
return _bucket__p_resolveConflict(OBJECT(self->ob_type), s);
}
#endif
static struct PyMethodDef Bucket_methods[] = {
{"__getstate__", (PyCFunction) bucket_getstate, METH_VARARGS,
"__getstate__() -- Return the picklable state of the object"},
......@@ -930,6 +1108,10 @@ static struct PyMethodDef Bucket_methods[] = {
"has_key(key) -- Test whether the bucket contains the given key"},
{"clear", (PyCFunction) bucket_clear, METH_VARARGS,
"clear() -- Remove all of the items from the bucket"},
{"update", (PyCFunction) Mapping_update, METH_VARARGS,
"update(collection) -- Add the items from the given collection"},
{"__init__", (PyCFunction) Mapping_update, METH_VARARGS,
"__init__(collection) -- Initialize with items from the given collection"},
{"maxKey", (PyCFunction) Bucket_maxKey, METH_VARARGS,
"maxKey([key]) -- Fine the maximum key\n\n"
"If an argument is given, find the maximum <= the argument"},
......@@ -949,6 +1131,8 @@ static struct PyMethodDef Bucket_methods[] = {
"Return the default (or None) if the key is not found."
},
#ifdef PERSISTENT
{"_p_resolveConflict", (PyCFunction) bucket__p_resolveConflict, METH_VARARGS,
"_p_resolveConflict() -- Reinitialize from a newly created copy"},
{"_p_deactivate", (PyCFunction) bucket__p_deactivate, METH_VARARGS,
"_p_deactivate() -- Reinitialize from a newly created copy"},
#endif
......@@ -960,13 +1144,8 @@ Bucket_dealloc(Bucket *self)
{
int i;
for (i=self->len; --i >= 0; )
{
DECREF_KEY(self->keys[i]);
if (self->values) DECREF_VALUE(self->values[i]);
}
free(self->keys);
free(self->values);
_bucket_clear(self);
PER_DEL(self);
Py_DECREF(self->ob_type);
......
......@@ -2,7 +2,7 @@
#define PERSISTENT
#define PREFIX "IO"
#define DEFAULT_MAX_BUCKET_SIZE 30
#define DEFAULT_MAX_BUCKET_SIZE 60
#define DEFAULT_MAX_BTREE_SIZE 500
#define INITMODULE initIOBTree
......
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. 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.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED 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 ITS
# 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.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
import OOBTree, Interface
......@@ -74,6 +158,9 @@ class ISetMutable(IKeyed):
def remove(key):
"""Remove the key from the set."""
def update(seq):
"""Add the items from the given sequence to the set"""
class IKeySequence(IKeyed, ISized):
......@@ -89,6 +176,7 @@ class ISet(IKeySequence, ISetMutable):
class ITreeSet(IKeyed, ISetMutable):
pass
class IDictionaryIsh(IKeyed, ISized):
......@@ -116,6 +204,14 @@ class IDictionaryIsh(IKeyed, ISized):
Raise a key error if the key if not in the collection."""
def update(collection):
"""Add the items from the given collection object to the collection
The input collection must be a sequence of key-value tuples,
or an object with an 'items' method that returns a sequence of
key-value tuples.
"""
def values(min=None, max=None):
"""Return a IReadSequence containing the values in the collection
......
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. 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.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED 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 ITS
# 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.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
import Persistence
class Length(Persistence.Persistent):
"""BTree lengths aqre too expensive to compute
Objects that use BTrees need to keep track of lengths themselves.
This class provides an object for doing this.
As a bonus, the object support application-level conflict resolution.
"""
def __init__(self, v=0): self.value=v
def __getstate__(self): return self.value
def __setstate__(self, v): self.value=v
def set(self, v): self.value=v
def _p_resolveConflict(self, old, s1, s2): return s1 + s2 - old
def _p_independent(self):
# My state doesn't depend on or materially effect the state of
# other objects.
return 1
def change(self, delta): self.value = self.value + delta
def __call__(self, *args): return self.value
/*****************************************************************************
Zope Public License (ZPL) Version 1.0
-------------------------------------
Copyright (c) Digital Creations. All rights reserved.
This license has been certified as Open Source(tm).
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions in source code must retain the above copyright
notice, this list of conditions, and the following disclaimer.
2. 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.
3. Digital Creations requests that attribution be given to Zope
in any manner possible. Zope includes a "Powered by Zope"
button that is installed by default. While it is not a license
violation to remove this button, it is requested that the
attribution remain. A significant investment has been put
into Zope, and this effort will continue if the Zope community
continues to grow. This is one way to assure that growth.
4. All advertising materials and documentation mentioning
features derived from or use of this software must display
the following acknowledgement:
"This product includes software developed by Digital Creations
for use in the Z Object Publishing Environment
(http://www.zope.org/)."
In the event that the product being advertised includes an
intact Zope distribution (with copyright and license included)
then this clause is waived.
5. Names associated with Zope or Digital Creations must not be used to
endorse or promote products derived from this software without
prior written permission from Digital Creations.
6. Modified redistributions of any form whatsoever must retain
the following acknowledgment:
"This product includes software developed by Digital Creations
for use in the Z Object Publishing Environment
(http://www.zope.org/)."
Intact (re-)distributions of any official Zope release do not
require an external acknowledgement.
7. Modifications are encouraged but must be packaged separately as
patches to official Zope releases. Distributions that do not
clearly separate the patches from the original work must be clearly
labeled as unofficial distributions. Modifications which do not
carry the name Zope may be packaged in any form, as long as they
conform to all of the clauses above.
Disclaimer
THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
EXPRESSED 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 ITS
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.
This software consists of contributions made by Digital Creations and
many individuals on behalf of Digital Creations. Specific
attributions are listed in the accompanying credits file.
****************************************************************************/
/****************************************************************************
Set operations
****************************************************************************/
static int
merge_output(Bucket *r, SetIteration *i, int mapping)
{
if(r->len >= r->size && Bucket_grow(r, ! mapping) < 0) return -1;
COPY_KEY(r->keys[r->len], i->key);
INCREF_KEY(r->keys[r->len]);
if (mapping)
{
COPY_VALUE(r->values[r->len], i->value);
INCREF_VALUE(r->values[r->len]);
}
r->len++;
return 0;
}
static PyObject *
merge_error(int p1, int p2, int p3, int reason)
{
PyObject *r;
UNLESS (r=Py_BuildValue("iiii", p1, p2, p3, reason)) r=Py_None;
PyErr_SetObject(PyExc_ValueError, r);
if (r != Py_None) Py_DECREF(r);
return NULL;
}
static PyObject *
bucket_merge(Bucket *s1, Bucket *s2, Bucket *s3)
{
Bucket *r=0;
PyObject *s;
SetIteration i1 = {0,0,0}, i2 = {0,0,0}, i3 = {0,0,0}, it;
int cmp12, cmp13, cmp23, mapping=0, set;
if (initSetIteration(&i1, OBJECT(s1), 0, &mapping) < 0) return NULL;
if (initSetIteration(&i2, OBJECT(s2), 0, &mapping) < 0) return NULL;
if (initSetIteration(&i3, OBJECT(s3), 0, &mapping) < 0) return NULL;
set = ! mapping;
if (mapping)
{
UNLESS(r=BUCKET(PyObject_CallObject(OBJECT(&BucketType), NULL)))
goto err;
}
else
{
UNLESS(r=BUCKET(PyObject_CallObject(OBJECT(&SetType), NULL)))
goto err;
}
if (i1.next(&i1) < 0) return NULL;
if (i2.next(&i2) < 0) return NULL;
if (i3.next(&i3) < 0) return NULL;
while (i1.position >= 0 && i2.position >= 0 && i3.position >= 0)
{
cmp12=TEST_KEY(i1.key, i2.key);
cmp13=TEST_KEY(i1.key, i3.key);
if (cmp12==0)
{
if (cmp13==0)
{
if (set || (TEST_VALUE(i1.value, i2.value) == 0))
{ /* change in i3 or all same */
if (merge_output(r, &i3, mapping) < 0) goto err;
}
else if (set || (TEST_VALUE(i1.value, i3.value) == 0))
{ /* change in i2 */
if (merge_output(r, &i2, mapping) < 0) goto err;
}
else
{ /* conflicting changes in i2 and i3 */
merge_error(i1.position, i2.position, i3.position, 1);
goto err;
}
if (i1.next(&i1) < 0) goto err;
if (i2.next(&i2) < 0) goto err;
if (i3.next(&i3) < 0) goto err;
}
else if (cmp13 > 0)
{ /* insert i3 */
if (merge_output(r, &i3, mapping) < 0) goto err;
if (i3.next(&i3) < 0) goto err;
}
else if (set || (TEST_VALUE(i1.value, i2.value) == 0))
{ /* delete i3 */
if (i1.next(&i1) < 0) goto err;
if (i2.next(&i2) < 0) goto err;
}
else
{ /* conflicting del in i3 and change in i2 */
merge_error(i1.position, i2.position, i3.position, 2);
goto err;
}
}
else if (cmp13 == 0)
{
if (cmp12 > 0)
{ /* insert i2 */
if (merge_output(r, &i2, mapping) < 0) goto err;
if (i2.next(&i2) < 0) goto err;
}
else if (set || (TEST_VALUE(i1.value, i3.value) == 0))
{ /* delete i2 */
if (i1.next(&i1) < 0) goto err;
if (i3.next(&i3) < 0) goto err;
}
else
{ /* conflicting del in i2 and change in i3 */
merge_error(i1.position, i2.position, i3.position, 3);
goto err;
}
}
else
{ /* Both keys changed */
cmp23=TEST_KEY(i2.key, i3.key);
if (cmp23==0)
{ /* dualing inserts or deletes */
merge_error(i1.position, i2.position, i3.position, 4);
goto err;
}
if (cmp12 > 0)
{ /* insert i2 */
if (cmp23 > 0)
{ /* insert i3 first */
if (merge_output(r, &i3, mapping) < 0) goto err;
if (i3.next(&i3) < 0) goto err;
}
else
{ /* insert i2 first */
if (merge_output(r, &i2, mapping) < 0) goto err;
if (i2.next(&i2) < 0) goto err;
}
}
else if (cmp13 > 0)
{ /* Insert i3 */
if (merge_output(r, &i3, mapping) < 0) goto err;
if (i3.next(&i3) < 0) goto err;
}
else
{ /* Dueling deletes */
merge_error(i1.position, i2.position, i3.position, 5);
goto err;
}
}
}
while (i2.position >= 0 && i3.position >= 0)
{ /* New inserts */
cmp23=TEST_KEY(i2.key, i3.key);
if (cmp23==0)
{ /* dualing inserts */
merge_error(i1.position, i2.position, i3.position, 6);
goto err;
}
if (cmp23 > 0)
{ /* insert i3 */
if (merge_output(r, &i3, mapping) < 0) goto err;
if (i3.next(&i3) < 0) goto err;
}
else
{ /* insert i2 */
if (merge_output(r, &i2, mapping) < 0) goto err;
if (i2.next(&i2) < 0) goto err;
}
}
while (i1.position >= 0 && i2.position >= 0)
{ /* deleting i3 */
cmp12=TEST_KEY(i1.key, i2.key);
if (cmp12 > 0)
{ /* insert i2 */
if (merge_output(r, &i2, mapping) < 0) goto err;
if (i2.next(&i2) < 0) goto err;
}
else if (cmp12==0 && (set || (TEST_VALUE(i1.value, i2.value) == 0)))
{ /* delete i3 */
if (i1.next(&i1) < 0) goto err;
if (i2.next(&i2) < 0) goto err;
}
else
{ /* Dualing deletes or delete and change */
merge_error(i1.position, i2.position, i3.position, 7);
goto err;
}
}
while (i1.position >= 0 && i3.position >= 0)
{ /* deleting i2 */
cmp13=TEST_KEY(i1.key, i3.key);
if (cmp13 > 0)
{ /* insert i3 */
if (merge_output(r, &i3, mapping) < 0) goto err;
if (i3.next(&i3) < 0) goto err;
}
else if (cmp13==0 && (set || (TEST_VALUE(i1.value, i3.value) == 0)))
{ /* delete i2 */
if (i1.next(&i1) < 0) goto err;
if (i3.next(&i3) < 0) goto err;
}
else
{ /* Dualing deletes or delete and change */
merge_error(i1.position, i2.position, i3.position, 8);
goto err;
}
}
if (i1.position >= 0)
{ /* Dueling deletes */
merge_error(i1.position, i2.position, i3.position, 9);
goto err;
}
while (i2.position >= 0)
{ /* Inserting i2 at end */
if (merge_output(r, &i2, mapping) < 0) goto err;
if (i2.next(&i2) < 0) goto err;
}
while (i3.position >= 0)
{ /* Inserting i2 at end */
if (merge_output(r, &i3, mapping) < 0) goto err;
if (i3.next(&i3) < 0) goto err;
}
Py_DECREF(i1.set);
Py_DECREF(i2.set);
Py_DECREF(i3.set);
if (s1->next)
{
Py_INCREF(s1->next);
r->next = s1->next;
}
s=bucket_getstate(r, NULL);
Py_DECREF(r);
return s;
invalid_set_operation:
PyErr_SetString(PyExc_TypeError, "invalid set operation");
err:
Py_XDECREF(i1.set);
Py_XDECREF(i2.set);
Py_XDECREF(i3.set);
Py_XDECREF(r);
return NULL;
}
......@@ -110,6 +110,23 @@ nextIntSet(SetIteration *i)
}
#endif
#ifdef KEY_CHECK
static int
nextKeyAsSet(SetIteration *i)
{
if (i->position >= 0)
{
if (i->position < 1)
{
i->position ++;
}
else
i->position = -1;
}
return 0;
}
#endif
static int
initSetIteration(SetIteration *i, PyObject *s, int w, int *merge)
{
......@@ -169,6 +186,19 @@ initSetIteration(SetIteration *i, PyObject *s, int w, int *merge)
i->next=nextIntSet;
i->hasValue=0;
}
#endif
#ifdef KEY_CHECK
else if (KEY_CHECK(s))
{
int copied=1;
i->set = s;
Py_INCREF(s);
i->next=nextKeyAsSet;
i->hasValue=0;
COPY_KEY_FROM_ARG(i->key, s, &copied);
UNLESS (copied) return -1;
}
#endif
else
{
......@@ -300,7 +330,7 @@ set_operation(PyObject *s1, PyObject *s2,
{
if(c2)
{
if(r->len >= r->size && Bucket_grow(r,1) < 0) goto err;
if(r->len >= r->size && Bucket_grow(r, ! merge) < 0) goto err;
COPY_KEY(r->keys[r->len], i2.key);
INCREF_KEY(r->keys[r->len]);
if (merge)
......
......@@ -90,58 +90,59 @@ Set_insert(Bucket *self, PyObject *args)
int i;
UNLESS (PyArg_ParseTuple(args, "O", &key)) return NULL;
if ( (i=_bucket_set(self, key, Py_None, 1, 1)) < 0) return NULL;
if ( (i=_bucket_set(self, key, Py_None, 1, 1, 0)) < 0) return NULL;
return PyInt_FromLong(i);
}
static PyObject *
Set_remove(Bucket *self, PyObject *args)
{
PyObject *key;
UNLESS (PyArg_ParseTuple(args, "O", &key)) return NULL;
if (_bucket_set(self, key, NULL, 0, 1) < 0) return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
set_getstate(Bucket *self, PyObject *args)
Set_update(Bucket *self, PyObject *args)
{
PyObject *r=0, *o=0, *items=0;
int i, l;
PyObject *seq=0, *o, *t, *v, *tb;
int i, n=0, ind;
PER_USE_OR_RETURN(self, NULL);
l=self->len;
UNLESS(PyArg_ParseTuple(args, "|O:update", &seq)) return NULL;
UNLESS (items=PyTuple_New(self->len)) goto err;
for (i=0; i<l; i++)
if (seq)
{
COPY_KEY_TO_OBJECT(o, self->keys[i]);
UNLESS (o) goto err;
PyTuple_SET_ITEM(items, i, o);
for (i=0; ; i++)
{
UNLESS (o=PySequence_GetItem(seq, i))
{
PyErr_Fetch(&t, &v, &tb);
if (t != PyExc_IndexError)
{
PyErr_Restore(t, v, tb);
return NULL;
}
Py_XDECREF(t);
Py_XDECREF(v);
Py_XDECREF(tb);
break;
}
ind=_bucket_set(self, o, Py_None, 1, 1, 0);
Py_DECREF(o);
if (ind < 0) return NULL;
n += ind;
}
}
if (self->next)
r=Py_BuildValue("OO", items, self->next);
else
r=Py_BuildValue("(O)", items);
return PyInt_FromLong(n);
}
PER_ALLOW_DEACTIVATION(self);
static PyObject *
Set_remove(Bucket *self, PyObject *args)
{
PyObject *key;
return r;
UNLESS (PyArg_ParseTuple(args, "O", &key)) return NULL;
if (_bucket_set(self, key, NULL, 0, 1, 0) < 0) return NULL;
err:
PER_ALLOW_DEACTIVATION(self);
Py_XDECREF(items);
Py_XDECREF(r);
return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
set_setstate(Bucket *self, PyObject *args)
static int
_set_setstate(Bucket *self, PyObject *args)
{
PyObject *k, *items;
Bucket *next=0;
......@@ -149,14 +150,10 @@ set_setstate(Bucket *self, PyObject *args)
KEY_TYPE *keys;
VALUE_TYPE *values;
PER_PREVENT_DEACTIVATION(self);
UNLESS (PyArg_ParseTuple(args, "O|O", &items, &next))
return -1;
UNLESS (PyArg_ParseTuple(args, "O", &args)) goto err;
UNLESS (PyArg_ParseTuple(args, "O|O!", &items, self->ob_type, &next))
goto err;
if ((l=PyTuple_Size(items)) < 0) goto err;
if ((l=PyTuple_Size(items)) < 0) return -1;
for (i=self->len; --i >= 0; )
{
......@@ -172,7 +169,7 @@ set_setstate(Bucket *self, PyObject *args)
if (l > self->size)
{
UNLESS (keys=PyRealloc(self->keys, sizeof(KEY_TYPE)*l)) goto err;
UNLESS (keys=PyRealloc(self->keys, sizeof(KEY_TYPE)*l)) return -1;
self->keys=keys;
self->size=l;
}
......@@ -181,25 +178,39 @@ set_setstate(Bucket *self, PyObject *args)
{
k=PyTuple_GET_ITEM(items, i);
COPY_KEY_FROM_ARG(self->keys[i], k, &copied);
UNLESS (copied) return NULL;
INCREF_KEY(k);
UNLESS (copied) return -1;
INCREF_KEY(self->keys[i]);
}
self->len=l;
if (next)
{
self->next=next;
Py_INCREF(next);
}
return 0;
}
static PyObject *
set_setstate(Bucket *self, PyObject *args)
{
int r;
UNLESS (PyArg_ParseTuple(args, "O", &args)) return NULL;
PER_PREVENT_DEACTIVATION(self);
r=_set_setstate(self, args);
PER_ALLOW_DEACTIVATION(self);
if (r < 0) return NULL;
Py_INCREF(Py_None);
return Py_None;
perr:
self->len=i;
err:
PER_ALLOW_DEACTIVATION(self);
return NULL;
}
static struct PyMethodDef Set_methods[] = {
{"__getstate__", (PyCFunction) set_getstate, METH_VARARGS,
{"__getstate__", (PyCFunction) bucket_getstate, METH_VARARGS,
"__getstate__() -- Return the picklable state of the object"},
{"__setstate__", (PyCFunction) set_setstate, METH_VARARGS,
"__setstate__() -- Set the state of the object"},
......@@ -216,11 +227,17 @@ static struct PyMethodDef Set_methods[] = {
"minKey([key]) -- Fine the minimum key\n\n"
"If an argument is given, find the minimum >= the argument"},
#ifdef PERSISTENT
{"_p_resolveConflict", (PyCFunction) bucket__p_resolveConflict, METH_VARARGS,
"_p_resolveConflict() -- Reinitialize from a newly created copy"},
{"_p_deactivate", (PyCFunction) bucket__p_deactivate, METH_VARARGS,
"_p_deactivate() -- Reinitialize from a newly created copy"},
#endif
{"insert", (PyCFunction)Set_insert, METH_VARARGS,
"insert(id,[ignored]) -- Add a key to the set"},
{"update", (PyCFunction)Set_update, METH_VARARGS,
"update(seq) -- Add the items from the given sequence to the set"},
{"__init__", (PyCFunction)Set_update, METH_VARARGS,
"__init__(seq) -- Initialize with the items from the given sequence"},
{"remove", (PyCFunction)Set_remove, METH_VARARGS,
"remove(id) -- Remove an id from the set"},
......
......@@ -94,6 +94,42 @@ TreeSet_insert(BTree *self, PyObject *args)
return PyInt_FromLong(i);
}
static PyObject *
TreeSet_update(BTree *self, PyObject *args)
{
PyObject *seq=0, *o, *t, *v, *tb;
int i, n=0, ind;
UNLESS(PyArg_ParseTuple(args, "|O:update", &seq)) return NULL;
if (seq)
{
for (i=0; ; i++)
{
UNLESS (o=PySequence_GetItem(seq, i))
{
PyErr_Fetch(&t, &v, &tb);
if (t != PyExc_IndexError)
{
PyErr_Restore(t, v, tb);
return NULL;
}
Py_XDECREF(t);
Py_XDECREF(v);
Py_XDECREF(tb);
break;
}
ind=_BTree_set(self, o, Py_None, 1, 1);
Py_DECREF(o);
if (ind < 0) return NULL;
n += ind;
}
}
return PyInt_FromLong(n);
}
static PyObject *
TreeSet_remove(BTree *self, PyObject *args)
{
......@@ -105,10 +141,26 @@ TreeSet_remove(BTree *self, PyObject *args)
return Py_None;
}
static PyObject *
TreeSet_setstate(BTree *self, PyObject *args)
{
int r;
if (!PyArg_ParseTuple(args,"O",&args)) return NULL;
PER_PREVENT_DEACTIVATION(self);
r=_BTree_setstate(self, args, 1);
PER_ALLOW_DEACTIVATION(self);
if (r < 0) return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static struct PyMethodDef TreeSet_methods[] = {
{"__getstate__", (PyCFunction) BTree_getstate, METH_VARARGS,
"__getstate__() -- Return the picklable state of the object"},
{"__setstate__", (PyCFunction) BTree_setstate, METH_VARARGS,
{"__setstate__", (PyCFunction) TreeSet_setstate, METH_VARARGS,
"__setstate__() -- Set the state of the object"},
{"has_key", (PyCFunction) BTree_has_key, METH_VARARGS,
"has_key(key) -- Test whether the bucket contains the given key"},
......@@ -124,9 +176,15 @@ static struct PyMethodDef TreeSet_methods[] = {
"clear() -- Remove all of the items from the BTree"},
{"insert", (PyCFunction)TreeSet_insert, METH_VARARGS,
"insert(id,[ignored]) -- Add an id to the set"},
{"update", (PyCFunction)TreeSet_update, METH_VARARGS,
"update(seq) -- Add the items from the given sequence to the set"},
{"__init__", (PyCFunction)TreeSet_update, METH_VARARGS,
"__init__(seq) -- Initialize with the items from the given sequence"},
{"remove", (PyCFunction)TreeSet_remove, METH_VARARGS,
"remove(id) -- Remove a key from the set"},
#ifdef PERSISTENT
{"_p_resolveConflict", (PyCFunction) BTree__p_resolveConflict, METH_VARARGS,
"_p_resolveConflict() -- Reinitialize from a newly created copy"},
{"_p_deactivate", (PyCFunction) BTree__p_deactivate, METH_VARARGS,
"_p_deactivate() -- Reinitialize from a newly created copy"},
#endif
......
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. 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.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED 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 ITS
# 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.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
def convert(old, new, threshold=200, f=None, None=None):
"Utility for converting old btree new new"
n=0
for k, v in old.items():
if f is not None: v=f(v)
new[k]=v
n=n+1
if n > threshold:
get_transaction().commit(1)
old._p_jar.cacheMinimize(3)
n=0
get_transaction().commit(1)
old._p_jar.cacheMinimize(3)
#define KEY_TYPE int
#define TEST_KEY(KEY, TARGET) ( (KEY) - (TARGET) )
#define KEY_CHECK PyInt_Check
#define TEST_KEY(K, T) (((K) < (T)) ? -1 : (((K) > (T)) ? 1: 0))
#define DECREF_KEY(KEY)
#define INCREF_KEY(k)
#define COPY_KEY(KEY, E) (KEY=(E))
......
#define VALUE_TYPE int
#define TEST_VALUE(VALUE, TARGET) ( (VALUE) - (TARGET) )
#define TEST_VALUE(K, T) (((K) < (T)) ? -1 : (((K) > (T)) ? 1: 0))
#define VALUE_SAME(VALUE, TARGET) ( (VALUE) == (TARGET) )
#define DECLARE_VALUE(NAME) VALUE_TYPE NAME
#define VALUE_PARSE "i"
#define DECREF_VALUE(k)
......
# If tests is a package, debugging is a bit easier.
......@@ -122,56 +122,65 @@ class Base:
os.system('rm fs_tmp__*')
def testLoadAndStore(self):
t = self.t
try:
root = self._getRoot()
root['t'] = t
get_transaction().commit()
except:
self._closeDB(root)
self._delDB()
raise
self._closeDB(root)
try:
root = self._getRoot()
#XXX BTree stuff doesn't implement comparison
if hasattr(t, 'items'):
assert list(root['t'].items()) == list(t.items())
else:
assert list(root['t'].keys()) == list(t.keys())
finally:
for i in 0, 10, 1000:
t = self.t.__class__()
self._populate(t, i)
try:
root = self._getRoot()
root[i] = t
get_transaction().commit()
except:
self._closeDB(root)
self._delDB()
raise
self._closeDB(root)
self._delDB()
try:
root = self._getRoot()
#XXX BTree stuff doesn't implement comparison
if hasattr(t, 'items'):
assert list(root[i].items()) == list(t.items())
else:
assert list(root[i].keys()) == list(t.keys())
finally:
self._closeDB(root)
self._delDB()
def testGhostUnghost(self):
t = self.t
try:
root = self._getRoot()
root['t'] = t
get_transaction().commit()
except:
self._closeDB(root)
self._delDB()
raise
self._closeDB(root)
try:
root = self._getRoot()
root['t']._p_changed = None
get_transaction().commit()
if hasattr(t,'items'):
assert list(root['t'].items()) == list(t.items())
else:
assert list(root['t'].keys()) == list(t.keys())
finally:
for i in 0, 10, 1000:
t = self.t.__class__()
self._populate(t, i)
try:
root = self._getRoot()
root[i] = t
get_transaction().commit()
except:
self._closeDB(root)
self._delDB()
raise
self._closeDB(root)
self._delDB()
try:
root = self._getRoot()
root[i]._p_changed = None
get_transaction().commit()
if hasattr(t,'items'):
assert list(root[i].items()) == list(t.items())
else:
assert list(root[i].keys()) == list(t.keys())
finally:
self._closeDB(root)
self._delDB()
class MappingBase(Base):
""" Tests common to mappings (buckets, btrees) """
def _populate(self, t, l):
# Make some data
for i in range(l): t[i]=i
def testGetItemFails(self):
self.assertRaises(KeyError, self._getitemfail)
......@@ -271,8 +280,45 @@ class MappingBase(Base):
diff = lsubtract(list(self.t.keys()), [])
assert diff == [], diff
def testUpdate(self):
"mapping update"
d={}
l=[]
for i in range(10000):
k=whrandom.randint(-2000, 2000)
d[k]=i
l.append((k, i))
items=d.items()
items.sort()
self.t.update(d)
assert list(self.t.items()) == items
self.t.clear()
assert list(self.t.items()) == []
self.t.update(l)
assert list(self.t.items()) == items
def testEmptyRangeSearches(self):
t=self.t
t.update([(1,1),(5,5),(9,9)])
assert list(t.keys(-6,-4))==[], list(t.keys(-6,-4))
assert list(t.keys(2,4))==[], list(t.keys(2,4))
assert list(t.keys(6,8))==[], list(t.keys(6,8))
assert list(t.keys(10,12))==[], list(t.keys(10,12))
class NormalSetTests(Base):
""" Test common to all set types """
def _populate(self, t, l):
# Make some data
t.update(range(l))
def testInsertReturnsValue(self):
t = self.t
assert t.insert(5) == 1
......@@ -343,6 +389,29 @@ class NormalSetTests(Base):
assert t.minKey(3) == 3
assert t.minKey(9) == 10
def testUpdate(self):
"mapping update"
d={}
l=[]
for i in range(10000):
k=whrandom.randint(-2000, 2000)
d[k]=i
l.append(k)
items=d.keys()
items.sort()
self.t.update(l)
assert list(self.t.keys()) == items
def testEmptyRangeSearches(self):
t=self.t
t.update([1,5,9])
assert list(t.keys(-6,-4))==[], list(t.keys(-6,-4))
assert list(t.keys(2,4))==[], list(t.keys(2,4))
assert list(t.keys(6,8))==[], list(t.keys(6,8))
assert list(t.keys(10,12))==[], list(t.keys(10,12))
class ExtendedSetTests(NormalSetTests):
def testLen(self):
t = self.t
......
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. 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.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED 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 ITS
# 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.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
import sys, os, time, whrandom
try:
sys.path.insert(0, '.')
import ZODB
except:
sys.path.insert(0, '../..')
import ZODB
from BTrees.OOBTree import OOBTree, OOBucket, OOSet, OOTreeSet
from BTrees.IOBTree import IOBTree, IOBucket, IOSet, IOTreeSet
from BTrees.IIBTree import IIBTree, IIBucket, IISet, IITreeSet
from BTrees.OIBTree import OIBTree, OIBucket, OISet, OITreeSet
from unittest import TestCase, TestSuite, JUnitTextTestRunner, VerboseTextTestRunner, makeSuite
TextTestRunner = VerboseTextTestRunner
class Base:
""" Tests common to all types: sets, buckets, and BTrees """
def tearDown(self):
self.t = None
del self.t
def _getRoot(self):
from ZODB.FileStorage import FileStorage
from ZODB.DB import DB
n = 'fs_tmp__%s' % os.getpid()
s = FileStorage(n)
db = DB(s)
root = db.open().root()
return root
def _closeDB(self, root):
root._p_jar._db.close()
root = None
def _delDB(self):
os.system('rm fs_tmp__*')
class MappingBase(Base):
""" Tests common to mappings (buckets, btrees) """
def _deletefail(self):
del self.t[1]
def _setupConflict(self):
base=self.t
d={}
for i in range(20):
d[whrandom.randint(-10000, 10000)]=i*100000
e1={}
while len(e1) < 5:
k=whrandom.randint(-10000, 10000)
if not d.has_key(k):
e1[k]=len(e1)
e1=e1.items()
e2={}
while len(e2) < 5:
k=whrandom.randint(-10000, 10000)
if not d.has_key(k) and not e2.has_key(k):
e2[k]=len(e2)
e2=e2.items()
base.update(d)
b1=base.__class__(base)
b2=base.__class__(base)
bm=base.__class__(base)
items=base.items()
return base, b1, b2, bm, e1, e2, items
def testMergeDelete(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
del b1[items[0][0]]
del b2[items[5][0]]
del b1[items[-1][0]]
del b2[items[-2][0]]
del bm[items[0][0]]
del bm[items[5][0]]
del bm[items[-1][0]]
del bm[items[-2][0]]
test_merge(base, b1, b2, bm, 'merge delete')
def testMergeDeleteAndUpdate(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
del b1[items[0][0]]
b2[items[5][0]]=1
del b1[items[-1][0]]
b2[items[-2][0]]=2
del bm[items[0][0]]
bm[items[5][0]]=1
del bm[items[-1][0]]
bm[items[-2][0]]=2
test_merge(base, b1, b2, bm, 'merge update and delete')
def testMergeUpdate(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
b1[items[0][0]]=1
b2[items[5][0]]=2
b1[items[-1][0]]=3
b2[items[-2][0]]=4
bm[items[0][0]]=1
bm[items[5][0]]=2
bm[items[-1][0]]=3
bm[items[-2][0]]=4
test_merge(base, b1, b2, bm, 'merge update')
def testFailMergeDelete(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
del b1[items[0][0]]
del b2[items[0][0]]
test_merge(base, b1, b2, bm, 'merge conflicting delete',
should_fail=1)
def testFailMergeUpdate(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
b1[items[0][0]]=1
b2[items[0][0]]=2
test_merge(base, b1, b2, bm, 'merge conflicting update',
should_fail=1)
def testFailMergeDeleteAndUpdate(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
del b1[items[0][0]]
b2[items[0][0]]=-9
test_merge(base, b1, b2, bm, 'merge conflicting update and delete',
should_fail=1)
def testMergeInserts(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
b1[-99999]=-99999
b1[e1[0][0]]=e1[0][1]
b2[99999]=99999
b2[e1[2][0]]=e1[2][1]
bm[-99999]=-99999
bm[e1[0][0]]=e1[0][1]
bm[99999]=99999
bm[e1[2][0]]=e1[2][1]
test_merge(base, b1, b2, bm, 'merge insert')
def testMergeInsertsFromEmpty(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
base.clear()
b1.clear()
b2.clear()
bm.clear()
b1.update(e1)
bm.update(e1)
b2.update(e2)
bm.update(e2)
test_merge(base, b1, b2, bm, 'merge insert from empty')
def testMergeEmptyAndFill(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
b1.clear()
bm.clear()
b2.update(e2)
bm.update(e2)
test_merge(base, b1, b2, bm, 'merge insert from empty')
def testMergeEmpty(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
b1.clear()
bm.clear()
test_merge(base, b1, b2, bm, 'empty one and not other')
def testFailMergeInsert(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
b1[-99999]=-99999
b1[e1[0][0]]=e1[0][1]
b2[99999]=99999
b2[e1[0][0]]=e1[0][1]
test_merge(base, b1, b2, bm, 'merge conflicting inserts',
should_fail=1)
class NormalSetTests(Base):
""" Test common to all set types """
class ExtendedSetTests(NormalSetTests):
"Set (as opposed to TreeSet) specific tests."
def _setupConflict(self):
base=self.t
d={}
for i in range(20):
d[whrandom.randint(-10000, 10000)]=i*100000
e1={}
while len(e1) < 5:
k=whrandom.randint(-10000, 10000)
if not d.has_key(k):
e1[k]=len(e1)
e1=e1.keys()
e2={}
while len(e2) < 5:
k=whrandom.randint(-10000, 10000)
if not d.has_key(k) and not e2.has_key(k):
e2[k]=len(e2)
e2=e2.keys()
base.update(d.keys())
b1=base.__class__(base)
b2=base.__class__(base)
bm=base.__class__(base)
items=base.keys()
return base, b1, b2, bm, e1, e2, items
def testMergeDelete(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
b1.remove(items[0])
b2.remove(items[5])
b1.remove(items[-1])
b2.remove(items[-2])
bm.remove(items[0])
bm.remove(items[5])
bm.remove(items[-1])
bm.remove(items[-2])
test_merge(base, b1, b2, bm, 'merge delete')
def testFailMergeDelete(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
b1.remove(items[0])
b2.remove(items[0])
test_merge(base, b1, b2, bm, 'merge conflicting delete',
should_fail=1)
def testMergeInserts(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
b1.insert(-99999)
b1.insert(e1[0])
b2.insert(99999)
b2.insert(e1[2])
bm.insert(-99999)
bm.insert(e1[0])
bm.insert(99999)
bm.insert(e1[2])
test_merge(base, b1, b2, bm, 'merge insert')
def testMergeInsertsFromEmpty(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
base.clear()
b1.clear()
b2.clear()
bm.clear()
b1.update(e1)
bm.update(e1)
b2.update(e2)
bm.update(e2)
test_merge(base, b1, b2, bm, 'merge insert from empty')
def testMergeEmptyAndFill(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
b1.clear()
bm.clear()
b2.update(e2)
bm.update(e2)
test_merge(base, b1, b2, bm, 'merge insert from empty')
def testMergeEmpty(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
b1.clear()
bm.clear()
test_merge(base, b1, b2, bm, 'empty one and not other')
def testFailMergeInsert(self):
base, b1, b2, bm, e1, e2, items = self._setupConflict()
b1.insert(-99999)
b1.insert(e1[0])
b2.insert(99999)
b2.insert(e1[0])
test_merge(base, b1, b2, bm, 'merge conflicting inserts',
should_fail=1)
def test_merge(o1, o2, o3, expect, message='failed to merge', should_fail=0):
s1=o1.__getstate__()
s2=o2.__getstate__()
s3=o3.__getstate__()
expected=expect.__getstate__()
if expected is None: expected=((((),),),)
if should_fail:
try: merged=o1._p_resolveConflict(s1, s2, s3)
except: pass # cool
else: assert 0, message
else:
merged=o1._p_resolveConflict(s1, s2, s3)
assert merged==expected, message
class BucketTests(MappingBase):
""" Tests common to all buckets """
class BTreeTests(MappingBase):
""" Tests common to all BTrees """
## BTree tests
class TestIOBTrees(BTreeTests, TestCase):
def setUp(self):
self.t = IOBTree()
class TestOOBTrees(BTreeTests, TestCase):
def setUp(self):
self.t = OOBTree()
class TestOIBTrees(BTreeTests, TestCase):
def setUp(self):
self.t = OIBTree()
class TestIIBTrees(BTreeTests, TestCase):
def setUp(self):
self.t = IIBTree()
## Set tests
class TestIOSets(ExtendedSetTests, TestCase):
def setUp(self):
self.t = IOSet()
class TestOOSets(ExtendedSetTests, TestCase):
def setUp(self):
self.t = OOSet()
class TestIISets(ExtendedSetTests, TestCase):
def setUp(self):
self.t = IISet()
class TestOISets(ExtendedSetTests, TestCase):
def setUp(self):
self.t = OISet()
class TestIOTreeSets(NormalSetTests, TestCase):
def setUp(self):
self.t = IOTreeSet()
class TestOOTreeSets(NormalSetTests, TestCase):
def setUp(self):
self.t = OOTreeSet()
class TestIITreeSets(NormalSetTests, TestCase):
def setUp(self):
self.t = IITreeSet()
class TestOITreeSets(NormalSetTests, TestCase):
def setUp(self):
self.t = OITreeSet()
## Bucket tests
class TestIOBuckets(BucketTests, TestCase):
def setUp(self):
self.t = IOBucket()
class TestOOBuckets(BucketTests, TestCase):
def setUp(self):
self.t = OOBucket()
class TestIIBuckets(BucketTests, TestCase):
def setUp(self):
self.t = IIBucket()
class TestOIBuckets(BucketTests, TestCase):
def setUp(self):
self.t = OIBucket()
def test_suite():
TIOBTree = makeSuite(TestIOBTrees, 'test')
TOOBTree = makeSuite(TestOOBTrees, 'test')
TOIBTree = makeSuite(TestOIBTrees, 'test')
TIIBTree = makeSuite(TestIIBTrees, 'test')
TIOSet = makeSuite(TestIOSets, 'test')
TOOSet = makeSuite(TestOOSets, 'test')
TOISet = makeSuite(TestIOSets, 'test')
TIISet = makeSuite(TestOOSets, 'test')
TIOTreeSet = makeSuite(TestIOTreeSets, 'test')
TOOTreeSet = makeSuite(TestOOTreeSets, 'test')
TOITreeSet = makeSuite(TestIOTreeSets, 'test')
TIITreeSet = makeSuite(TestOOTreeSets, 'test')
TIOBucket = makeSuite(TestIOBuckets, 'test')
TOOBucket = makeSuite(TestOOBuckets, 'test')
TOIBucket = makeSuite(TestOIBuckets, 'test')
TIIBucket = makeSuite(TestIIBuckets, 'test')
alltests = TestSuite((TIOSet, TOOSet, TOISet, TIISet,
TIOTreeSet, TOOTreeSet, TOITreeSet, TIITreeSet,
TIOBucket, TOOBucket, TOIBucket, TIIBucket,
TOOBTree, TIOBTree, TOIBTree, TIIBTree))
return alltests
def main():
alltests=test_suite()
runner = TextTestRunner()
runner.run(alltests)
def debug():
test_suite().debug()
def pdebug():
import pdb
pdb.run('debug()')
## utility functions
def lsubtract(l1, l2):
l1=list(l1)
l2=list(l2)
l = filter(lambda x, l1=l1: x not in l1, l2)
l = l + filter(lambda x, l2=l2: x not in l2, l1)
return l
def realseq(itemsob):
return map(lambda x: x, itemsob)
if __name__=='__main__':
if len(sys.argv) > 1:
globals()[sys.argv[1]]()
else:
main()
......@@ -84,9 +84,10 @@
##############################################################################
"""Handy standard storage machinery
"""
__version__='$Revision: 1.10 $'[11:-2]
__version__='$Revision: 1.11 $'[11:-2]
import time, bpthread, UndoLogCompatible
import ThreadLock, bpthread
import time, UndoLogCompatible
from POSException import UndoError
from TimeStamp import TimeStamp
z64='\0'*8
......@@ -101,7 +102,7 @@ class BaseStorage(UndoLogCompatible.UndoLogCompatible):
self.__name__=name
# Allocate locks:
l=bpthread.allocate_lock()
l=ThreadLock.allocate_lock()
self._lock_acquire=l.acquire
self._lock_release=l.release
l=bpthread.allocate_lock()
......
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. 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.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED 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 ITS
# 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.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
from cStringIO import StringIO
from cPickle import Unpickler, Pickler
import sys
#import traceback
bad_classes={}
bad_class=bad_classes.has_key
ResolvedSerial='rs'
def _classFactory(location, name,
_silly=('__doc__',), _globals={}):
return getattr(__import__(location, _globals, _globals, _silly),
name)
def state(self, oid, serial, prfactory):
p=self.loadSerial(oid, serial)
file=StringIO(p)
unpickler=Unpickler(file)
unpickler.persistent_load=prfactory
class_tuple=unpickler.load()
state=unpickler.load()
return state
class PersistentReference:
def __repr__(self):
return "PR(%s %s)" % (id(self), self.data)
def __getstate__(self):
raise "Can't pickle PersistentReference"
class PersistentReferenceFactory:
data=None
def __call__(self, oid,
getattr=getattr, None=None):
data=self.data
if not data: data=self.data={}
r=data.get(oid, None)
if r is None:
r=PersistentReference()
r.data=oid
data[oid]=r
return r
def persistent_id(object,
PersistentReference=PersistentReference,
getattr=getattr
):
if getattr(object, '__class__', 0) is not PersistentReference:
return None
return object.data
class ConflictResolvingStorage:
"Mix-in class that provides conflict resolution handling for storages"
def tryToResolveConflict(self, oid, committedSerial, oldSerial, newpickle):
#class_tuple, old, committed, newstate = ('',''), 0, 0, 0
try:
file=StringIO(newpickle)
unpickler=Unpickler(file)
prfactory=PersistentReferenceFactory()
unpickler.persistent_load=prfactory
class_tuple=unpickler.load()[0]
if bad_class(class_tuple):
#sys.stderr.write(' b%s ' % class_tuple[1]); sys.stderr.flush()
return 0
newstate=unpickler.load()
klass=_classFactory(class_tuple[0], class_tuple[1])
klass._p_resolveConflict
inst=klass.__basicnew__()
try:
resolve=inst._p_resolveConflict
except AttributeError:
bad_classes[class_tuple]=1
#traceback.print_exc()
#sys.stderr.write(' b%s ' % class_tuple[1]); sys.stderr.flush()
return 0
old=state(self, oid, oldSerial, prfactory)
committed=state(self, oid, committedSerial, prfactory)
resolved=resolve(old, committed, newstate)
file=StringIO()
pickler=Pickler(file,1)
pickler.persistent_id=persistent_id
pickler.dump(class_tuple)
pickler.dump(resolved)
#sys.stderr.write(' r%s ' % class_tuple[1]); sys.stderr.flush()
return file.getvalue(1)
except Exception, v:
#print '='*70
#print v, v.args
#print '='*70
#print old
#print '='*70
#print committed
#print '='*70
#print newstate
#print '='*70
#traceback.print_exc()
#sys.stderr.write(' c%s ' % class_tuple[1]); sys.stderr.flush()
return 0
......@@ -84,8 +84,8 @@
##############################################################################
"""Database connection support
$Id: Connection.py,v 1.45 2001/02/09 14:11:49 jim Exp $"""
__version__='$Revision: 1.45 $'[11:-2]
$Id: Connection.py,v 1.46 2001/03/15 13:16:26 jim Exp $"""
__version__='$Revision: 1.46 $'[11:-2]
from cPickleCache import PickleCache
from POSException import ConflictError, ExportError
......@@ -94,8 +94,9 @@ from cPickle import Unpickler, Pickler
from ExtensionClass import Base
from time import time
import Transaction, string, ExportImport, sys, traceback, TmpStore
from zLOG import LOG, ERROR
from zLOG import LOG, ERROR, BLATHER
from coptimizations import new_persistent_id
from ConflictResolution import ResolvedSerial
ExtensionKlass=Base.__class__
......@@ -230,7 +231,10 @@ class Connection(ExportImport.ExportImport):
This just deactivates the thing.
"""
self._cache.invalidate(object._p_oid)
if object is self:
self._cache.invalidate(self._invalidated)
else:
self._cache.invalidate(object._p_oid)
def cacheFullSweep(self, dt=0): self._cache.full_sweep(dt)
def cacheMinimize(self, dt=0): self._cache.minimize(dt)
......@@ -257,6 +261,8 @@ class Connection(ExportImport.ExportImport):
db._closeConnection(self)
def commit(self, object, transaction, _type=type, _st=type('')):
if object is self:
return # we registered ourself
oid=object._p_oid
invalid=self._invalid
if oid is None or object._p_jar is not self:
......@@ -267,7 +273,12 @@ class Connection(ExportImport.ExportImport):
self._creating.append(oid)
elif object._p_changed:
if invalid(oid) or invalid(None): raise ConflictError, `oid`
if (
(invalid(oid) and not hasattr(object, '_p_resolveConflict'))
or
invalid(None)
):
raise ConflictError, `oid`
self._invalidating.append(oid)
else:
......@@ -328,7 +339,13 @@ class Connection(ExportImport.ExportImport):
self._creating.append(oid)
else:
#XXX We should never get here
if invalid(oid) or invalid(None): raise ConflictError, `oid`
if (
(invalid(oid) and
not hasattr(object, '_p_resolveConflict'))
or
invalid(None)
):
raise ConflictError, `oid`
self._invalidating.append(oid)
klass = object.__class__
......@@ -362,8 +379,12 @@ class Connection(ExportImport.ExportImport):
# Note that if s is false, then the storage defered the return
if _type(s) is _st:
# normal case
object._p_serial=s
object._p_changed=0
if s is ResolvedSerial:
# resolved conflict
object._p_changed=None
else:
object._p_serial=s
object._p_changed=0
else:
# defered returns
for oi, s in s:
......@@ -389,6 +410,10 @@ class Connection(ExportImport.ExportImport):
tmp=self._tmp
if tmp is _None: return
src=self._storage
LOG('ZODB', BLATHER,
'Commiting subtransaction of size %s' % src.getSize())
self._storage=tmp
self._tmp=_None
......@@ -487,7 +512,13 @@ class Connection(ExportImport.ExportImport):
# notifications between the time we check and the time we
# read.
invalid=self._invalid
if invalid(oid) or invalid(None): raise ConflictError, `oid`
if invalid(oid) or invalid(None):
if not hasattr(object.__class__, '_p_independent'):
get_transaction().register(self)
raise ConflictError(`oid`, `object.__class__`)
invalid=1
else:
invalid=0
file=StringIO(p)
unpickler=Unpickler(file)
......@@ -503,6 +534,14 @@ class Connection(ExportImport.ExportImport):
object._p_serial=serial
if invalid:
if object._p_independent():
try: del self._invalidated[oid]
except KeyError: pass
else:
get_transaction().register(self)
raise ConflictError(`oid`, `object.__class__`)
except:
t, v =sys.exc_info()[:2]
LOG('ZODB',ERROR, "Couldn't load state for %s" % `oid`,
......
......@@ -184,7 +184,7 @@
# may have a back pointer to a version record or to a non-version
# record.
#
__version__='$Revision: 1.50 $'[11:-2]
__version__='$Revision: 1.51 $'[11:-2]
import struct, time, os, bpthread, string, base64, sys
from struct import pack, unpack
......@@ -197,6 +197,7 @@ from zLOG import LOG, WARNING, ERROR, PANIC, register_subsystem
register_subsystem('ZODB FS')
import BaseStorage
from cPickle import Pickler, Unpickler
import ConflictResolution
try: from posix import fsync
except: fsync=None
......@@ -240,7 +241,8 @@ class FileStorageQuotaError(FileStorageError,
packed_version='FS21'
class FileStorage(BaseStorage.BaseStorage):
class FileStorage(BaseStorage.BaseStorage,
ConflictResolution.ConflictResolvingStorage):
_packt=z64
def __init__(self, file_name, create=0, read_only=0, stop=None,
......@@ -663,18 +665,23 @@ class FileStorage(BaseStorage.BaseStorage):
raise POSException.VersionLockError, (
`oid`, locked_version)
if serial != oserial: raise POSException.ConflictError, (
serial, oserial)
if serial != oserial:
data=self.tryToResolveConflict(oid, oserial, serial, data)
if not data:
raise POSException.ConflictError, (
serial, oserial)
else:
oserial=serial
tfile=self._tfile
write=tfile.write
pos=self._pos
here=pos+(tfile.tell()+self._thl)
self._tappend((oid, here))
serial=self._serial
newserial=self._serial
write(pack(">8s8s8s8sH8s",
oid,serial,p64(old),p64(pos),
len(version),p64(len(data))
oid, newserial, p64(old), p64(pos),
len(version), p64(len(data))
)
)
if version:
......@@ -695,7 +702,8 @@ class FileStorage(BaseStorage.BaseStorage):
raise FileStorageQuotaError, (
'The storage quota has been exceeded.')
return serial
return (serial == oserial and newserial
or ConflictResolution.ResolvedSerial)
finally:
self._lock_release()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment