diff --git a/doc/www/pelicanconf.py b/doc/www/pelicanconf.py index 51305aa05c7b1c5bfc0f61d276b0f41c7a17f0e2..c27b653152dd7e6b55c04fadce786bff7cd651f3 100644 --- a/doc/www/pelicanconf.py +++ b/doc/www/pelicanconf.py @@ -39,13 +39,15 @@ TAG_SAVE_AS = '' STATIC_PATHS = [ 'robots.txt', - 'ycsb' + 'ycsb', + 'js/highcharts.js', + 'js/tabs.js' ] EXTRA_PATH_METADATA = { - 'robots.txt' : { 'path': 'robots.txt' }, - 'ycsb' : { 'path': 'ycsb' }, - 'js/highlight.js': { 'path': 'highlight.js'}, - 'js/tabs.js' : { 'path': 'tabs.js' }, + 'robots.txt' : { 'path': 'robots.txt' }, + 'ycsb' : { 'path': 'ycsb' }, + 'js/highcharts.js': { 'path': 'highcharts.js'}, + 'js/tabs.js' : { 'path': 'tabs.js' }, } # Uncomment following line if you want document-relative URLs when developing diff --git a/src/box/hash_index.cc b/src/box/hash_index.cc index fb3a4d48484dbb630f55133e903610b2aeeb5899..d8a8db0525af6f9b8e11c03fde870e73db9460f6 100644 --- a/src/box/hash_index.cc +++ b/src/box/hash_index.cc @@ -53,6 +53,40 @@ mh_index_eq_key(const char *key, struct tuple *const *tuple, key_def) == 0; } +static inline uint32_t +mh_hash_field(uint32_t *ph1, uint32_t *pcarry, const char **field, + enum field_type type) +{ + const char *f = *field; + uint32_t size; + + switch (type) { + case STRING: + /* + * (!) MP_STR fields hashed **excluding** MsgPack format + * indentifier. We have to do that to keep compatibility + * with old third-party MsgPack (spec-old.md) implementations. + * \sa https://github.com/tarantool/tarantool/issues/522 + */ + f = mp_decode_str(field, &size); + break; + default: + mp_next(field); + size = *field - f; /* calculate the size of field */ + /* + * (!) All other fields hashed **including** MsgPack format + * identifier (e.g. 0xcc). This was done **intentionally** + * for performance reasons. Please follow MsgPack specification + * and pack all your numbers to the most compact representation. + * If you still want to add support for broken MsgPack, + * please don't forget to patch tuple_compare_field(). + */ + break; + } + assert(size < INT32_MAX); + PMurHash32_Process(ph1, pcarry, f, size); + return size; +} static inline uint32_t mh_index_hash(struct tuple *const *tuple, const struct key_def *key_def) @@ -76,12 +110,7 @@ mh_index_hash(struct tuple *const *tuple, const struct key_def *key_def) for ( ; part < key_def->parts + key_def->part_count; part++) { const char *field = tuple_field(*tuple, part->fieldno); - const char *f = field; - mp_next(&f); - uint32_t size = f - field; - assert(size < INT32_MAX); - PMurHash32_Process(&h, &carry, field, size); - total_size += size; + total_size += mh_hash_field(&h, &carry, &field, part->type); } return PMurHash32_Result(h, carry, total_size); @@ -99,13 +128,15 @@ mh_index_hash_key(const char *key, const struct key_def *key_def) return ((uint32_t)((val)>>33^(val)^(val)<<11)); } - /* Calculate key size */ - const char *k = key; - for (uint32_t part = 0; part < key_def->part_count; part++) { - mp_next(&k); - } + uint32_t h = HASH_SEED; + uint32_t carry = 0; + uint32_t total_size = 0; + + /* Hash fields part by part (see mh_hash_field() comments) */ + for ( ; part < key_def->parts + key_def->part_count; part++) + total_size += mh_hash_field(&h, &carry, &key, part->type); - return PMurHash32(HASH_SEED, key, k - key); + return PMurHash32_Result(h, carry, total_size); } #define mh_int_t uint32_t diff --git a/test/box/call.test.py b/test/box/call.test.py index cba95b6cf7dde82d65172ab3d44f74723c4b3092..bfb30d386a9509b7842ada91e683100b23fb9766 100644 --- a/test/box/call.test.py +++ b/test/box/call.test.py @@ -122,3 +122,6 @@ sql("call space:delete(4)") admin("space:drop()") admin("box.schema.user.drop('test')") + +# Re-connect after removing user +sql.py_con.close() diff --git a/test/box/iproto.result b/test/box/iproto.result index 92cd4fd8ae85c073a7b34fb16e74d933be4d14f7..9b6826200771788236f0f5e126eac12457059c5c 100644 --- a/test/box/iproto.result +++ b/test/box/iproto.result @@ -1,3 +1,6 @@ +box.schema.user.grant('guest', 'read,write,execute', 'universe') +--- +... # # iproto packages test @@ -106,3 +109,52 @@ box.schema.user.grant('guest', 'read,write,execute', 'space', 'test') space:drop() --- ... +space = box.schema.create_space('test') +--- +... +space:create_index('primary', { type = 'hash', parts = {1, 'str'}}) +--- +... +STR 1 +-- +0xa1 => ok ok ok ok ok ok +0xd901 => ok ok ok ok ok ok +0xda0001 => ok ok ok ok ok ok +0xdb00000001 => ok ok ok ok ok ok + +STR 31 +-- +0xbf => ok ok ok ok ok ok +0xd91f => ok ok ok ok ok ok +0xda001f => ok ok ok ok ok ok +0xdb0000001f => ok ok ok ok ok ok + +STR 32 +-- +0xd920 => ok ok ok ok ok +0xda0020 => ok ok ok ok ok +0xdb00000020 => ok ok ok ok ok + +STR 255 +-- +0xd9ff => ok ok ok ok ok +0xda00ff => ok ok ok ok ok +0xdb000000ff => ok ok ok ok ok + +STR 256 +-- +0xda0100 => ok ok ok ok +0xdb00000100 => ok ok ok ok + +STR 65535 +-- +0xdaffff => ok ok ok ok +0xdb0000ffff => ok ok ok ok + +STR 65536 +-- +0xdb00010000 => ok ok ok + +space:drop() +--- +... diff --git a/test/box/iproto.test.py b/test/box/iproto.test.py index d95af37e1e7f156fb18dfe925d637f7e29bb1c86..c4c60e52bc91ea2fb1a122b306305856c22e7b1b 100644 --- a/test/box/iproto.test.py +++ b/test/box/iproto.test.py @@ -5,11 +5,12 @@ import socket import msgpack from tarantool.const import * from tarantool import Connection -from tarantool.request import RequestInsert -from tarantool.request import RequestSelect +from tarantool.request import Request, RequestInsert, RequestSelect from tarantool.response import Response from lib.tarantool_connection import TarantoolConnection +admin("box.schema.user.grant('guest', 'read,write,execute', 'universe')") + print """ # # iproto packages test @@ -149,3 +150,66 @@ c.close() admin("space:drop()") +# +# gh-522: Broken compatibility with msgpack-python for strings of size 33..255 +# +admin("space = box.schema.create_space('test')") +admin("space:create_index('primary', { type = 'hash', parts = {1, 'str'}})") + +class RawInsert(Request): + request_type = REQUEST_TYPE_INSERT + def __init__(self, conn, space_no, blob): + super(RawInsert, self).__init__(conn) + request_body = "\x82" + msgpack.dumps(IPROTO_SPACE_ID) + \ + msgpack.dumps(space_id) + msgpack.dumps(IPROTO_TUPLE) + blob + self._bytes = self.header(len(request_body)) + request_body + +class RawSelect(Request): + request_type = REQUEST_TYPE_SELECT + def __init__(self, conn, space_no, blob): + super(RawSelect, self).__init__(conn) + request_body = "\x83" + msgpack.dumps(IPROTO_SPACE_ID) + \ + msgpack.dumps(space_id) + msgpack.dumps(IPROTO_KEY) + blob + \ + msgpack.dumps(IPROTO_LIMIT) + msgpack.dumps(100); + self._bytes = self.header(len(request_body)) + request_body + +c = sql.py_con +space = c.space('test') +space_id = space.space_no + +TESTS = [ + (1, "\xa1", "\xd9\x01", "\xda\x00\x01", "\xdb\x00\x00\x00\x01"), + (31, "\xbf", "\xd9\x1f", "\xda\x00\x1f", "\xdb\x00\x00\x00\x1f"), + (32, "\xd9\x20", "\xda\x00\x20", "\xdb\x00\x00\x00\x20"), + (255, "\xd9\xff", "\xda\x00\xff", "\xdb\x00\x00\x00\xff"), + (256, "\xda\x01\x00", "\xdb\x00\x00\x01\x00"), + (65535, "\xda\xff\xff", "\xdb\x00\x00\xff\xff"), + (65536, "\xdb\x00\x01\x00\x00"), +] + +for test in TESTS: + it = iter(test) + size = next(it) + print 'STR', size + print '--' + for fmt in it: + print '0x' + fmt.encode('hex'), '=>', + field = '*' * size + c._send_request(RawInsert(c, space_id, "\x91" + fmt + field)) + tuple = space.select(field)[0] + print len(tuple[0])== size and 'ok' or 'fail', + it2 = iter(test) + next(it2) + for fmt2 in it2: + tuple = c._send_request(RawSelect(c, space_id, + "\x91" + fmt2 + field))[0] + print len(tuple[0]) == size and 'ok' or 'fail', + tuple = space.delete(field)[0] + print len(tuple[0]) == size and 'ok' or 'fail', + print + print + +admin("space:drop()") + +server.stop() +server.deploy()