Commit 269dcdab authored by Jim Fulton's avatar Jim Fulton

Allow None as a special key (sorted smaller than all others).

parent eb8a9f1f
...@@ -113,7 +113,7 @@ class _BucketBase(_Base): ...@@ -113,7 +113,7 @@ class _BucketBase(_Base):
k = keys[i] k = keys[i]
if k is key or k == key: if k is key or k == key:
return i return i
if k < key: if k is None or (key is not None and k < key):
low = i + 1 low = i + 1
else: else:
high = i high = i
...@@ -256,7 +256,7 @@ _object_lt = getattr(object, '__lt__', _marker) ...@@ -256,7 +256,7 @@ _object_lt = getattr(object, '__lt__', _marker)
def _no_default_comparison(key): def _no_default_comparison(key):
# Enforce test that key has non-default comparison. # Enforce test that key has non-default comparison.
if key is None: if key is None:
raise TypeError("Can't use None as a key") return
if type(key) is object: if type(key) is object:
raise TypeError("Can't use object() as keys") raise TypeError("Can't use object() as keys")
lt = getattr(key, '__lt__', None) lt = getattr(key, '__lt__', None)
...@@ -919,7 +919,7 @@ class _Tree(_Base): ...@@ -919,7 +919,7 @@ class _Tree(_Base):
max = self._to_key(max) max = self._to_key(max)
index = self._search(max) index = self._search(max)
if index and data[index].child.minKey() > max: if index and cmp(data[index].child.minKey(), max) > 0:
index -= 1 #pragma: no cover no idea how to provoke this index -= 1 #pragma: no cover no idea how to provoke this
return data[index].child.maxKey(max) return data[index].child.maxKey(max)
...@@ -1014,7 +1014,7 @@ class _Tree(_Base): ...@@ -1014,7 +1014,7 @@ class _Tree(_Base):
self._p_changed = True self._p_changed = True
# fix up the node key, but not for the 0'th one. # fix up the node key, but not for the 0'th one.
if index > 0 and child.size and key == data[index].key: if index > 0 and child.size and cmp(key, data[index].key) == 0:
self._p_changed = True self._p_changed = True
data[index].key = child.minKey() data[index].key = child.minKey()
...@@ -1269,7 +1269,7 @@ class Tree(_Tree): ...@@ -1269,7 +1269,7 @@ class Tree(_Tree):
def byValue(self, min): def byValue(self, min):
return reversed( return reversed(
sorted((v, k) for (k, v) in self.iteritems() if v >= min)) sorted(((v, k) for (k, v) in self.iteritems() if v >= min)))
def insert(self, key, value): def insert(self, key, value):
return bool(self._set(key, value, True)[0]) return bool(self._set(key, value, True)[0])
......
...@@ -33,8 +33,9 @@ ...@@ -33,8 +33,9 @@
-1 -> There was an error, which the caller will detect with PyError_Occurred. -1 -> There was an error, which the caller will detect with PyError_Occurred.
*/ */
#define COMPARE(lhs, rhs) \ #define COMPARE(lhs, rhs) \
PyObject_RichCompareBool((lhs), (rhs), Py_LT) != 0 ? -1 : \ (lhs == Py_None ? (rhs == Py_None ? 0 : -1) : (rhs == Py_None ? 1 : \
(PyObject_RichCompareBool((lhs), (rhs), Py_EQ) > 0 ? 0 : 1) (PyObject_RichCompareBool((lhs), (rhs), Py_LT) != 0 ? -1 : \
(PyObject_RichCompareBool((lhs), (rhs), Py_EQ) > 0 ? 0 : 1))))
#else #else
...@@ -45,7 +46,9 @@ ...@@ -45,7 +46,9 @@
#define TEXT_FROM_STRING PyString_FromString #define TEXT_FROM_STRING PyString_FromString
#define TEXT_FORMAT PyString_Format #define TEXT_FORMAT PyString_Format
#define COMPARE(lhs, rhs) PyObject_Compare((lhs), (rhs)) #define COMPARE(lhs, rhs) \
(lhs == Py_None ? (rhs == Py_None ? 0 : -1) : (rhs == Py_None ? 1 : \
PyObject_Compare((lhs), (rhs))))
#endif #endif
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
############################################################################## ##############################################################################
import sys import sys
None_ = None
if sys.version_info[0] < 3: #pragma NO COVER Python2 if sys.version_info[0] < 3: #pragma NO COVER Python2
PY2 = True PY2 = True
...@@ -23,7 +24,17 @@ if sys.version_info[0] < 3: #pragma NO COVER Python2 ...@@ -23,7 +24,17 @@ if sys.version_info[0] < 3: #pragma NO COVER Python2
int_types = int, long int_types = int, long
xrange = xrange xrange = xrange
cmp = cmp cmp_ = cmp
def cmp(x, y):
if x is None_:
if y is None_:
return 0
else:
return -1
elif y is None_:
return 1
else:
return cmp_(x, y)
_bytes = str _bytes = str
def _ascii(x): def _ascii(x):
...@@ -44,6 +55,14 @@ else: #pragma NO COVER Python3 ...@@ -44,6 +55,14 @@ else: #pragma NO COVER Python3
xrange = range xrange = range
def cmp(x, y): def cmp(x, y):
if x is None_:
if y is None_:
return 0
else:
return -1
elif y is None_:
return 1
else:
return (x > y) - (y > x) return (x > y) - (y > x)
_bytes = bytes _bytes = bytes
......
...@@ -56,6 +56,8 @@ from BTrees.utils import oid_repr ...@@ -56,6 +56,8 @@ from BTrees.utils import oid_repr
TYPE_UNKNOWN, TYPE_BTREE, TYPE_BUCKET = range(3) TYPE_UNKNOWN, TYPE_BTREE, TYPE_BUCKET = range(3)
from ._compat import cmp
_type2kind = {} _type2kind = {}
for kv in ('OO', for kv in ('OO',
'II', 'IO', 'OI', 'IF', 'II', 'IO', 'OI', 'IF',
...@@ -346,13 +348,13 @@ class Checker(Walker): ...@@ -346,13 +348,13 @@ class Checker(Walker):
def check_sorted(self, obj, path, keys, lo, hi): def check_sorted(self, obj, path, keys, lo, hi):
i, n = 0, len(keys) i, n = 0, len(keys)
for x in keys: for x in keys:
if lo is not None and not lo <= x: if lo is not None and not cmp(lo, x) <= 0:
s = "key %r < lower bound %r at index %d" % (x, lo, i) s = "key %r < lower bound %r at index %d" % (x, lo, i)
self.complain(s, obj, path) self.complain(s, obj, path)
if hi is not None and not x < hi: if hi is not None and not cmp(x, hi) < 0:
s = "key %r >= upper bound %r at index %d" % (x, hi, i) s = "key %r >= upper bound %r at index %d" % (x, hi, i)
self.complain(s, obj, path) self.complain(s, obj, path)
if i < n-1 and not x < keys[i+1]: if i < n-1 and not cmp(x, keys[i+1]) < 0:
s = "key %r at index %d >= key %r at index %d" % ( s = "key %r at index %d >= key %r at index %d" % (
x, i, keys[i+1], i+1) x, i, keys[i+1], i+1)
self.complain(s, obj, path) self.complain(s, obj, path)
......
...@@ -15,6 +15,9 @@ check_argument_cmp(PyObject *arg) ...@@ -15,6 +15,9 @@ check_argument_cmp(PyObject *arg)
/* ((PyTypeObject *)object_)->ob_type->tp_richcompare, */ /* ((PyTypeObject *)object_)->ob_type->tp_richcompare, */
/* arg->ob_type->tp_compare, */ /* arg->ob_type->tp_compare, */
/* ((PyTypeObject *)object_)->ob_type->tp_compare); */ /* ((PyTypeObject *)object_)->ob_type->tp_compare); */
if (arg == Py_None) {
return 1;
}
#ifdef PY3K #ifdef PY3K
if (Py_TYPE(arg)->tp_richcompare == Py_TYPE(object_)->tp_richcompare) if (Py_TYPE(arg)->tp_richcompare == Py_TYPE(object_)->tp_richcompare)
......
...@@ -161,6 +161,32 @@ class OOBTreeTest(BTreeTests, unittest.TestCase): ...@@ -161,6 +161,32 @@ class OOBTreeTest(BTreeTests, unittest.TestCase):
self.assertRaises(KeyError, t.__getitem__, C()) self.assertRaises(KeyError, t.__getitem__, C())
self.assertFalse(C() in t) self.assertFalse(C() in t)
def test_None_is_smallest(self):
t = self._makeOne()
for i in range(999): # Make sure we multiple buckets
t[i] = i*i
t[None] = -1
for i in range(-99,0): # Make sure we multiple buckets
t[i] = i*i
self.assertEqual(list(t), [None] + list(range(-99, 999)))
self.assertEqual(list(t.values()),
[-1] + [i*i for i in range(-99, 999)])
self.assertEqual(t[2], 4)
self.assertEqual(t[-2], 4)
self.assertEqual(t[None], -1)
t[None] = -2
self.assertEqual(t[None], -2)
t2 = t.__class__(t)
del t[None]
self.assertEqual(list(t), list(range(-99, 999)))
if 'Py' in self.__class__.__name__:
return
from BTrees.OOBTree import difference, union, intersection
self.assertEqual(list(difference(t2, t).items()), [(None, -2)])
self.assertEqual(list(union(t, t2)), list(t2))
self.assertEqual(list(intersection(t, t2)), list(t))
@_skip_under_Py3k @_skip_under_Py3k
def testDeleteNoneKey(self): def testDeleteNoneKey(self):
# Check that a None key can be deleted in Python 2. # Check that a None key can be deleted in Python 2.
......
``BTrees`` Changelog ``BTrees`` Changelog
==================== ====================
- Allow None as a special key (sorted smaller than all others).
4.3.2 (2017-01-05) 4.3.2 (2017-01-05)
------------------ ------------------
......
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