mirror of
https://github.com/apple/foundationdb.git
synced 2025-05-14 09:58:50 +08:00
added versionstamp type to python tuple layer and updated bindingtester to test it
This commit is contained in:
parent
4b21da1cd6
commit
bd6dabacdb
@ -171,6 +171,8 @@ class TestRunner(object):
|
||||
if self.args.no_threads and self.args.concurrency > 1:
|
||||
raise Exception('Not all testers support concurrency')
|
||||
|
||||
# Test types should be intersection of all tester supported types
|
||||
self.args.types = reduce(lambda t1, t2: filter(t1.__contains__, t2), map(lambda tester: tester.types, self.testers))
|
||||
|
||||
def print_test(self):
|
||||
test_instructions = self._generate_test()
|
||||
|
@ -21,15 +21,18 @@
|
||||
import os
|
||||
|
||||
MAX_API_VERSION = 500
|
||||
COMMON_TYPES = [ 'null', 'bytes', 'string', 'int', 'uuid', 'bool', 'float', 'double', 'tuple' ]
|
||||
ALL_TYPES = COMMON_TYPES + [ 'versionstamp' ]
|
||||
|
||||
class Tester:
|
||||
def __init__(self, name, cmd, max_int_bits=64, min_api_version=0, max_api_version=MAX_API_VERSION, threads_enabled=True):
|
||||
def __init__(self, name, cmd, max_int_bits=64, min_api_version=0, max_api_version=MAX_API_VERSION, threads_enabled=True, types=COMMON_TYPES):
|
||||
self.name = name
|
||||
self.cmd = cmd
|
||||
self.max_int_bits = max_int_bits
|
||||
self.min_api_version = min_api_version
|
||||
self.max_api_version = max_api_version
|
||||
self.threads_enabled = threads_enabled
|
||||
self.types = types
|
||||
|
||||
def supports_api_version(self, api_version):
|
||||
return api_version >= self.min_api_version and api_version <= self.max_api_version
|
||||
@ -54,8 +57,8 @@ _java_completable_cmd = 'java -ea -cp %s:%s com.apple.cie.foundationdb.test.' %
|
||||
|
||||
# We could set min_api_version lower on some of these if the testers were updated to support them
|
||||
testers = {
|
||||
'python' : Tester('python', 'python ' + _absolute_path('python/tests/tester.py'), 2040, 23, MAX_API_VERSION),
|
||||
'python3' : Tester('python3', 'python3 ' + _absolute_path('python/tests/tester.py'), 2040, 23, MAX_API_VERSION),
|
||||
'python' : Tester('python', 'python ' + _absolute_path('python/tests/tester.py'), 2040, 23, MAX_API_VERSION, types=ALL_TYPES),
|
||||
'python3' : Tester('python3', 'python3 ' + _absolute_path('python/tests/tester.py'), 2040, 23, MAX_API_VERSION, types=ALL_TYPES),
|
||||
'node' : Tester('node', _absolute_path('nodejs/tests/tester.js'), 53, 500, MAX_API_VERSION),
|
||||
'streamline' : Tester('streamline', _absolute_path('nodejs/tests/streamline_tester._js'), 53, 500, MAX_API_VERSION),
|
||||
'ruby' : Tester('ruby', _absolute_path('ruby/tests/tester.rb'), 64, 23, MAX_API_VERSION),
|
||||
|
@ -293,6 +293,20 @@ TUPLE_PACK
|
||||
stack and packs them as the tuple [item0,item1,...,itemN], and then pushes
|
||||
this single packed value onto the stack.
|
||||
|
||||
TUPLE_PACK_WITH_VERSIONSTAMP
|
||||
|
||||
Pops the top item off of the stack as a byte string prefix. Pops the next item
|
||||
off of the stack as N. Pops the next N items off of the stack and packs them
|
||||
as the tuple [item0,item1,...,itemN], with the provided prefix and tries to
|
||||
append insert the position of the first incomplete versionstamp as if the byte
|
||||
string was to be used as a key in a SET_VERSIONSTAMP_KEY atomic op. If there
|
||||
are no incomplete versionstamp instances, then this pushes the literal byte
|
||||
string 'ERROR: NONE' to the stack. If there is more than one, then this pushes
|
||||
the literal byte string 'ERROR: MULTIPLE'. If there is exactly one, then it pushes
|
||||
the literal byte string 'OK' and then pushes the packed tuple. (Languages that
|
||||
do not contain a 'Versionstamp' tuple-type do not have to implement this
|
||||
operation.)
|
||||
|
||||
TUPLE_UNPACK
|
||||
|
||||
Pops the top item off of the stack as PACKED, and then unpacks PACKED into a
|
||||
|
@ -54,7 +54,7 @@ class ApiTest(Test):
|
||||
|
||||
self.generated_keys = []
|
||||
self.outstanding_ops = []
|
||||
self.random = test_util.RandomGenerator(args.max_int_bits, args.api_version)
|
||||
self.random = test_util.RandomGenerator(args.max_int_bits, args.api_version, args.types)
|
||||
|
||||
def add_stack_items(self, num):
|
||||
self.stack_size += num
|
||||
@ -148,6 +148,8 @@ class ApiTest(Test):
|
||||
versions = ['GET_READ_VERSION', 'SET_READ_VERSION', 'GET_COMMITTED_VERSION']
|
||||
snapshot_versions = ['GET_READ_VERSION_SNAPSHOT']
|
||||
tuples = ['TUPLE_PACK', 'TUPLE_UNPACK', 'TUPLE_RANGE', 'TUPLE_SORT', 'SUB', 'ENCODE_FLOAT', 'ENCODE_DOUBLE', 'DECODE_DOUBLE', 'DECODE_FLOAT']
|
||||
if 'versionstamp' in args.types:
|
||||
tuples.append('TUPLE_PACK_WITH_VERSIONSTAMP')
|
||||
resets = ['ON_ERROR', 'RESET', 'CANCEL']
|
||||
read_conflicts = ['READ_CONFLICT_RANGE', 'READ_CONFLICT_KEY']
|
||||
write_conflicts = ['WRITE_CONFLICT_RANGE', 'WRITE_CONFLICT_KEY', 'DISABLE_WRITE_CONFLICT']
|
||||
@ -348,12 +350,12 @@ class ApiTest(Test):
|
||||
key1 = self.versionstamped_values.pack((rand_str1,))
|
||||
|
||||
split = random.randint(0, 70)
|
||||
rand_str2 = self.random.random_string(20+split) + 'XXXXXXXXXX' + self.random.random_string(70-split)
|
||||
rand_str2 = self.random.random_string(20+split) + fdb.tuple.Versionstamp._UNSET_GLOBAL_VERSION + self.random.random_string(70-split)
|
||||
key2 = self.versionstamped_keys.pack() + rand_str2
|
||||
index = key2.find('XXXXXXXXXX')
|
||||
index = key2.find(fdb.tuple.Versionstamp._UNSET_GLOBAL_VERSION)
|
||||
key2 += chr(index%256)+chr(index/256)
|
||||
|
||||
instructions.push_args(u'SET_VERSIONSTAMPED_VALUE', key1, 'XXXXXXXXXX' + rand_str2)
|
||||
instructions.push_args(u'SET_VERSIONSTAMPED_VALUE', key1, fdb.tuple.Versionstamp._UNSET_GLOBAL_VERSION + rand_str2)
|
||||
instructions.append('ATOMIC_OP')
|
||||
|
||||
instructions.push_args(u'SET_VERSIONSTAMPED_KEY', key2, rand_str1)
|
||||
@ -425,6 +427,29 @@ class ApiTest(Test):
|
||||
else:
|
||||
self.add_strings(2)
|
||||
|
||||
elif op == 'TUPLE_PACK_WITH_VERSIONSTAMP':
|
||||
tup = self.random.random_tuple(10, incomplete_versionstamps=True)
|
||||
instructions.push_args(self.versionstamped_keys.pack(), len(tup), *tup)
|
||||
instructions.append(op)
|
||||
self.add_strings(1)
|
||||
|
||||
version_key = fdb.tuple.pack(tup, prefix=self.versionstamped_keys.pack())
|
||||
first_incomplete = version_key.find(fdb.tuple.Versionstamp._UNSET_GLOBAL_VERSION)
|
||||
second_incomplete = -1 if first_incomplete < 0 else version_key.find(fdb.tuple.Versionstamp._UNSET_GLOBAL_VERSION, first_incomplete + 1)
|
||||
|
||||
# If there is exactly one incomplete versionstamp, perform the versionstamped key operation.
|
||||
if first_incomplete >= 0 and second_incomplete < 0:
|
||||
rand_str = self.random.random_string(100)
|
||||
|
||||
instructions.push_args(rand_str)
|
||||
test_util.to_front(instructions, 1)
|
||||
instructions.push_args(u'SET_VERSIONSTAMPED_KEY')
|
||||
instructions.append('ATOMIC_OP')
|
||||
|
||||
version_value_key = self.versionstamped_values.pack((rand_str,))
|
||||
instructions.push_args(u'SET_VERSIONSTAMPED_VALUE', version_value_key, fdb.tuple.Versionstamp._UNSET_GLOBAL_VERSION + fdb.tuple.pack(tup))
|
||||
instructions.append('ATOMIC_OP')
|
||||
|
||||
elif op == 'TUPLE_UNPACK':
|
||||
tup = self.random.random_tuple(10)
|
||||
instructions.push_args(len(tup), *tup)
|
||||
@ -511,7 +536,7 @@ class ApiTest(Test):
|
||||
for k,v in tr.get_range(begin_key, self.versionstamped_values.range().stop, limit=limit):
|
||||
next_begin = k + '\x00'
|
||||
tup = fdb.tuple.unpack(k)
|
||||
key = self.versionstamped_keys.pack() + v[10:].replace('XXXXXXXXXX', v[:10], 1)
|
||||
key = self.versionstamped_keys.pack() + v[10:].replace(fdb.tuple.Versionstamp._UNSET_GLOBAL_VERSION, v[:10], 1)
|
||||
if tr[key] != tup[-1]:
|
||||
incorrect_versionstamps += 1
|
||||
|
||||
|
@ -65,7 +65,7 @@ class DirectoryTest(Test):
|
||||
|
||||
def setup(self, args):
|
||||
self.dir_index = 0
|
||||
self.random = test_util.RandomGenerator(args.max_int_bits, args.api_version)
|
||||
self.random = test_util.RandomGenerator(args.max_int_bits, args.api_version, args.types)
|
||||
|
||||
def generate(self, args, thread_number):
|
||||
instructions = InstructionSet()
|
||||
|
@ -38,7 +38,7 @@ class DirectoryHcaTest(Test):
|
||||
self.next_path = 1
|
||||
|
||||
def setup(self, args):
|
||||
self.random = test_util.RandomGenerator(args.max_int_bits, args.api_version)
|
||||
self.random = test_util.RandomGenerator(args.max_int_bits, args.api_version, args.types)
|
||||
self.transactions = ['tr%d' % i for i in range(3)] # SOMEDAY: parameterize this number?
|
||||
self.barrier_num = 0
|
||||
|
||||
|
@ -29,11 +29,13 @@ import fdb.tuple
|
||||
|
||||
from bindingtester import util
|
||||
from bindingtester import FDB_API_VERSION
|
||||
from bindingtester.known_testers import COMMON_TYPES
|
||||
|
||||
class RandomGenerator(object):
|
||||
def __init__(self, max_int_bits=64, api_version=FDB_API_VERSION):
|
||||
def __init__(self, max_int_bits=64, api_version=FDB_API_VERSION, types=COMMON_TYPES):
|
||||
self.max_int_bits = max_int_bits
|
||||
self.api_version = api_version
|
||||
self.types = types
|
||||
|
||||
def random_unicode_str(self, length):
|
||||
return u''.join(self.random_unicode_char() for i in range(0, length))
|
||||
@ -59,38 +61,45 @@ class RandomGenerator(object):
|
||||
mantissa = random.random()
|
||||
return sign * math.pow(2, exponent) * mantissa
|
||||
|
||||
def random_tuple(self, max_size):
|
||||
def random_tuple(self, max_size, incomplete_versionstamps=False):
|
||||
size = random.randint(1, max_size)
|
||||
tup = []
|
||||
|
||||
for i in range(size):
|
||||
choice = random.randint(0, 8)
|
||||
if choice == 0:
|
||||
choice = random.choice(self.types)
|
||||
if choice == 'int':
|
||||
tup.append(self.random_int())
|
||||
elif choice == 1:
|
||||
elif choice == 'null':
|
||||
tup.append(None)
|
||||
elif choice == 2:
|
||||
elif choice == 'bytes':
|
||||
tup.append(self.random_string(random.randint(0, 100)))
|
||||
elif choice == 3:
|
||||
elif choice == 'string':
|
||||
tup.append(self.random_unicode_str(random.randint(0, 100)))
|
||||
elif choice == 4:
|
||||
elif choice == 'uuid':
|
||||
tup.append(uuid.uuid4())
|
||||
elif choice == 5:
|
||||
elif choice == 'bool':
|
||||
b = random.random() < 0.5
|
||||
if self.api_version < 500:
|
||||
tup.append(int(b))
|
||||
else:
|
||||
tup.append(b)
|
||||
elif choice == 6:
|
||||
elif choice == 'double':
|
||||
tup.append(fdb.tuple.SingleFloat(self.random_float(8)))
|
||||
elif choice == 7:
|
||||
elif choice == 'float':
|
||||
tup.append(self.random_float(11))
|
||||
elif choice == 8:
|
||||
elif choice == 'tuple':
|
||||
length = random.randint(0, max_size - size)
|
||||
if length == 0:
|
||||
tup.append(())
|
||||
else:
|
||||
tup.append(self.random_tuple(length))
|
||||
elif choice == 'versionstamp':
|
||||
if incomplete_versionstamps and random.random() < 0.5:
|
||||
global_version = fdb.tuple.Versionstamp._UNSET_GLOBAL_VERSION
|
||||
else:
|
||||
global_version = self.random_string(10)
|
||||
local_version = random.randint(0, 0xffff)
|
||||
tup.append(fdb.tuple.Versionstamp(global_version, local_version))
|
||||
else:
|
||||
assert false
|
||||
|
||||
|
@ -25,7 +25,7 @@ import fdb.tuple
|
||||
class Subspace (object):
|
||||
|
||||
def __init__(self, prefixTuple=tuple(), rawPrefix=b''):
|
||||
self.rawPrefix = rawPrefix + fdb.tuple.pack(prefixTuple)
|
||||
self.rawPrefix = fdb.tuple.pack(prefixTuple, prefix=rawPrefix)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Subspace(rawPrefix=' + repr(self.rawPrefix) + ')'
|
||||
@ -37,13 +37,13 @@ class Subspace (object):
|
||||
return self.rawPrefix
|
||||
|
||||
def pack(self, t=tuple()):
|
||||
return self.rawPrefix + fdb.tuple.pack(t)
|
||||
return fdb.tuple.pack(t, prefix=self.rawPrefix)
|
||||
|
||||
def unpack(self, key):
|
||||
if not self.contains(key):
|
||||
raise ValueError('Cannot unpack key that is not in subspace.')
|
||||
|
||||
return fdb.tuple.unpack(key[len(self.rawPrefix):])
|
||||
return fdb.tuple.unpack(key, prefix_len=len(self.rawPrefix))
|
||||
|
||||
def range(self, t=tuple()):
|
||||
p = fdb.tuple.range(t)
|
||||
|
@ -29,18 +29,19 @@ import fdb
|
||||
_size_limits = tuple( (1 << (i*8))-1 for i in range(9) )
|
||||
|
||||
# Define type codes:
|
||||
NULL_CODE = 0x00
|
||||
BYTES_CODE = 0x01
|
||||
STRING_CODE = 0x02
|
||||
NESTED_CODE = 0x05
|
||||
INT_ZERO_CODE = 0x14
|
||||
POS_INT_END = 0x1d
|
||||
NEG_INT_START = 0x0b
|
||||
FLOAT_CODE = 0x20
|
||||
DOUBLE_CODE = 0x21
|
||||
FALSE_CODE = 0x26
|
||||
TRUE_CODE = 0x27
|
||||
UUID_CODE = 0x30
|
||||
NULL_CODE = 0x00
|
||||
BYTES_CODE = 0x01
|
||||
STRING_CODE = 0x02
|
||||
NESTED_CODE = 0x05
|
||||
INT_ZERO_CODE = 0x14
|
||||
POS_INT_END = 0x1d
|
||||
NEG_INT_START = 0x0b
|
||||
FLOAT_CODE = 0x20
|
||||
DOUBLE_CODE = 0x21
|
||||
FALSE_CODE = 0x26
|
||||
TRUE_CODE = 0x27
|
||||
UUID_CODE = 0x30
|
||||
VERSIONSTAMP_CODE = 0x33
|
||||
|
||||
# Reserved: Codes 0x03, 0x04, 0x23, and 0x24 are reserved for historical reasons.
|
||||
|
||||
@ -115,6 +116,104 @@ class SingleFloat(object):
|
||||
def __nonzero__(self):
|
||||
return bool(self.value)
|
||||
|
||||
class Versionstamp(object):
|
||||
_GLOBAL_VERSION_LEN = 10
|
||||
_MAX_LOCAL_VERSION = (1 << 16) - 1
|
||||
_UNSET_GLOBAL_VERSION = 10 * six.int2byte(0xff)
|
||||
_STRUCT_FORMAT_STRING = '>' + str(_GLOBAL_VERSION_LEN) + 'sH'
|
||||
|
||||
@classmethod
|
||||
def validate_global_version(cls, global_version):
|
||||
if global_version is None:
|
||||
return
|
||||
if not isinstance(global_version, bytes):
|
||||
raise TypeError("Global version has illegal type " + str(type(global_version)) + " (requires bytes)")
|
||||
elif len(global_version) != cls._GLOBAL_VERSION_LEN:
|
||||
raise ValueError("Global version has incorrect length " + str(len(global_version)) + " (requires " + str(cls._GLOBAL_VERSION_LEN) + ")")
|
||||
|
||||
@classmethod
|
||||
def validate_local_version(cls, local_version):
|
||||
if not isinstance(local_version, six.integer_types):
|
||||
raise TypeError("Local version has illegal type " + str(type(local_version)) + " (requires integer type)")
|
||||
elif local_version < 0 or local_version > cls._MAX_LOCAL_VERSION:
|
||||
raise ValueError("Local version has value " + str(local_version) + " which is out of range")
|
||||
|
||||
|
||||
def __init__(self, global_version=None, local_version=0):
|
||||
Versionstamp.validate_global_version(global_version)
|
||||
Versionstamp.validate_local_version(local_version)
|
||||
self.global_version = global_version
|
||||
self.local_version = local_version
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, v, start=0):
|
||||
if not isinstance(v, bytes):
|
||||
raise TypeError("Cannot parse versionstamp from non-byte string")
|
||||
elif len(v) - start < cls._GLOBAL_VERSION_LEN + 2:
|
||||
raise ValueError("Versionstamp byte string is too short (only " + str(len(v) - start) + " bytes to read from")
|
||||
else:
|
||||
global_version = v[start:start + cls._GLOBAL_VERSION_LEN]
|
||||
if global_version == cls._UNSET_GLOBAL_VERSION:
|
||||
global_version = None
|
||||
local_version = six.indexbytes(v, start + cls._GLOBAL_VERSION_LEN) * (1 << 8) + six.indexbytes(v, start + cls._GLOBAL_VERSION_LEN + 1)
|
||||
return Versionstamp(global_version, local_version)
|
||||
|
||||
def is_complete(self):
|
||||
return self.global_version is not None
|
||||
|
||||
def __repr__(self):
|
||||
return "fdb.tuple.Versionstamp(" + repr(self.global_version) + ", " + repr(self.local_version) + ")"
|
||||
|
||||
def __str__(self):
|
||||
return "Versionstamp(" + str(self.global_version) + ", " + str(self.local_version) + ")"
|
||||
|
||||
def to_bytes(self):
|
||||
return struct.pack(self._STRUCT_FORMAT_STRING,
|
||||
self.global_version if self.is_complete() else self._UNSET_GLOBAL_VERSION,
|
||||
self.local_version)
|
||||
|
||||
def completed(self, new_global_version):
|
||||
if self.is_complete():
|
||||
raise RuntimeError("Cannot complete Versionstamp twice")
|
||||
else:
|
||||
return Versionstamp(new_global_version, self.local_version)
|
||||
|
||||
# Comparisons
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Versionstamp):
|
||||
return self.global_version == other.global_version and self.local_version == other.local_version
|
||||
else:
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __cmp__(self, other):
|
||||
if self.is_complete():
|
||||
if other.is_complete():
|
||||
if self.global_version == other.global_version:
|
||||
return cmp(self.local_version, other.local_version)
|
||||
else:
|
||||
return cmp(self.global_version, other.global_version)
|
||||
else:
|
||||
# All complete are less than all incomplete.
|
||||
return -1
|
||||
else:
|
||||
if other.is_complete():
|
||||
# All incomplete are greater than all complete
|
||||
return 1
|
||||
else:
|
||||
return cmp(self.local_version, other.local_version)
|
||||
|
||||
def __hash__(self):
|
||||
if self.global_version is None:
|
||||
return hash(self.local_version)
|
||||
else:
|
||||
return hash(self.global_version) * 37 ^ hash(self.local_version)
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self.global_version) or bool(self.local_version)
|
||||
|
||||
def _decode(v, pos):
|
||||
code = six.indexbytes(v, pos)
|
||||
if code == NULL_CODE:
|
||||
@ -161,6 +260,8 @@ def _decode(v, pos):
|
||||
if hasattr(fdb, "_version") and fdb._version < 500:
|
||||
raise ValueError("Invalid API version " + str(fdb._version) + " for boolean types")
|
||||
return True, pos+1
|
||||
elif code == VERSIONSTAMP_CODE:
|
||||
return Versionstamp.from_bytes(v, pos+1), pos + 13
|
||||
elif code == NESTED_CODE:
|
||||
ret = []
|
||||
end_pos = pos+1
|
||||
@ -178,32 +279,45 @@ def _decode(v, pos):
|
||||
else:
|
||||
raise ValueError("Unknown data type in DB: " + repr(v))
|
||||
|
||||
def _reduce_children(child_values):
|
||||
version_pos = -1
|
||||
len_so_far = 0
|
||||
bytes_list = []
|
||||
for child_bytes, child_pos in child_values:
|
||||
if child_pos >= 0:
|
||||
if version_pos >= 0:
|
||||
raise ValueError("Multiple unset versionstamps included in tuple")
|
||||
version_pos = len_so_far + child_pos
|
||||
len_so_far += len(child_bytes)
|
||||
bytes_list.append(child_bytes)
|
||||
return bytes_list, version_pos
|
||||
|
||||
def _encode(value, nested=False):
|
||||
# returns [code][data] (code != 0xFF)
|
||||
# encoded values are self-terminating
|
||||
# sorting need to work too!
|
||||
if value == None: # ==, not is, because some fdb.impl.Value are equal to None
|
||||
if nested:
|
||||
return b''.join([six.int2byte(NULL_CODE), six.int2byte(0xff)])
|
||||
return b''.join([six.int2byte(NULL_CODE), six.int2byte(0xff)]), -1
|
||||
else:
|
||||
return b''.join([six.int2byte(NULL_CODE)])
|
||||
return b''.join([six.int2byte(NULL_CODE)]), -1
|
||||
elif isinstance(value, bytes): # also gets non-None fdb.impl.Value
|
||||
return six.int2byte(BYTES_CODE) + value.replace(b'\x00', b'\x00\xFF') + b'\x00'
|
||||
return six.int2byte(BYTES_CODE) + value.replace(b'\x00', b'\x00\xFF') + b'\x00', -1
|
||||
elif isinstance(value, six.text_type):
|
||||
return six.int2byte(STRING_CODE) + value.encode('utf-8').replace(b'\x00', b'\x00\xFF') + b'\x00'
|
||||
return six.int2byte(STRING_CODE) + value.encode('utf-8').replace(b'\x00', b'\x00\xFF') + b'\x00', -1
|
||||
elif isinstance(value, six.integer_types) and (not isinstance(value, bool) or (hasattr(fdb, '_version') and fdb._version < 500)):
|
||||
if value == 0:
|
||||
return b''.join([six.int2byte(INT_ZERO_CODE)])
|
||||
return b''.join([six.int2byte(INT_ZERO_CODE)]), -1
|
||||
elif value > 0:
|
||||
if value >= _size_limits[-1]:
|
||||
length = (value.bit_length()+7)//8
|
||||
data = [six.int2byte(POS_INT_END), six.int2byte(length)]
|
||||
for i in _range(length-1,-1,-1):
|
||||
data.append(six.int2byte( (value>>(8*i))&0xff ))
|
||||
return b''.join(data)
|
||||
return b''.join(data), -1
|
||||
|
||||
n = bisect_left( _size_limits, value )
|
||||
return six.int2byte(INT_ZERO_CODE + n) + struct.pack( ">Q", value )[-n:]
|
||||
return six.int2byte(INT_ZERO_CODE + n) + struct.pack( ">Q", value )[-n:], -1
|
||||
else:
|
||||
if -value >= _size_limits[-1]:
|
||||
length = (value.bit_length()+7)//8
|
||||
@ -211,38 +325,72 @@ def _encode(value, nested=False):
|
||||
data = [six.int2byte(NEG_INT_START), six.int2byte(length^0xff)]
|
||||
for i in _range(length-1,-1,-1):
|
||||
data.append(six.int2byte( (value>>(8*i))&0xff ))
|
||||
return b''.join(data)
|
||||
return b''.join(data), -1
|
||||
|
||||
n = bisect_left( _size_limits, -value )
|
||||
maxv = _size_limits[n]
|
||||
return six.int2byte(INT_ZERO_CODE - n) + struct.pack( ">Q", maxv+value)[-n:]
|
||||
return six.int2byte(INT_ZERO_CODE - n) + struct.pack( ">Q", maxv+value)[-n:], -1
|
||||
elif isinstance(value, ctypes.c_float) or isinstance(value, SingleFloat):
|
||||
return six.int2byte(FLOAT_CODE) + _float_adjust(struct.pack(">f", value.value), True)
|
||||
return six.int2byte(FLOAT_CODE) + _float_adjust(struct.pack(">f", value.value), True), -1
|
||||
elif isinstance(value, ctypes.c_double):
|
||||
return six.int2byte(DOUBLE_CODE) + _float_adjust(struct.pack(">d", value.value), True)
|
||||
return six.int2byte(DOUBLE_CODE) + _float_adjust(struct.pack(">d", value.value), True), -1
|
||||
elif isinstance(value, float):
|
||||
return six.int2byte(DOUBLE_CODE) + _float_adjust(struct.pack(">d", value), True)
|
||||
return six.int2byte(DOUBLE_CODE) + _float_adjust(struct.pack(">d", value), True), -1
|
||||
elif isinstance(value, uuid.UUID):
|
||||
return six.int2byte(UUID_CODE) + value.bytes
|
||||
return six.int2byte(UUID_CODE) + value.bytes, -1
|
||||
elif isinstance(value, bool):
|
||||
if value:
|
||||
return b''.join([six.int2byte(TRUE_CODE)])
|
||||
return b''.join([six.int2byte(TRUE_CODE)]), -1
|
||||
else:
|
||||
return b''.join([six.int2byte(FALSE_CODE)])
|
||||
return b''.join([six.int2byte(FALSE_CODE)]), -1
|
||||
elif isinstance(value, Versionstamp):
|
||||
version_pos = -1 if value.is_complete() else 1
|
||||
return six.int2byte(VERSIONSTAMP_CODE) + value.to_bytes(), version_pos
|
||||
elif isinstance(value, tuple) or isinstance(value, list):
|
||||
return b''.join([six.int2byte(NESTED_CODE)] + list(map(lambda x: _encode(x, True), value)) + [six.int2byte(0x00)])
|
||||
child_bytes, version_pos = _reduce_children(map(lambda x: _encode(x, True), value))
|
||||
new_version_pos = -1 if version_pos < 0 else version_pos + 1
|
||||
return b''.join([six.int2byte(NESTED_CODE)] + child_bytes + [six.int2byte(0x00)]), version_pos
|
||||
else:
|
||||
raise ValueError("Unsupported data type: " + str(type(value)))
|
||||
|
||||
# packs the specified tuple into a key
|
||||
def pack(t):
|
||||
# packs the specified tuple into that may be used for versionstamp operations but may be used for regular ops
|
||||
def pack_maybe_with_versionstamp(t, prefix=None, prefix_len=0):
|
||||
if prefix is not None and prefix_len > 0 and len(prefix) != prefix_len:
|
||||
raise ValueError("Inconsistent values specified for prefix and prefix_len")
|
||||
if prefix_len < 0:
|
||||
raise ValueError("Illegal prefix_len " + str(prefix_len) + " specified")
|
||||
if not isinstance(t, tuple):
|
||||
raise Exception("fdbtuple pack() expects a tuple, got a " + str(type(t)))
|
||||
return b''.join([_encode(x) for x in t])
|
||||
|
||||
bytes_list = [prefix] if prefix is not None else []
|
||||
|
||||
child_bytes, version_pos = _reduce_children(map(_encode, t))
|
||||
if version_pos >= 0:
|
||||
version_pos += len(prefix) if prefix is not None else prefix_len
|
||||
bytes_list.extend(child_bytes)
|
||||
bytes_list.append(struct.pack('<H', version_pos))
|
||||
else:
|
||||
bytes_list.extend(child_bytes)
|
||||
|
||||
return b''.join(bytes_list), version_pos
|
||||
|
||||
# packs the specified tuple into a key
|
||||
def pack(t, prefix=None):
|
||||
res, version_pos = pack_maybe_with_versionstamp(t, prefix)
|
||||
if version_pos >= 0:
|
||||
raise ValueError("Incomplete versionstamp included in vanilla tuple pack")
|
||||
return res
|
||||
|
||||
# packs the specified tuple into a key for versionstamp operations
|
||||
def pack_with_versionstamp(t, prefix=None, prefix_len=0):
|
||||
res, version_pos = pack_maybe_with_versionstamp(t, prefix, prefix_len)
|
||||
if version_pos < 0:
|
||||
raise ValueError("No incomplete versionstamp included in tuple pack with versionstamp")
|
||||
return res
|
||||
|
||||
# unpacks the specified key into a tuple
|
||||
def unpack(key):
|
||||
pos = 0
|
||||
def unpack(key, prefix_len=0):
|
||||
pos = prefix_len
|
||||
res = []
|
||||
while pos < len(key):
|
||||
r, pos = _decode(key, pos)
|
||||
@ -282,6 +430,8 @@ def _code_for(value):
|
||||
return DOUBLE_CODE
|
||||
elif isinstance(value, uuid.UUID):
|
||||
return UUID_CODE
|
||||
elif isinstance(value, Versionstamp):
|
||||
return VERSIONSTAMP_CODE
|
||||
elif isinstance(value, tuple) or isinstance(value, list):
|
||||
return NESTED_CODE
|
||||
else:
|
||||
@ -334,7 +484,7 @@ def _compare_values(value1, value2):
|
||||
elif code1 == NESTED_CODE:
|
||||
return compare(value1, value2)
|
||||
else:
|
||||
# Booleans, UUIDs, and integers can just use standard comparison.
|
||||
# Booleans, UUIDs, integers, and Versionstamps can just use standard comparison.
|
||||
return -1 if value1 < value2 else 0 if value1 == value2 else 1
|
||||
|
||||
# compare element by element and return -1 if t1 < t2 or 1 if t1 > t2 or 0 if t1 == t2
|
||||
|
@ -453,6 +453,19 @@ class Tester:
|
||||
count = inst.pop()
|
||||
items = inst.pop(count)
|
||||
inst.push(fdb.tuple.pack(tuple(items)))
|
||||
elif inst.op == six.u("TUPLE_PACK_WITH_VERSIONSTAMP"):
|
||||
prefix = inst.pop()
|
||||
count = inst.pop()
|
||||
items = inst.pop(count)
|
||||
try:
|
||||
packed = fdb.tuple.pack_with_versionstamp(tuple(items), prefix=prefix)
|
||||
inst.push(b"OK")
|
||||
inst.push(packed)
|
||||
except ValueError as e:
|
||||
if str(e).startswith("No incomplete"):
|
||||
inst.push(b"ERROR: NONE")
|
||||
else:
|
||||
inst.push(b"ERROR: MULTIPLE")
|
||||
elif inst.op == six.u("TUPLE_UNPACK"):
|
||||
for i in fdb.tuple.unpack( inst.pop() ):
|
||||
inst.push(fdb.tuple.pack((i,)))
|
||||
|
Loading…
x
Reference in New Issue
Block a user