<HTML><BODY><div><div>Hi,</div><div> </div><div>Thanks for the patch!</div><div> </div><div>LGTM, i checked for the number of compilers that if with the templated</div><div>agrument is being processed at the compile time so that there are no jumps</div><div>in asm code.</div><div> </div><div>What i think is needed is the enum like the following:</div><div><div style="background-color:#ffffff; color:#222222; font-weight:normal; line-height:16px; white-space:pre"><div style="font-family:"Ubuntu Mono"; font-size:12px"><span style="color: #0000ff;">enum</span><span style="color: #222222;"> {</span></div><div style="font-family:"Ubuntu Mono"; font-size:12px"><span style="color: #222222;">        NOT_HINTED = </span><span style="color: #09885a;">0</span><span style="color: #222222;">,</span></div><div style="font-family:"Ubuntu Mono"; font-size:12px"><span style="color: #222222;">        HINTED = </span><span style="color: #09885a;">1</span><span style="color: #222222;">,</span></div><div style="font-family:"Ubuntu Mono"; font-size:12px"><span style="color: #222222;">};</span></div><div style="font-family:"Ubuntu Mono"; font-size:12px"> </div><div><font face="Arial">to avoid using <0> & <1>. If needed i can push such patch to the branch.</font></div></div></div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;">Понедельник, 19 октября 2020, 12:52 +03:00 от Aleksandr Lyapunov <alyapunov@tarantool.org>:<br> <div id=""><div class="js-helper js-readmsg-msg"><div><div id="style_16031011350314166834_BODY">Since 9fba29abb4e05babb9b23b4413bd8083f0fba933 (memtx: introduce tuple<br>compare hint) memtx tree key data (indexes) started to contain extra 8<br>bytes as a hint. Now it is optional and can be turned off in an index<br>options with "hint = false" entry.<br><br>Closes #4927<br><br>@TarantoolBot document<br>Title: memtx: optional tuple compare hints<br>Update the documentation for an index creation to reflect that there is<br>now an option to turn off hints for the index.<br>---<br> src/box/index_def.c | 2 +<br> src/box/index_def.h | 6 +<br> src/box/lua/schema.lua | 53 ++++<br> src/box/lua/space.cc | 7 +<br> src/box/memtx_engine.c | 2 +<br> src/box/memtx_tree.cc | 599 ++++++++++++++++++++++++------------<br> src/lib/salad/bps_tree.h | 19 ++<br> test/box/alter.result | 103 ++++++-<br> test/box/alter.test.lua | 34 ++<br> test/box/errinj.result | 3 +-<br> test/box/tree_pk.result | 314 +++++++++++++++++++<br> test/box/tree_pk.test.lua | 115 +++++++<br> test/box/tree_pk_multipart.result | 153 +++++++++<br> test/box/tree_pk_multipart.test.lua | 64 ++++<br> 14 files changed, 1269 insertions(+), 205 deletions(-)<br><br>diff --git a/src/box/index_def.c b/src/box/index_def.c<br>index 9802961..79394b8 100644<br>--- a/src/box/index_def.c<br>+++ b/src/box/index_def.c<br>@@ -51,6 +51,7 @@ const struct index_opts index_opts_default = {<br>  /* .lsn = */ 0,<br>  /* .stat = */ NULL,<br>  /* .func = */ 0,<br>+ /* .hint = */ true,<br> };<br> <br> const struct opt_def index_opts_reg[] = {<br>@@ -66,6 +67,7 @@ const struct opt_def index_opts_reg[] = {<br>  OPT_DEF("lsn", OPT_INT64, struct index_opts, lsn),<br>  OPT_DEF("func", OPT_UINT32, struct index_opts, func_id),<br>  OPT_DEF_LEGACY("sql"),<br>+ OPT_DEF("hint", OPT_BOOL, struct index_opts, hint),<br>  OPT_END,<br> };<br> <br>diff --git a/src/box/index_def.h b/src/box/index_def.h<br>index d928b23..2180a69 100644<br>--- a/src/box/index_def.h<br>+++ b/src/box/index_def.h<br>@@ -165,6 +165,10 @@ struct index_opts {<br>  struct index_stat *stat;<br>  /** Identifier of the functional index function. */<br>  uint32_t func_id;<br>+ /**<br>+ * Use hint optimization for tree index.<br>+ */<br>+ bool hint;<br> };<br> <br> extern const struct index_opts index_opts_default;<br>@@ -211,6 +215,8 @@ index_opts_cmp(const struct index_opts *o1, const struct index_opts *o2)<br>  return o1->bloom_fpr < o2->bloom_fpr ? -1 : 1;<br>  if (o1->func_id != o2->func_id)<br>  return o1->func_id - o2->func_id;<br>+ if (o1->hint != o2->hint)<br>+ return o1->hint - o2->hint;<br>  return 0;<br> }<br> <br>diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua<br>index 1131af7..9cc1289 100644<br>--- a/src/box/lua/schema.lua<br>+++ b/src/box/lua/schema.lua<br>@@ -1000,8 +1000,31 @@ local index_options = {<br>     page_size = 'number',<br>     bloom_fpr = 'number',<br>     func = 'number, string',<br>+ hint = 'boolean',<br> }<br> <br>+local function jsonpaths_from_idx_parts(parts)<br>+ local paths = {}<br>+<br>+ for _, part in pairs(parts) do<br>+ if type(part.path) == 'string' then<br>+ table.insert(paths, part.path)<br>+ end<br>+ end<br>+<br>+ return paths<br>+end<br>+<br>+local function is_multikey_index(parts)<br>+ for _, path in pairs(jsonpaths_from_idx_parts(parts)) do<br>+ if path:find('[*]', 1, true) then<br>+ return true<br>+ end<br>+ end<br>+<br>+ return false<br>+end<br>+<br> --<br> -- check_param_table() template for alter index,<br> -- includes all index options.<br>@@ -1076,6 +1099,15 @@ box.schema.index.create = function(space_id, name, options)<br>         options_defaults = {}<br>     end<br>     options = update_param_table(options, options_defaults)<br>+ if options.hint and<br>+ (options.type ~= 'tree' or box.space[space_id].engine ~= 'memtx') then<br>+ box.error(box.error.MODIFY_INDEX, name, space.name,<br>+ "hint is only reasonable with memtx tree index")<br>+ end<br>+ if options.hint and options.func then<br>+ box.error(box.error.MODIFY_INDEX, name, space.name,<br>+ "functional index can't use hints")<br>+ end<br> <br>     local _index = box.space[box.schema.INDEX_ID]<br>     local _vindex = box.space[box.schema.VINDEX_ID]<br>@@ -1115,6 +1147,7 @@ box.schema.index.create = function(space_id, name, options)<br>             run_size_ratio = options.run_size_ratio,<br>             bloom_fpr = options.bloom_fpr,<br>             func = options.func,<br>+ hint = options.hint,<br>     }<br>     local field_type_aliases = {<br>         num = 'unsigned'; -- Deprecated since 1.7.2<br>@@ -1135,6 +1168,10 @@ box.schema.index.create = function(space_id, name, options)<br>     if parts_can_be_simplified then<br>         parts = simplify_index_parts(parts)<br>     end<br>+ if options.hint and is_multikey_index(parts) then<br>+ box.error(box.error.MODIFY_INDEX, name, space.name,<br>+ "multikey index can't use hints")<br>+ end<br>     if index_opts.func ~= nil and type(index_opts.func) == 'string' then<br>         index_opts.func = func_id_by_name(index_opts.func)<br>     end<br>@@ -1253,6 +1290,17 @@ box.schema.index.alter = function(space_id, index_id, options)<br>             index_opts[k] = options[k]<br>         end<br>     end<br>+ if options.hint and<br>+ (options.type ~= 'tree' or box.space[space_id].engine ~= 'memtx') then<br>+ box.error(box.error.MODIFY_INDEX, space.index[index_id].name,<br>+ space.name,<br>+ "hint is only reasonable with memtx tree index")<br>+ end<br>+ if options.hint and options.func then<br>+ box.error(box.error.MODIFY_INDEX, space.index[index_id].name,<br>+ space.name,<br>+ "functional index can't use hints")<br>+ end<br>     if options.parts then<br>         local parts_can_be_simplified<br>         parts, parts_can_be_simplified =<br>@@ -1262,6 +1310,11 @@ box.schema.index.alter = function(space_id, index_id, options)<br>             parts = simplify_index_parts(parts)<br>         end<br>     end<br>+ if options.hint and is_multikey_index(parts) then<br>+ box.error(box.error.MODIFY_INDEX, space.index[index_id].name,<br>+ space.name,<br>+ "multikey index can't use hints")<br>+ end<br>     if index_opts.func ~= nil and type(index_opts.func) == 'string' then<br>         index_opts.func = func_id_by_name(index_opts.func)<br>     end<br>diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc<br>index 177c588..1ea993c 100644<br>--- a/src/box/lua/space.cc<br>+++ b/src/box/lua/space.cc<br>@@ -344,6 +344,13 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i)<br>  lua_pushnumber(L, index_opts->dimension);<br>  lua_setfield(L, -2, "dimension");<br>  }<br>+ if (space_is_memtx(space) && index_def->type == TREE) {<br>+ lua_pushboolean(L, index_opts->hint);<br>+ lua_setfield(L, -2, "hint");<br>+ } else {<br>+ lua_pushnil(L);<br>+ lua_setfield(L, -2, "hint");<br>+ }<br> <br>  if (index_opts->func_id > 0) {<br>  lua_pushstring(L, "func");<br>diff --git a/src/box/memtx_engine.c b/src/box/memtx_engine.c<br>index <span class="js-phone-number">8147557</span>..43000ba 100644<br>--- a/src/box/memtx_engine.c<br>+++ b/src/box/memtx_engine.c<br>@@ -1398,6 +1398,8 @@ memtx_index_def_change_requires_rebuild(struct index *index,<br>  return true;<br>  if (old_def->opts.func_id != new_def->opts.func_id)<br>  return true;<br>+ if (old_def->opts.hint != new_def->opts.hint)<br>+ return true;<br> <br>  const struct key_def *old_cmp_def, *new_cmp_def;<br>  if (index_depends_on_pk(index)) {<br>diff --git a/src/box/memtx_tree.cc b/src/box/memtx_tree.cc<br>index d3b993b..17d58c5 100644<br>--- a/src/box/memtx_tree.cc<br>+++ b/src/box/memtx_tree.cc<br>@@ -45,23 +45,51 @@<br> /**<br>  * Struct that is used as a key in BPS tree definition.<br>  */<br>-struct memtx_tree_key_data {<br>+struct memtx_tree_key_data_common {<br>  /** Sequence of msgpacked search fields. */<br>  const char *key;<br>  /** Number of msgpacked search fields. */<br>  uint32_t part_count;<br>+};<br>+<br>+template <int USE_HINT><br>+struct memtx_tree_key_data;<br>+<br>+template <><br>+struct memtx_tree_key_data<0> : memtx_tree_key_data_common {<br>+ static constexpr hint_t hint = HINT_NONE;<br>+ void set_hint(hint_t) { assert(false); }<br>+};<br>+<br>+template <><br>+struct memtx_tree_key_data<1> : memtx_tree_key_data_common {<br>  /** Comparison hint, see tuple_hint(). */<br>  hint_t hint;<br>+ void set_hint(hint_t h) { hint = h; }<br> };<br> <br> /**<br>  * Struct that is used as a elem in BPS tree definition.<br>  */<br>-struct memtx_tree_data {<br>+struct memtx_tree_data_common {<br>  /* Tuple that this node is represents. */<br>  struct tuple *tuple;<br>+};<br>+<br>+template <int USE_HINT><br>+struct memtx_tree_data;<br>+<br>+template <><br>+struct memtx_tree_data<0> : memtx_tree_data_common {<br>+ static constexpr hint_t hint = HINT_NONE;<br>+ void set_hint(hint_t) { assert(false); }<br>+};<br>+<br>+template <><br>+struct memtx_tree_data<1> : memtx_tree_data<0> {<br>  /** Comparison hint, see key_hint(). */<br>  hint_t hint;<br>+ void set_hint(hint_t h) { hint = h; }<br> };<br> <br> /**<br>@@ -73,8 +101,8 @@ struct memtx_tree_data {<br>  * @retval false - Otherwise.<br>  */<br> static bool<br>-memtx_tree_data_is_equal(const struct memtx_tree_data *a,<br>- const struct memtx_tree_data *b)<br>+memtx_tree_data_is_equal(const struct memtx_tree_data_common *a,<br>+ const struct memtx_tree_data_common *b)<br> {<br>  return a->tuple == b->tuple;<br> }<br>@@ -89,12 +117,28 @@ memtx_tree_data_is_equal(const struct memtx_tree_data *a,<br>  (b)->part_count, (b)->hint, arg)<br> #define BPS_TREE_IS_IDENTICAL(a, b) memtx_tree_data_is_equal(&a, &b)<br> #define BPS_TREE_NO_DEBUG 1<br>-#define bps_tree_elem_t struct memtx_tree_data<br>-#define bps_tree_key_t struct memtx_tree_key_data *<br> #define bps_tree_arg_t struct key_def *<br> <br>+#define BPS_TREE_NAMESPACE NS_NO_HINT<br>+#define bps_tree_elem_t struct memtx_tree_data<0><br>+#define bps_tree_key_t struct memtx_tree_key_data<0> *<br>+<br> #include "salad/bps_tree.h"<br> <br>+#undef BPS_TREE_NAMESPACE<br>+#undef bps_tree_elem_t<br>+#undef bps_tree_key_t<br>+<br>+#define BPS_TREE_NAMESPACE NS_USE_HINT<br>+#define bps_tree_elem_t struct memtx_tree_data<1><br>+#define bps_tree_key_t struct memtx_tree_key_data<1> *<br>+<br>+#include "salad/bps_tree.h"<br>+<br>+#undef BPS_TREE_NAMESPACE<br>+#undef bps_tree_elem_t<br>+#undef bps_tree_key_t<br>+<br> #undef BPS_TREE_NAME<br> #undef BPS_TREE_BLOCK_SIZE<br> #undef BPS_TREE_EXTENT_SIZE<br>@@ -102,66 +146,119 @@ memtx_tree_data_is_equal(const struct memtx_tree_data *a,<br> #undef BPS_TREE_COMPARE_KEY<br> #undef BPS_TREE_IS_IDENTICAL<br> #undef BPS_TREE_NO_DEBUG<br>-#undef bps_tree_elem_t<br>-#undef bps_tree_key_t<br> #undef bps_tree_arg_t<br> <br>+using namespace NS_NO_HINT;<br>+using namespace NS_USE_HINT;<br>+<br>+template <int USE_HINT><br>+struct memtx_tree_selector;<br>+<br>+template <><br>+struct memtx_tree_selector<0> : NS_NO_HINT::memtx_tree {};<br>+<br>+template <><br>+struct memtx_tree_selector<1> : NS_USE_HINT::memtx_tree {};<br>+<br>+template <int USE_HINT><br>+using memtx_tree_t = struct memtx_tree_selector<USE_HINT>;<br>+<br>+template <int USE_HINT><br>+struct memtx_tree_iterator_selector;<br>+<br>+template <><br>+struct memtx_tree_iterator_selector<0> {<br>+ using type = NS_NO_HINT::memtx_tree_iterator;<br>+};<br>+<br>+template <><br>+struct memtx_tree_iterator_selector<1> {<br>+ using type = NS_USE_HINT::memtx_tree_iterator;<br>+};<br>+<br>+template <int USE_HINT><br>+using memtx_tree_iterator_t = typename memtx_tree_iterator_selector<USE_HINT>::type;<br>+<br>+static void<br>+invalidate_tree_iterator(NS_NO_HINT::memtx_tree_iterator *itr)<br>+{<br>+ *itr = NS_NO_HINT::memtx_tree_invalid_iterator();<br>+}<br>+<br>+static void<br>+invalidate_tree_iterator(NS_USE_HINT::memtx_tree_iterator *itr)<br>+{<br>+ *itr = NS_USE_HINT::memtx_tree_invalid_iterator();<br>+}<br>+<br>+template <int USE_HINT><br> struct memtx_tree_index {<br>  struct index base;<br>- struct memtx_tree tree;<br>- struct memtx_tree_data *build_array;<br>+ memtx_tree_t<USE_HINT> tree;<br>+ struct memtx_tree_data<USE_HINT> *build_array;<br>  size_t build_array_size, build_array_alloc_size;<br>  struct memtx_gc_task gc_task;<br>- struct memtx_tree_iterator gc_iterator;<br>+ memtx_tree_iterator_t<USE_HINT> gc_iterator;<br> };<br> <br> /* {{{ Utilities. *************************************************/<br> <br>+template <class TREE><br> static inline struct key_def *<br>-memtx_tree_cmp_def(struct memtx_tree *tree)<br>+memtx_tree_cmp_def(TREE *tree)<br> {<br>  return tree->arg;<br> }<br> <br>+template <int USE_HINT><br> static int<br> memtx_tree_qcompare(const void* a, const void *b, void *c)<br> {<br>- const struct memtx_tree_data *data_a = (struct memtx_tree_data *)a;<br>- const struct memtx_tree_data *data_b = (struct memtx_tree_data *)b;<br>+ const struct memtx_tree_data<USE_HINT> *data_a =<br>+ (struct memtx_tree_data<USE_HINT> *)a;<br>+ const struct memtx_tree_data<USE_HINT> *data_b =<br>+ (struct memtx_tree_data<USE_HINT> *)b;<br>  struct key_def *key_def = (struct key_def *)c;<br>  return tuple_compare(data_a->tuple, data_a->hint, data_b->tuple,<br>  data_b->hint, key_def);<br> }<br> <br> /* {{{ MemtxTree Iterators ****************************************/<br>+template <int USE_HINT><br> struct tree_iterator {<br>  struct iterator base;<br>- struct memtx_tree_iterator tree_iterator;<br>+ memtx_tree_iterator_t<USE_HINT> tree_iterator;<br>  enum iterator_type type;<br>- struct memtx_tree_key_data key_data;<br>- struct memtx_tree_data current;<br>+ struct memtx_tree_key_data<USE_HINT> key_data;<br>+ struct memtx_tree_data<USE_HINT> current;<br>  /** Memory pool the iterator was allocated from. */<br>  struct mempool *pool;<br> };<br> <br>-static_assert(sizeof(struct tree_iterator) <= MEMTX_ITERATOR_SIZE,<br>- "sizeof(struct tree_iterator) must be less than or equal "<br>+static_assert(sizeof(struct tree_iterator<0>) <= MEMTX_ITERATOR_SIZE,<br>+ "sizeof(struct tree_iterator<0>) must be less than or equal "<br>+ "to MEMTX_ITERATOR_SIZE");<br>+static_assert(sizeof(struct tree_iterator<1>) <= MEMTX_ITERATOR_SIZE,<br>+ "sizeof(struct tree_iterator<1>) must be less than or equal "<br>  "to MEMTX_ITERATOR_SIZE");<br> <br>+template <int USE_HINT><br> static void<br> tree_iterator_free(struct iterator *iterator);<br> <br>-static inline struct tree_iterator *<br>-tree_iterator(struct iterator *it)<br>+template <int USE_HINT><br>+static inline struct tree_iterator<USE_HINT> *<br>+get_tree_iterator(struct iterator *it)<br> {<br>- assert(it->free == tree_iterator_free);<br>- return (struct tree_iterator *) it;<br>+ assert(it->free == &tree_iterator_free<USE_HINT>);<br>+ return (struct tree_iterator<USE_HINT> *) it;<br> }<br> <br>+template <int USE_HINT><br> static void<br> tree_iterator_free(struct iterator *iterator)<br> {<br>- struct tree_iterator *it = tree_iterator(iterator);<br>+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);<br>  struct tuple *tuple = it->current.tuple;<br>  if (tuple != NULL)<br>  tuple_unref(tuple);<br>@@ -176,14 +273,15 @@ tree_iterator_dummie(struct iterator *iterator, struct tuple **ret)<br>  return 0;<br> }<br> <br>+template <int USE_HINT><br> static int<br> tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)<br> {<br>- struct memtx_tree_index *index =<br>- (struct memtx_tree_index *)iterator->index;<br>- struct tree_iterator *it = tree_iterator(iterator);<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)iterator->index;<br>+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);<br>  assert(it->current.tuple != NULL);<br>- struct memtx_tree_data *check =<br>+ struct memtx_tree_data<USE_HINT> *check =<br>  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);<br>  if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {<br>  it->tree_iterator = memtx_tree_upper_bound_elem(&index->tree,<br>@@ -192,7 +290,7 @@ tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)<br>  memtx_tree_iterator_next(&index->tree, &it->tree_iterator);<br>  }<br>  tuple_unref(it->current.tuple);<br>- struct memtx_tree_data *res =<br>+ struct memtx_tree_data<USE_HINT> *res =<br>  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);<br>  if (res == NULL) {<br>  iterator->next = tree_iterator_dummie;<br>@@ -206,14 +304,15 @@ tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)<br>  return 0;<br> }<br> <br>+template <int USE_HINT><br> static int<br> tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)<br> {<br>- struct memtx_tree_index *index =<br>- (struct memtx_tree_index *)iterator->index;<br>- struct tree_iterator *it = tree_iterator(iterator);<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)iterator->index;<br>+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);<br>  assert(it->current.tuple != NULL);<br>- struct memtx_tree_data *check =<br>+ struct memtx_tree_data<USE_HINT> *check =<br>  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);<br>  if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {<br>  it->tree_iterator = memtx_tree_lower_bound_elem(&index->tree,<br>@@ -221,7 +320,7 @@ tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)<br>  }<br>  memtx_tree_iterator_prev(&index->tree, &it->tree_iterator);<br>  tuple_unref(it->current.tuple);<br>- struct memtx_tree_data *res =<br>+ struct memtx_tree_data<USE_HINT> *res =<br>  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);<br>  if (!res) {<br>  iterator->next = tree_iterator_dummie;<br>@@ -235,14 +334,15 @@ tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)<br>  return 0;<br> }<br> <br>+template <int USE_HINT><br> static int<br> tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)<br> {<br>- struct memtx_tree_index *index =<br>- (struct memtx_tree_index *)iterator->index;<br>- struct tree_iterator *it = tree_iterator(iterator);<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)iterator->index;<br>+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);<br>  assert(it->current.tuple != NULL);<br>- struct memtx_tree_data *check =<br>+ struct memtx_tree_data<USE_HINT> *check =<br>  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);<br>  if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {<br>  it->tree_iterator = memtx_tree_upper_bound_elem(&index->tree,<br>@@ -251,7 +351,7 @@ tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)<br>  memtx_tree_iterator_next(&index->tree, &it->tree_iterator);<br>  }<br>  tuple_unref(it->current.tuple);<br>- struct memtx_tree_data *res =<br>+ struct memtx_tree_data<USE_HINT> *res =<br>  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);<br>  /* Use user key def to save a few loops. */<br>  if (res == NULL ||<br>@@ -271,14 +371,15 @@ tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)<br>  return 0;<br> }<br> <br>+template <int USE_HINT><br> static int<br> tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)<br> {<br>- struct memtx_tree_index *index =<br>- (struct memtx_tree_index *)iterator->index;<br>- struct tree_iterator *it = tree_iterator(iterator);<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)iterator->index;<br>+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);<br>  assert(it->current.tuple != NULL);<br>- struct memtx_tree_data *check =<br>+ struct memtx_tree_data<USE_HINT> *check =<br>  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);<br>  if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {<br>  it->tree_iterator = memtx_tree_lower_bound_elem(&index->tree,<br>@@ -286,7 +387,7 @@ tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)<br>  }<br>  memtx_tree_iterator_prev(&index->tree, &it->tree_iterator);<br>  tuple_unref(it->current.tuple);<br>- struct memtx_tree_data *res =<br>+ struct memtx_tree_data<USE_HINT> *res =<br>  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);<br>  /* Use user key def to save a few loops. */<br>  if (res == NULL ||<br>@@ -307,28 +408,30 @@ tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)<br> }<br> <br> #define WRAP_ITERATOR_METHOD(name) \<br>+template <int USE_HINT> \<br> static int \<br> name(struct iterator *iterator, struct tuple **ret) \<br> { \<br>- struct memtx_tree *tree = \<br>- &((struct memtx_tree_index *)iterator->index)->tree; \<br>- struct tree_iterator *it = tree_iterator(iterator); \<br>- struct memtx_tree_iterator *ti = &it->tree_iterator; \<br>+ memtx_tree_t<USE_HINT> *tree = \<br>+ &((struct memtx_tree_index<USE_HINT> *)iterator->index)->tree; \<br>+ struct tree_iterator<USE_HINT> *it = \<br>+ get_tree_iterator<USE_HINT>(iterator); \<br>+ memtx_tree_iterator_t<USE_HINT> *ti = &it->tree_iterator; \<br>  uint32_t iid = iterator->index->def->iid; \<br>  bool is_multikey = iterator->index->def->key_def->is_multikey; \<br>  struct txn *txn = in_txn(); \<br>  struct space *space = space_by_id(iterator->space_id); \<br>  bool is_rw = txn != NULL; \<br>  do { \<br>- int rc = name##_base(iterator, ret); \<br>+ int rc = name##_base<USE_HINT>(iterator, ret); \<br>  if (rc != 0 || *ret == NULL) \<br>  return rc; \<br>  uint32_t mk_index = 0; \<br>  if (is_multikey) { \<br>- struct memtx_tree_data *check = \<br>+ struct memtx_tree_data<USE_HINT> *check = \<br>  memtx_tree_iterator_get_elem(tree, ti); \<br>  assert(check != NULL); \<br>- mk_index = check->hint; \<br>+ mk_index = (uint32_t)check->hint; \<br>  } \<br>  *ret = memtx_tx_tuple_clarify(txn, space, *ret, \<br>  iid, mk_index, is_rw); \<br>@@ -347,27 +450,28 @@ WRAP_ITERATOR_METHOD(tree_iterator_prev_equal);<br> <br> #undef WRAP_ITERATOR_METHOD<br> <br>+template <int USE_HINT><br> static void<br>-tree_iterator_set_next_method(struct tree_iterator *it)<br>+tree_iterator_set_next_method(struct tree_iterator<USE_HINT> *it)<br> {<br>  assert(it->current.tuple != NULL);<br>  switch (it->type) {<br>  case ITER_EQ:<br>- it->base.next = tree_iterator_next_equal;<br>+ it->base.next = tree_iterator_next_equal<USE_HINT>;<br>  break;<br>  case ITER_REQ:<br>- it->base.next = tree_iterator_prev_equal;<br>+ it->base.next = tree_iterator_prev_equal<USE_HINT>;<br>  break;<br>  case ITER_ALL:<br>- it->base.next = tree_iterator_next;<br>+ it->base.next = tree_iterator_next<USE_HINT>;<br>  break;<br>  case ITER_LT:<br>  case ITER_LE:<br>- it->base.next = tree_iterator_prev;<br>+ it->base.next = tree_iterator_prev<USE_HINT>;<br>  break;<br>  case ITER_GE:<br>  case ITER_GT:<br>- it->base.next = tree_iterator_next;<br>+ it->base.next = tree_iterator_next<USE_HINT>;<br>  break;<br>  default:<br>  /* The type was checked in initIterator */<br>@@ -375,15 +479,16 @@ tree_iterator_set_next_method(struct tree_iterator *it)<br>  }<br> }<br> <br>+template <int USE_HINT><br> static int<br> tree_iterator_start(struct iterator *iterator, struct tuple **ret)<br> {<br>  *ret = NULL;<br>- struct memtx_tree_index *index =<br>- (struct memtx_tree_index *)iterator->index;<br>- struct tree_iterator *it = tree_iterator(iterator);<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)iterator->index;<br>+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);<br>  it->base.next = tree_iterator_dummie;<br>- struct memtx_tree *tree = &index->tree;<br>+ memtx_tree_t<USE_HINT> *tree = &index->tree;<br>  enum iterator_type type = it->type;<br>  bool exact = false;<br>  assert(it->current.tuple == NULL);<br>@@ -423,8 +528,8 @@ tree_iterator_start(struct iterator *iterator, struct tuple **ret)<br>  }<br>  }<br> <br>- struct memtx_tree_data *res = memtx_tree_iterator_get_elem(tree,<br>- &it->tree_iterator);<br>+ struct memtx_tree_data<USE_HINT> *res =<br>+ memtx_tree_iterator_get_elem(tree, &it->tree_iterator);<br>  if (!res)<br>  return 0;<br>  *ret = res->tuple;<br>@@ -454,14 +559,16 @@ tree_iterator_start(struct iterator *iterator, struct tuple **ret)<br> <br> /* {{{ MemtxTree **********************************************************/<br> <br>+template <int USE_HINT><br> static void<br>-memtx_tree_index_free(struct memtx_tree_index *index)<br>+memtx_tree_index_free(struct memtx_tree_index<USE_HINT> *index)<br> {<br>  memtx_tree_destroy(&index->tree);<br>  free(index->build_array);<br>  free(index);<br> }<br> <br>+template <int USE_HINT><br> static void<br> memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)<br> {<br>@@ -475,14 +582,14 @@ memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)<br>  enum { YIELD_LOOPS = 10 };<br> #endif<br> <br>- struct memtx_tree_index *index = container_of(task,<br>- struct memtx_tree_index, gc_task);<br>- struct memtx_tree *tree = &index->tree;<br>- struct memtx_tree_iterator *itr = &index->gc_iterator;<br>+ struct memtx_tree_index<USE_HINT> *index = container_of(task,<br>+ struct memtx_tree_index<USE_HINT>, gc_task);<br>+ memtx_tree_t<USE_HINT> *tree = &index->tree;<br>+ memtx_tree_iterator_t<USE_HINT> *itr = &index->gc_iterator;<br> <br>  unsigned int loops = 0;<br>  while (!memtx_tree_iterator_is_invalid(itr)) {<br>- struct memtx_tree_data *res =<br>+ struct memtx_tree_data<USE_HINT> *res =<br>  memtx_tree_iterator_get_elem(tree, itr);<br>  memtx_tree_iterator_next(tree, itr);<br>  tuple_unref(res->tuple);<br>@@ -494,23 +601,32 @@ memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)<br>  *done = true;<br> }<br> <br>+template <int USE_HINT><br> static void<br> memtx_tree_index_gc_free(struct memtx_gc_task *task)<br> {<br>- struct memtx_tree_index *index = container_of(task,<br>- struct memtx_tree_index, gc_task);<br>+ struct memtx_tree_index<USE_HINT> *index = container_of(task,<br>+ struct memtx_tree_index<USE_HINT>, gc_task);<br>  memtx_tree_index_free(index);<br> }<br> <br>-static const struct memtx_gc_task_vtab memtx_tree_index_gc_vtab = {<br>- .run = memtx_tree_index_gc_run,<br>- .free = memtx_tree_index_gc_free,<br>+template <int USE_HINT><br>+static struct memtx_gc_task_vtab * get_memtx_tree_index_gc_vtab()<br>+{<br>+ static memtx_gc_task_vtab tab =<br>+ {<br>+ .run = memtx_tree_index_gc_run<USE_HINT>,<br>+ .free = memtx_tree_index_gc_free<USE_HINT>,<br>+ };<br>+ return &tab;<br> };<br> <br>+template <int USE_HINT><br> static void<br> memtx_tree_index_destroy(struct index *base)<br> {<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)base;<br>  struct memtx_engine *memtx = (struct memtx_engine *)base->engine;<br>  if (base->def->iid == 0) {<br>  /*<br>@@ -518,7 +634,7 @@ memtx_tree_index_destroy(struct index *base)<br>  * in the index, which may take a while. Schedule a<br>  * background task in order not to block tx thread.<br>  */<br>- index->gc_task.vtab = &memtx_tree_index_gc_vtab;<br>+ index->gc_task.vtab = get_memtx_tree_index_gc_vtab<USE_HINT>();<br>  index->gc_iterator = memtx_tree_iterator_first(&index->tree);<br>  memtx_engine_schedule_gc(memtx, &index->gc_task);<br>  } else {<br>@@ -530,10 +646,12 @@ memtx_tree_index_destroy(struct index *base)<br>  }<br> }<br> <br>+template <int USE_HINT><br> static void<br> memtx_tree_index_update_def(struct index *base)<br> {<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)base;<br>  struct index_def *def = base->def;<br>  /*<br>  * We use extended key def for non-unique and nullable<br>@@ -553,51 +671,62 @@ memtx_tree_index_depends_on_pk(struct index *base)<br>  return !def->opts.is_unique || def->key_def->is_nullable;<br> }<br> <br>+template <int USE_HINT><br> static ssize_t<br> memtx_tree_index_size(struct index *base)<br> {<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)base;<br>  return memtx_tree_size(&index->tree);<br> }<br> <br>+template <int USE_HINT><br> static ssize_t<br> memtx_tree_index_bsize(struct index *base)<br> {<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)base;<br>  return memtx_tree_mem_used(&index->tree);<br> }<br> <br>+template <int USE_HINT><br> static int<br> memtx_tree_index_random(struct index *base, uint32_t rnd, struct tuple **result)<br> {<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>- struct memtx_tree_data *res = memtx_tree_random(&index->tree, rnd);<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)base;<br>+ struct memtx_tree_data<USE_HINT> *res = memtx_tree_random(&index->tree, rnd);<br>  *result = res != NULL ? res->tuple : NULL;<br>  return 0;<br> }<br> <br>+template <int USE_HINT><br> static ssize_t<br> memtx_tree_index_count(struct index *base, enum iterator_type type,<br>  const char *key, uint32_t part_count)<br> {<br>  if (type == ITER_ALL)<br>- return memtx_tree_index_size(base); /* optimization */<br>+ return memtx_tree_index_size<USE_HINT>(base); /* optimization */<br>  return generic_index_count(base, type, key, part_count);<br> }<br> <br>+template <int USE_HINT><br> static int<br> memtx_tree_index_get(struct index *base, const char *key,<br>  uint32_t part_count, struct tuple **result)<br> {<br>  assert(base->def->opts.is_unique &&<br>  part_count == base->def->key_def->part_count);<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)base;<br>  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);<br>- struct memtx_tree_key_data key_data;<br>+ struct memtx_tree_key_data<USE_HINT> key_data;<br>  key_data.key = key;<br>  key_data.part_count = part_count;<br>- key_data.hint = key_hint(key, part_count, cmp_def);<br>- struct memtx_tree_data *res = memtx_tree_find(&index->tree, &key_data);<br>+ if (USE_HINT)<br>+ key_data.set_hint(key_hint(key, part_count, cmp_def));<br>+ struct memtx_tree_data<USE_HINT> *res =<br>+ memtx_tree_find(&index->tree, &key_data);<br>  if (res == NULL) {<br>  *result = NULL;<br>  return 0;<br>@@ -611,18 +740,21 @@ memtx_tree_index_get(struct index *base, const char *key,<br>  return 0;<br> }<br> <br>+template <int USE_HINT><br> static int<br> memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,<br>  struct tuple *new_tuple, enum dup_replace_mode mode,<br>  struct tuple **result)<br> {<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)base;<br>  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);<br>  if (new_tuple) {<br>- struct memtx_tree_data new_data;<br>+ struct memtx_tree_data<USE_HINT> new_data;<br>  new_data.tuple = new_tuple;<br>- new_data.hint = tuple_hint(new_tuple, cmp_def);<br>- struct memtx_tree_data dup_data;<br>+ if (USE_HINT)<br>+ new_data.set_hint(tuple_hint(new_tuple, cmp_def));<br>+ struct memtx_tree_data<USE_HINT> dup_data;<br>  dup_data.tuple = NULL;<br> <br>  /* Try to optimistically replace the new_tuple. */<br>@@ -652,9 +784,10 @@ memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,<br>  }<br>  }<br>  if (old_tuple) {<br>- struct memtx_tree_data old_data;<br>+ struct memtx_tree_data<USE_HINT> old_data;<br>  old_data.tuple = old_tuple;<br>- old_data.hint = tuple_hint(old_tuple, cmp_def);<br>+ if (USE_HINT)<br>+ old_data.set_hint(tuple_hint(old_tuple, cmp_def));<br>  memtx_tree_delete(&index->tree, old_data);<br>  }<br>  *result = old_tuple;<br>@@ -667,13 +800,13 @@ memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,<br>  * by all it's multikey indexes.<br>  */<br> static int<br>-memtx_tree_index_replace_multikey_one(struct memtx_tree_index *index,<br>+memtx_tree_index_replace_multikey_one(struct memtx_tree_index<1> *index,<br>  struct tuple *old_tuple, struct tuple *new_tuple,<br>  enum dup_replace_mode mode, hint_t hint,<br>- struct memtx_tree_data *replaced_data,<br>+ struct memtx_tree_data<1> *replaced_data,<br>  bool *is_multikey_conflict)<br> {<br>- struct memtx_tree_data new_data, dup_data;<br>+ struct memtx_tree_data<1> new_data, dup_data;<br>  new_data.tuple = new_tuple;<br>  new_data.hint = hint;<br>  dup_data.tuple = NULL;<br>@@ -720,11 +853,11 @@ memtx_tree_index_replace_multikey_one(struct memtx_tree_index *index,<br>  * delete operation is fault-tolerant.<br>  */<br> static void<br>-memtx_tree_index_replace_multikey_rollback(struct memtx_tree_index *index,<br>+memtx_tree_index_replace_multikey_rollback(struct memtx_tree_index<1> *index,<br>  struct tuple *new_tuple, struct tuple *replaced_tuple,<br>  int err_multikey_idx)<br> {<br>- struct memtx_tree_data data;<br>+ struct memtx_tree_data<1> data;<br>  if (replaced_tuple != NULL) {<br>  /* Restore replaced tuple index occurrences. */<br>  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);<br>@@ -798,7 +931,7 @@ memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,<br>  struct tuple *new_tuple, enum dup_replace_mode mode,<br>  struct tuple **result)<br> {<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>+ struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;<br>  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);<br>  *result = NULL;<br>  if (new_tuple != NULL) {<br>@@ -808,7 +941,7 @@ memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,<br>  for (; (uint32_t) multikey_idx < multikey_count;<br>  multikey_idx++) {<br>  bool is_multikey_conflict;<br>- struct memtx_tree_data replaced_data;<br>+ struct memtx_tree_data<1> replaced_data;<br>  err = memtx_tree_index_replace_multikey_one(index,<br>  old_tuple, new_tuple, mode,<br>  multikey_idx, &replaced_data,<br>@@ -833,7 +966,7 @@ memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,<br>  }<br>  }<br>  if (old_tuple != NULL) {<br>- struct memtx_tree_data data;<br>+ struct memtx_tree_data<1> data;<br>  data.tuple = old_tuple;<br>  uint32_t multikey_count =<br>  tuple_multikey_count(old_tuple, cmp_def);<br>@@ -865,7 +998,7 @@ struct func_key_undo {<br>  /** A link to organize entries in list. */<br>  struct rlist link;<br>  /** An inserted record copy. */<br>- struct memtx_tree_data key;<br>+ struct memtx_tree_data<1> key;<br> };<br> <br> /** Allocate a new func_key_undo on given region. */<br>@@ -888,7 +1021,7 @@ func_key_undo_new(struct region *region)<br>  * return a given index object in it's original state.<br>  */<br> static void<br>-memtx_tree_func_index_replace_rollback(struct memtx_tree_index *index,<br>+memtx_tree_func_index_replace_rollback(struct memtx_tree_index<1> *index,<br>  struct rlist *old_keys,<br>  struct rlist *new_keys)<br> {<br>@@ -919,7 +1052,7 @@ memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,<br>  struct tuple *new_tuple, enum dup_replace_mode mode,<br>  struct tuple **result)<br> {<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>+ struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;<br>  struct index_def *index_def = index->base.def;<br>  assert(index_def->key_def->for_func_index);<br> <br>@@ -952,7 +1085,7 @@ memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,<br>  undo->key.hint = (hint_t)key;<br>  rlist_add(&new_keys, &undo->link);<br>  bool is_multikey_conflict;<br>- struct memtx_tree_data old_data;<br>+ struct memtx_tree_data<1> old_data;<br>  old_data.tuple = NULL;<br>  err = memtx_tree_index_replace_multikey_one(index,<br>  old_tuple, new_tuple,<br>@@ -1015,7 +1148,7 @@ memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,<br>  if (key_list_iterator_create(&it, old_tuple, index_def, false,<br>  func_index_key_dummy_alloc) != 0)<br>  goto end;<br>- struct memtx_tree_data data, deleted_data;<br>+ struct memtx_tree_data<1> data, deleted_data;<br>  data.tuple = old_tuple;<br>  const char *key;<br>  while (key_list_iterator_next(&it, &key) == 0 && key != NULL) {<br>@@ -1040,11 +1173,13 @@ end:<br>  return rc;<br> }<br> <br>+template <int USE_HINT><br> static struct iterator *<br> memtx_tree_index_create_iterator(struct index *base, enum iterator_type type,<br>  const char *key, uint32_t part_count)<br> {<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)base;<br>  struct memtx_engine *memtx = (struct memtx_engine *)base->engine;<br>  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);<br> <br>@@ -1064,42 +1199,47 @@ memtx_tree_index_create_iterator(struct index *base, enum iterator_type type,<br>  key = NULL;<br>  }<br> <br>- struct tree_iterator *it = (struct tree_iterator *)<br>+ struct tree_iterator<USE_HINT> *it = (struct tree_iterator<USE_HINT> *)<br>  mempool_alloc(&memtx->iterator_pool);<br>  if (it == NULL) {<br>- diag_set(OutOfMemory, sizeof(struct tree_iterator),<br>+ diag_set(OutOfMemory, sizeof(struct tree_iterator<USE_HINT>),<br>  "memtx_tree_index", "iterator");<br>  return NULL;<br>  }<br>  iterator_create(&it->base, base);<br>  it->pool = &memtx->iterator_pool;<br>- it->base.next = tree_iterator_start;<br>- it->base.free = tree_iterator_free;<br>+ it->base.next = tree_iterator_start<USE_HINT>;<br>+ it->base.free = tree_iterator_free<USE_HINT>;<br>  it->type = type;<br>  it->key_data.key = key;<br>  it->key_data.part_count = part_count;<br>- it->key_data.hint = key_hint(key, part_count, cmp_def);<br>- it->tree_iterator = memtx_tree_invalid_iterator();<br>+ if (USE_HINT)<br>+ it->key_data.set_hint(key_hint(key, part_count, cmp_def));<br>+ invalidate_tree_iterator(&it->tree_iterator);<br>  it->current.tuple = NULL;<br>  return (struct iterator *)it;<br> }<br> <br>+template <int USE_HINT><br> static void<br> memtx_tree_index_begin_build(struct index *base)<br> {<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)base;<br>  assert(memtx_tree_size(&index->tree) == 0);<br>  (void)index;<br> }<br> <br>+template <int USE_HINT><br> static int<br> memtx_tree_index_reserve(struct index *base, uint32_t size_hint)<br> {<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)base;<br>  if (size_hint < index->build_array_alloc_size)<br>  return 0;<br>- struct memtx_tree_data *tmp =<br>- (struct memtx_tree_data *)<br>+ struct memtx_tree_data<USE_HINT> *tmp =<br>+ (struct memtx_tree_data<USE_HINT> *)<br>  realloc(index->build_array, size_hint * sizeof(*tmp));<br>  if (tmp == NULL) {<br>  diag_set(OutOfMemory, size_hint * sizeof(*tmp),<br>@@ -1111,14 +1251,15 @@ memtx_tree_index_reserve(struct index *base, uint32_t size_hint)<br>  return 0;<br> }<br> <br>+template <int USE_HINT><br> /** Initialize the next element of the index build_array. */<br> static int<br>-memtx_tree_index_build_array_append(struct memtx_tree_index *index,<br>+memtx_tree_index_build_array_append(struct memtx_tree_index<USE_HINT> *index,<br>  struct tuple *tuple, hint_t hint)<br> {<br>  if (index->build_array == NULL) {<br>  index->build_array =<br>- (struct memtx_tree_data *)malloc(MEMTX_EXTENT_SIZE);<br>+ (struct memtx_tree_data<USE_HINT> *)malloc(MEMTX_EXTENT_SIZE);<br>  if (index->build_array == NULL) {<br>  diag_set(OutOfMemory, MEMTX_EXTENT_SIZE,<br>  "memtx_tree_index", "build_next");<br>@@ -1131,8 +1272,8 @@ memtx_tree_index_build_array_append(struct memtx_tree_index *index,<br>  if (index->build_array_size == index->build_array_alloc_size) {<br>  index->build_array_alloc_size = index->build_array_alloc_size +<br>  DIV_ROUND_UP(index->build_array_alloc_size, 2);<br>- struct memtx_tree_data *tmp =<br>- (struct memtx_tree_data *)realloc(index->build_array,<br>+ struct memtx_tree_data<USE_HINT> *tmp =<br>+ (struct memtx_tree_data<USE_HINT> *)realloc(index->build_array,<br>  index->build_array_alloc_size * sizeof(*tmp));<br>  if (tmp == NULL) {<br>  diag_set(OutOfMemory, index->build_array_alloc_size *<br>@@ -1141,17 +1282,20 @@ memtx_tree_index_build_array_append(struct memtx_tree_index *index,<br>  }<br>  index->build_array = tmp;<br>  }<br>- struct memtx_tree_data *elem =<br>+ struct memtx_tree_data<USE_HINT> *elem =<br>  &index->build_array[index->build_array_size++];<br>  elem->tuple = tuple;<br>- elem->hint = hint;<br>+ if (USE_HINT)<br>+ elem->set_hint(hint);<br>  return 0;<br> }<br> <br>+template <int USE_HINT><br> static int<br> memtx_tree_index_build_next(struct index *base, struct tuple *tuple)<br> {<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)base;<br>  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);<br>  return memtx_tree_index_build_array_append(index, tuple,<br>  tuple_hint(tuple, cmp_def));<br>@@ -1160,7 +1304,7 @@ memtx_tree_index_build_next(struct index *base, struct tuple *tuple)<br> static int<br> memtx_tree_index_build_next_multikey(struct index *base, struct tuple *tuple)<br> {<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>+ struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;<br>  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);<br>  uint32_t multikey_count = tuple_multikey_count(tuple, cmp_def);<br>  for (uint32_t multikey_idx = 0; multikey_idx < multikey_count;<br>@@ -1175,7 +1319,7 @@ memtx_tree_index_build_next_multikey(struct index *base, struct tuple *tuple)<br> static int<br> memtx_tree_func_index_build_next(struct index *base, struct tuple *tuple)<br> {<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>+ struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;<br>  struct index_def *index_def = index->base.def;<br>  assert(index_def->key_def->for_func_index);<br> <br>@@ -1211,8 +1355,9 @@ error:<br>  * of equal tuples (in terms of index's cmp_def and have same<br>  * tuple pointer). The build_array is expected to be sorted.<br>  */<br>+template <int USE_HINT><br> static void<br>-memtx_tree_index_build_array_deduplicate(struct memtx_tree_index *index,<br>+memtx_tree_index_build_array_deduplicate(struct memtx_tree_index<USE_HINT> *index,<br>  void (*destroy)(struct tuple *tuple, const char *hint))<br> {<br>  if (index->build_array_size == 0)<br>@@ -1246,13 +1391,16 @@ memtx_tree_index_build_array_deduplicate(struct memtx_tree_index *index,<br>  index->build_array_size = w_idx + 1;<br> }<br> <br>+template <int USE_HINT><br> static void<br> memtx_tree_index_end_build(struct index *base)<br> {<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)base;<br>  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);<br>  qsort_arg(index->build_array, index->build_array_size,<br>- sizeof(index->build_array[0]), memtx_tree_qcompare, cmp_def);<br>+ sizeof(index->build_array[0]),<br>+ memtx_tree_qcompare<USE_HINT>, cmp_def);<br>  if (cmp_def->is_multikey) {<br>  /*<br>  * Multikey index may have equal(in terms of<br>@@ -1261,9 +1409,9 @@ memtx_tree_index_end_build(struct index *base)<br>  * the following memtx_tree_build assumes that<br>  * all keys are unique.<br>  */<br>- memtx_tree_index_build_array_deduplicate(index, NULL);<br>+ memtx_tree_index_build_array_deduplicate<USE_HINT>(index, NULL);<br>  } else if (cmp_def->for_func_index) {<br>- memtx_tree_index_build_array_deduplicate(index,<br>+ memtx_tree_index_build_array_deduplicate<USE_HINT>(index,<br>  tuple_chunk_delete);<br>  }<br>  memtx_tree_build(&index->tree, index->build_array,<br>@@ -1275,19 +1423,21 @@ memtx_tree_index_end_build(struct index *base)<br>  index->build_array_alloc_size = 0;<br> }<br> <br>+template <int USE_HINT><br> struct tree_snapshot_iterator {<br>  struct snapshot_iterator base;<br>- struct memtx_tree_index *index;<br>- struct memtx_tree_iterator tree_iterator;<br>+ struct memtx_tree_index<USE_HINT> *index;<br>+ memtx_tree_iterator_t<USE_HINT> tree_iterator;<br>  struct memtx_tx_snapshot_cleaner cleaner;<br> };<br> <br>+template <int USE_HINT><br> static void<br> tree_snapshot_iterator_free(struct snapshot_iterator *iterator)<br> {<br>- assert(iterator->free == tree_snapshot_iterator_free);<br>- struct tree_snapshot_iterator *it =<br>- (struct tree_snapshot_iterator *)iterator;<br>+ assert(iterator->free == &tree_snapshot_iterator_free<USE_HINT>);<br>+ struct tree_snapshot_iterator<USE_HINT> *it =<br>+ (struct tree_snapshot_iterator<USE_HINT> *)iterator;<br>  memtx_leave_delayed_free_mode((struct memtx_engine *)<br>  it->index->base.engine);<br>  memtx_tree_iterator_destroy(&it->index->tree, &it->tree_iterator);<br>@@ -1296,17 +1446,18 @@ tree_snapshot_iterator_free(struct snapshot_iterator *iterator)<br>  free(iterator);<br> }<br> <br>+template <int USE_HINT><br> static int<br> tree_snapshot_iterator_next(struct snapshot_iterator *iterator,<br>  const char **data, uint32_t *size)<br> {<br>- assert(iterator->free == tree_snapshot_iterator_free);<br>- struct tree_snapshot_iterator *it =<br>- (struct tree_snapshot_iterator *)iterator;<br>- struct memtx_tree *tree = &it->index->tree;<br>+ assert(iterator->free == &tree_snapshot_iterator_free<USE_HINT>);<br>+ struct tree_snapshot_iterator<USE_HINT> *it =<br>+ (struct tree_snapshot_iterator<USE_HINT> *)iterator;<br>+ memtx_tree_t<USE_HINT> *tree = &it->index->tree;<br> <br>  while (true) {<br>- struct memtx_tree_data *res =<br>+ struct memtx_tree_data<USE_HINT> *res =<br>  memtx_tree_iterator_get_elem(tree, &it->tree_iterator);<br> <br>  if (res == NULL) {<br>@@ -1333,14 +1484,18 @@ tree_snapshot_iterator_next(struct snapshot_iterator *iterator,<br>  * index modifications will not affect the iteration results.<br>  * Must be destroyed by iterator->free after usage.<br>  */<br>+template <int USE_HINT><br> static struct snapshot_iterator *<br> memtx_tree_index_create_snapshot_iterator(struct index *base)<br> {<br>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;<br>- struct tree_snapshot_iterator *it =<br>- (struct tree_snapshot_iterator *) calloc(1, sizeof(*it));<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)base;<br>+ struct tree_snapshot_iterator<USE_HINT> *it =<br>+ (struct tree_snapshot_iterator<USE_HINT> *)<br>+ calloc(1, sizeof(*it));<br>  if (it == NULL) {<br>- diag_set(OutOfMemory, sizeof(struct tree_snapshot_iterator),<br>+ diag_set(OutOfMemory,<br>+ sizeof(struct tree_snapshot_iterator<USE_HINT>),<br>  "memtx_tree_index", "create_snapshot_iterator");<br>  return NULL;<br>  }<br>@@ -1352,8 +1507,8 @@ memtx_tree_index_create_snapshot_iterator(struct index *base)<br>  return NULL;<br>  }<br> <br>- it->base.free = tree_snapshot_iterator_free;<br>- it->base.next = tree_snapshot_iterator_next;<br>+ it->base.free = tree_snapshot_iterator_free<USE_HINT>;<br>+ it->base.next = tree_snapshot_iterator_next<USE_HINT>;<br>  it->index = index;<br>  index_ref(base);<br>  it->tree_iterator = memtx_tree_iterator_first(&index->tree);<br>@@ -1362,94 +1517,124 @@ memtx_tree_index_create_snapshot_iterator(struct index *base)<br>  return (struct snapshot_iterator *) it;<br> }<br> <br>-static const struct index_vtab memtx_tree_index_vtab = {<br>- /* .destroy = */ memtx_tree_index_destroy,<br>+static const struct index_vtab memtx_tree_no_hint_index_vtab = {<br>+ /* .destroy = */ memtx_tree_index_destroy<0>,<br>  /* .commit_create = */ generic_index_commit_create,<br>  /* .abort_create = */ generic_index_abort_create,<br>  /* .commit_modify = */ generic_index_commit_modify,<br>  /* .commit_drop = */ generic_index_commit_drop,<br>- /* .update_def = */ memtx_tree_index_update_def,<br>+ /* .update_def = */ memtx_tree_index_update_def<0>,<br>  /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,<br>  /* .def_change_requires_rebuild = */<br>  memtx_index_def_change_requires_rebuild,<br>- /* .size = */ memtx_tree_index_size,<br>- /* .bsize = */ memtx_tree_index_bsize,<br>+ /* .size = */ memtx_tree_index_size<0>,<br>+ /* .bsize = */ memtx_tree_index_bsize<0>,<br>  /* .min = */ generic_index_min,<br>  /* .max = */ generic_index_max,<br>- /* .random = */ memtx_tree_index_random,<br>- /* .count = */ memtx_tree_index_count,<br>- /* .get = */ memtx_tree_index_get,<br>- /* .replace = */ memtx_tree_index_replace,<br>- /* .create_iterator = */ memtx_tree_index_create_iterator,<br>+ /* .random = */ memtx_tree_index_random<0>,<br>+ /* .count = */ memtx_tree_index_count<0>,<br>+ /* .get = */ memtx_tree_index_get<0>,<br>+ /* .replace = */ memtx_tree_index_replace<0>,<br>+ /* .create_iterator = */ memtx_tree_index_create_iterator<0>,<br>  /* .create_snapshot_iterator = */<br>- memtx_tree_index_create_snapshot_iterator,<br>+ memtx_tree_index_create_snapshot_iterator<0>,<br>  /* .stat = */ generic_index_stat,<br>  /* .compact = */ generic_index_compact,<br>  /* .reset_stat = */ generic_index_reset_stat,<br>- /* .begin_build = */ memtx_tree_index_begin_build,<br>- /* .reserve = */ memtx_tree_index_reserve,<br>- /* .build_next = */ memtx_tree_index_build_next,<br>- /* .end_build = */ memtx_tree_index_end_build,<br>+ /* .begin_build = */ memtx_tree_index_begin_build<0>,<br>+ /* .reserve = */ memtx_tree_index_reserve<0>,<br>+ /* .build_next = */ memtx_tree_index_build_next<0>,<br>+ /* .end_build = */ memtx_tree_index_end_build<0>,<br>+};<br>+<br>+static const struct index_vtab memtx_tree_use_hint_index_vtab = {<br>+ /* .destroy = */ memtx_tree_index_destroy<1>,<br>+ /* .commit_create = */ generic_index_commit_create,<br>+ /* .abort_create = */ generic_index_abort_create,<br>+ /* .commit_modify = */ generic_index_commit_modify,<br>+ /* .commit_drop = */ generic_index_commit_drop,<br>+ /* .update_def = */ memtx_tree_index_update_def<1>,<br>+ /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,<br>+ /* .def_change_requires_rebuild = */<br>+ memtx_index_def_change_requires_rebuild,<br>+ /* .size = */ memtx_tree_index_size<1>,<br>+ /* .bsize = */ memtx_tree_index_bsize<1>,<br>+ /* .min = */ generic_index_min,<br>+ /* .max = */ generic_index_max,<br>+ /* .random = */ memtx_tree_index_random<1>,<br>+ /* .count = */ memtx_tree_index_count<1>,<br>+ /* .get = */ memtx_tree_index_get<1>,<br>+ /* .replace = */ memtx_tree_index_replace<1>,<br>+ /* .create_iterator = */ memtx_tree_index_create_iterator<1>,<br>+ /* .create_snapshot_iterator = */<br>+ memtx_tree_index_create_snapshot_iterator<1>,<br>+ /* .stat = */ generic_index_stat,<br>+ /* .compact = */ generic_index_compact,<br>+ /* .reset_stat = */ generic_index_reset_stat,<br>+ /* .begin_build = */ memtx_tree_index_begin_build<1>,<br>+ /* .reserve = */ memtx_tree_index_reserve<1>,<br>+ /* .build_next = */ memtx_tree_index_build_next<1>,<br>+ /* .end_build = */ memtx_tree_index_end_build<1>,<br> };<br> <br> static const struct index_vtab memtx_tree_index_multikey_vtab = {<br>- /* .destroy = */ memtx_tree_index_destroy,<br>+ /* .destroy = */ memtx_tree_index_destroy<1>,<br>  /* .commit_create = */ generic_index_commit_create,<br>  /* .abort_create = */ generic_index_abort_create,<br>  /* .commit_modify = */ generic_index_commit_modify,<br>  /* .commit_drop = */ generic_index_commit_drop,<br>- /* .update_def = */ memtx_tree_index_update_def,<br>+ /* .update_def = */ memtx_tree_index_update_def<1>,<br>  /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,<br>  /* .def_change_requires_rebuild = */<br>  memtx_index_def_change_requires_rebuild,<br>- /* .size = */ memtx_tree_index_size,<br>- /* .bsize = */ memtx_tree_index_bsize,<br>+ /* .size = */ memtx_tree_index_size<1>,<br>+ /* .bsize = */ memtx_tree_index_bsize<1>,<br>  /* .min = */ generic_index_min,<br>  /* .max = */ generic_index_max,<br>- /* .random = */ memtx_tree_index_random,<br>- /* .count = */ memtx_tree_index_count,<br>- /* .get = */ memtx_tree_index_get,<br>+ /* .random = */ memtx_tree_index_random<1>,<br>+ /* .count = */ memtx_tree_index_count<1>,<br>+ /* .get = */ memtx_tree_index_get<1>,<br>  /* .replace = */ memtx_tree_index_replace_multikey,<br>- /* .create_iterator = */ memtx_tree_index_create_iterator,<br>+ /* .create_iterator = */ memtx_tree_index_create_iterator<1>,<br>  /* .create_snapshot_iterator = */<br>- memtx_tree_index_create_snapshot_iterator,<br>+ memtx_tree_index_create_snapshot_iterator<1>,<br>  /* .stat = */ generic_index_stat,<br>  /* .compact = */ generic_index_compact,<br>  /* .reset_stat = */ generic_index_reset_stat,<br>- /* .begin_build = */ memtx_tree_index_begin_build,<br>- /* .reserve = */ memtx_tree_index_reserve,<br>+ /* .begin_build = */ memtx_tree_index_begin_build<1>,<br>+ /* .reserve = */ memtx_tree_index_reserve<1>,<br>  /* .build_next = */ memtx_tree_index_build_next_multikey,<br>- /* .end_build = */ memtx_tree_index_end_build,<br>+ /* .end_build = */ memtx_tree_index_end_build<1>,<br> };<br> <br> static const struct index_vtab memtx_tree_func_index_vtab = {<br>- /* .destroy = */ memtx_tree_index_destroy,<br>+ /* .destroy = */ memtx_tree_index_destroy<1>,<br>  /* .commit_create = */ generic_index_commit_create,<br>  /* .abort_create = */ generic_index_abort_create,<br>  /* .commit_modify = */ generic_index_commit_modify,<br>  /* .commit_drop = */ generic_index_commit_drop,<br>- /* .update_def = */ memtx_tree_index_update_def,<br>+ /* .update_def = */ memtx_tree_index_update_def<1>,<br>  /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,<br>  /* .def_change_requires_rebuild = */<br>  memtx_index_def_change_requires_rebuild,<br>- /* .size = */ memtx_tree_index_size,<br>- /* .bsize = */ memtx_tree_index_bsize,<br>+ /* .size = */ memtx_tree_index_size<1>,<br>+ /* .bsize = */ memtx_tree_index_bsize<1>,<br>  /* .min = */ generic_index_min,<br>  /* .max = */ generic_index_max,<br>- /* .random = */ memtx_tree_index_random,<br>- /* .count = */ memtx_tree_index_count,<br>- /* .get = */ memtx_tree_index_get,<br>+ /* .random = */ memtx_tree_index_random<1>,<br>+ /* .count = */ memtx_tree_index_count<1>,<br>+ /* .get = */ memtx_tree_index_get<1>,<br>  /* .replace = */ memtx_tree_func_index_replace,<br>- /* .create_iterator = */ memtx_tree_index_create_iterator,<br>+ /* .create_iterator = */ memtx_tree_index_create_iterator<1>,<br>  /* .create_snapshot_iterator = */<br>- memtx_tree_index_create_snapshot_iterator,<br>+ memtx_tree_index_create_snapshot_iterator<1>,<br>  /* .stat = */ generic_index_stat,<br>  /* .compact = */ generic_index_compact,<br>  /* .reset_stat = */ generic_index_reset_stat,<br>- /* .begin_build = */ memtx_tree_index_begin_build,<br>- /* .reserve = */ memtx_tree_index_reserve,<br>+ /* .begin_build = */ memtx_tree_index_begin_build<1>,<br>+ /* .reserve = */ memtx_tree_index_reserve<1>,<br>  /* .build_next = */ memtx_tree_func_index_build_next,<br>- /* .end_build = */ memtx_tree_index_end_build,<br>+ /* .end_build = */ memtx_tree_index_end_build<1>,<br> };<br> <br> /**<br>@@ -1459,7 +1644,7 @@ static const struct index_vtab memtx_tree_func_index_vtab = {<br>  * key defintion is not completely initialized at that moment).<br>  */<br> static const struct index_vtab memtx_tree_disabled_index_vtab = {<br>- /* .destroy = */ memtx_tree_index_destroy,<br>+ /* .destroy = */ memtx_tree_index_destroy<1>,<br>  /* .commit_create = */ generic_index_commit_create,<br>  /* .abort_create = */ generic_index_abort_create,<br>  /* .commit_modify = */ generic_index_commit_modify,<br>@@ -1488,27 +1673,19 @@ static const struct index_vtab memtx_tree_disabled_index_vtab = {<br>  /* .end_build = */ generic_index_end_build,<br> };<br> <br>-struct index *<br>-memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)<br>+template <int USE_HINT><br>+static struct index *<br>+memtx_tree_index_new_tpl(struct memtx_engine *memtx, struct index_def *def,<br>+ const struct index_vtab *vtab)<br> {<br>- struct memtx_tree_index *index =<br>- (struct memtx_tree_index *)calloc(1, sizeof(*index));<br>+ struct memtx_tree_index<USE_HINT> *index =<br>+ (struct memtx_tree_index<USE_HINT> *)<br>+ calloc(1, sizeof(*index));<br>  if (index == NULL) {<br>  diag_set(OutOfMemory, sizeof(*index),<br>  "malloc", "struct memtx_tree_index");<br>  return NULL;<br>  }<br>- const struct index_vtab *vtab;<br>- if (def->key_def->for_func_index) {<br>- if (def->key_def->func_index_func == NULL)<br>- vtab = &memtx_tree_disabled_index_vtab;<br>- else<br>- vtab = &memtx_tree_func_index_vtab;<br>- } else if (def->key_def->is_multikey) {<br>- vtab = &memtx_tree_index_multikey_vtab;<br>- } else {<br>- vtab = &memtx_tree_index_vtab;<br>- }<br>  if (index_create(&index->base, (struct engine *)memtx,<br>  vtab, def) != 0) {<br>  free(index);<br>@@ -1524,3 +1701,23 @@ memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)<br>  memtx_index_extent_free, memtx);<br>  return &index->base;<br> }<br>+<br>+struct index *<br>+memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)<br>+{<br>+ const struct index_vtab *vtab;<br>+ if (def->key_def->for_func_index) {<br>+ if (def->key_def->func_index_func == NULL)<br>+ vtab = &memtx_tree_disabled_index_vtab;<br>+ else<br>+ vtab = &memtx_tree_func_index_vtab;<br>+ } else if (def->key_def->is_multikey) {<br>+ vtab = &memtx_tree_index_multikey_vtab;<br>+ } else if (def->opts.hint) {<br>+ vtab = &memtx_tree_use_hint_index_vtab;<br>+ } else {<br>+ vtab = &memtx_tree_no_hint_index_vtab;<br>+ return memtx_tree_index_new_tpl<0>(memtx, def, vtab);<br>+ }<br>+ return memtx_tree_index_new_tpl<1>(memtx, def, vtab);<br>+}<br>diff --git a/src/lib/salad/bps_tree.h b/src/lib/salad/bps_tree.h<br>index ef5ae3d..0bb803a 100644<br>--- a/src/lib/salad/bps_tree.h<br>+++ b/src/lib/salad/bps_tree.h<br>@@ -168,6 +168,17 @@<br> <br> /* {{{ BPS-tree interface settings */<br> /**<br>+ * Optional namespace for structs and functions.<br>+ * Is set, struct and functions will have BPS_TREE_NAMESPACE:: prefix.<br>+ * For example one can #define BPS_TREE_NAMESPACE my along with<br>+ * #define BPS_TREE_NAME _test, and use then<br>+ * struct my::bps_tree_test my_tree;<br>+ * my::bps_tree_test_create(&my_tree, ...);<br>+ *<br>+ * #define BPS_TREE_NAMESPACE<br>+ */<br>+<br>+/**<br>  * Custom name for structs and functions.<br>  * Struct and functions will have bps_tree##BPS_TREE_NAME name or prefix.<br>  * For example one can #define BPS_TREE_NAME _test, and use then<br>@@ -300,6 +311,10 @@<br> <br> /* }}} */<br> <br>+#ifdef BPS_TREE_NAMESPACE<br>+namespace BPS_TREE_NAMESPACE {<br>+#endif<br>+<br> /* {{{ BPS-tree internal settings */<br> typedef int16_t bps_tree_pos_t;<br> typedef uint32_t bps_tree_block_id_t;<br>@@ -6188,3 +6203,7 @@ bps_tree_debug_check_internal_functions(bool assertme)<br> #undef bps_tree_debug_check_insert_and_move_to_left_inner<br> <br> /* }}} */<br>+<br>+#ifdef BPS_TREE_NAMESPACE<br>+} /* namespace BPS_TREE_NAMESPACE { */<br>+#endif<br>diff --git a/test/box/alter.result b/test/box/alter.result<br>index 237c2d8..5b412a7 100644<br>--- a/test/box/alter.result<br>+++ b/test/box/alter.result<br>@@ -680,8 +680,9 @@ s.index.pk<br>   - type: unsigned<br>     is_nullable: false<br>     fieldno: 2<br>- type: TREE<br>+ hint: true<br>   id: 0<br>+ type: TREE<br>   space_id: 731<br>   name: pk<br> ...<br>@@ -710,9 +711,10 @@ s.index.pk<br>   - type: unsigned<br>     is_nullable: false<br>     fieldno: 1<br>- space_id: 731<br>+ hint: true<br>   id: 0<br>   type: TREE<br>+ space_id: 731<br>   name: pk<br> ...<br> s.index.secondary<br>@@ -722,8 +724,9 @@ s.index.secondary<br>   - type: unsigned<br>     is_nullable: false<br>     fieldno: 2<br>- type: TREE<br>+ hint: true<br>   id: 1<br>+ type: TREE<br>   space_id: 731<br>   name: secondary<br> ...<br>@@ -1559,3 +1562,97 @@ assert(err:match('does not exist') ~= nil)<br> ---<br> - true<br> ...<br>+-- hint<br>+s = box.schema.create_space('test');<br>+---<br>+...<br>+s:create_index('test1', {type='tree', parts={{1, 'uint'}}}).hint<br>+---<br>+- true<br>+...<br>+s:create_index('test2', {type='tree', parts={{2, 'uint'}}, hint = true}).hint<br>+---<br>+- true<br>+...<br>+s:create_index('test3', {type='tree', parts={{3, 'uint'}}, hint = false}).hint<br>+---<br>+- false<br>+...<br>+s:create_index('test4', {type='tree', parts={{4, 'string'}}, hint = false}).hint<br>+---<br>+- false<br>+...<br>+s.index.test1.hint<br>+---<br>+- true<br>+...<br>+s.index.test2.hint<br>+---<br>+- true<br>+...<br>+s.index.test3.hint<br>+---<br>+- false<br>+...<br>+s.index.test4.hint<br>+---<br>+- false<br>+...<br>+N = 1000 box.begin() for i = 1,N do s:replace{i, i, i, '' .. i} end box.commit()<br>+---<br>+...<br>+s.index.test1:bsize() == s.index.test2:bsize()<br>+---<br>+- true<br>+...<br>+s.index.test1:bsize() > s.index.test3:bsize()<br>+---<br>+- true<br>+...<br>+s.index.test1:bsize() > s.index.test4:bsize()<br>+---<br>+- true<br>+...<br>+s.index.test1:alter{hint=false}<br>+---<br>+...<br>+s.index.test2:alter{hint=true}<br>+---<br>+...<br>+s.index.test3:alter{name='test33', hint=false}<br>+---<br>+...<br>+s.index.test4:alter{hint=true}<br>+---<br>+...<br>+s.index.test1.hint<br>+---<br>+- false<br>+...<br>+s.index.test2.hint<br>+---<br>+- true<br>+...<br>+s.index.test33.hint<br>+---<br>+- false<br>+...<br>+s.index.test4.hint<br>+---<br>+- true<br>+...<br>+s.index.test1:bsize() < s.index.test2:bsize()<br>+---<br>+- true<br>+...<br>+s.index.test1:bsize() == s.index.test33:bsize()<br>+---<br>+- true<br>+...<br>+s.index.test1:bsize() < s.index.test4:bsize()<br>+---<br>+- true<br>+...<br>+s:drop()<br>+---<br>+...<br>diff --git a/test/box/alter.test.lua b/test/box/alter.test.lua<br>index abd08e2..2114186 100644<br>--- a/test/box/alter.test.lua<br>+++ b/test/box/alter.test.lua<br>@@ -620,3 +620,37 @@ s:drop()<br> s:alter({})<br> ok, err = pcall(box.schema.space.alter, s.id, {})<br> assert(err:match('does not exist') ~= nil)<br>+<br>+-- hint<br>+s = box.schema.create_space('test');<br>+s:create_index('test1', {type='tree', parts={{1, 'uint'}}}).hint<br>+s:create_index('test2', {type='tree', parts={{2, 'uint'}}, hint = true}).hint<br>+s:create_index('test3', {type='tree', parts={{3, 'uint'}}, hint = false}).hint<br>+s:create_index('test4', {type='tree', parts={{4, 'string'}}, hint = false}).hint<br>+<br>+s.index.test1.hint<br>+s.index.test2.hint<br>+s.index.test3.hint<br>+s.index.test4.hint<br>+<br>+N = 1000 box.begin() for i = 1,N do s:replace{i, i, i, '' .. i} end box.commit()<br>+<br>+s.index.test1:bsize() == s.index.test2:bsize()<br>+s.index.test1:bsize() > s.index.test3:bsize()<br>+s.index.test1:bsize() > s.index.test4:bsize()<br>+<br>+s.index.test1:alter{hint=false}<br>+s.index.test2:alter{hint=true}<br>+s.index.test3:alter{name='test33', hint=false}<br>+s.index.test4:alter{hint=true}<br>+<br>+s.index.test1.hint<br>+s.index.test2.hint<br>+s.index.test33.hint<br>+s.index.test4.hint<br>+<br>+s.index.test1:bsize() < s.index.test2:bsize()<br>+s.index.test1:bsize() == s.index.test33:bsize()<br>+s.index.test1:bsize() < s.index.test4:bsize()<br>+<br>+s:drop()<br>\ No newline at end of file<br>diff --git a/test/box/errinj.result b/test/box/errinj.result<br>index 613d22c..b8c2476 100644<br>--- a/test/box/errinj.result<br>+++ b/test/box/errinj.result<br>@@ -1774,9 +1774,10 @@ rtreespace:create_index('pk', {if_not_exists = true})<br>   - type: unsigned<br>     is_nullable: false<br>     fieldno: 1<br>+ hint: true<br>   id: 0<br>- space_id: 512<br>   type: TREE<br>+ space_id: 512<br>   name: pk<br> ...<br> rtreespace:create_index('target', {type='rtree', dimension = 3, parts={2, 'array'},unique = false, if_not_exists = true,})<br>diff --git a/test/box/tree_pk.result b/test/box/tree_pk.result<br>index df3c78b..18cb607 100644<br>--- a/test/box/tree_pk.result<br>+++ b/test/box/tree_pk.result<br>@@ -852,3 +852,317 @@ box.internal.collation.drop('test')<br> box.internal.collation.drop('test-ci')<br> ---<br> ...<br>+-- hints<br>+s = box.schema.space.create('test')<br>+---<br>+...<br>+s:create_index('test', {type = 'tree', hint = 'true'} )<br>+---<br>+- error: Illegal parameters, options parameter 'hint' should be of type boolean<br>+...<br>+s:create_index('test', {type = 'hash', hint = true} )<br>+---<br>+- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable<br>+ with memtx tree index'<br>+...<br>+s:create_index('test', {type = 'hash'}):alter({hint = true})<br>+---<br>+- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable<br>+ with memtx tree index'<br>+...<br>+s:create_index('multikey', {hint = true, parts = {{2, 'int', path = '[*]'}}})<br>+---<br>+- error: 'Can''t create or modify index ''multikey'' in space ''test'': multikey index<br>+ can''t use hints'<br>+...<br>+s:create_index('multikey', {parts = {{2, 'int', path = '[*]'}}}):alter({hint = true})<br>+---<br>+- error: 'Can''t create or modify index ''multikey'' in space ''test'': multikey index<br>+ can''t use hints'<br>+...<br>+lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]]<br>+---<br>+...<br>+box.schema.func.create('s', {body = lua_code, is_deterministic = true, is_sandboxed = true})<br>+---<br>+...<br>+s:create_index('func', {hint = true, func = box.func.s.id, parts = {{1, 'unsigned'}}})<br>+---<br>+- error: 'Can''t create or modify index ''func'' in space ''test'': functional index<br>+ can''t use hints'<br>+...<br>+s:drop()<br>+---<br>+...<br>+s = box.schema.space.create('test', {engine = 'vinyl'})<br>+---<br>+...<br>+s:create_index('test', {type = 'tree', hint = true} )<br>+---<br>+- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable<br>+ with memtx tree index'<br>+...<br>+s:drop()<br>+---<br>+...<br>+-- numeric hints<br>+s1 = box.schema.space.create('test1')<br>+---<br>+...<br>+s1:create_index('test', {type = 'tree', hint = false}).hint<br>+---<br>+- false<br>+...<br>+s2 = box.schema.space.create('test2')<br>+---<br>+...<br>+s2:create_index('test', {type = 'tree'}).hint<br>+---<br>+- true<br>+...<br>+N = 2000<br>+---<br>+...<br>+box.begin() for i = 1,N do local v = math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()<br>+---<br>+...<br>+s1:count() == s2:count()<br>+---<br>+- true<br>+...<br>+good = true r1 = s1:select{} r2 = s2:select{}<br>+---<br>+...<br>+if #r1 ~= #r2 then good = false end<br>+---<br>+...<br>+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end<br>+---<br>+...<br>+good<br>+---<br>+- true<br>+...<br>+r1 = nil r2 = nil<br>+---<br>+...<br>+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end<br>+---<br>+...<br>+for i = 1, N * 10 do if diff(s1:get{i}, s2:get{i}) then good = false end end<br>+---<br>+...<br>+good<br>+---<br>+- true<br>+...<br>+s1.index[0]:bsize() < s2.index[0]:bsize()<br>+---<br>+- true<br>+...<br>+s1.index[0]:alter{hint=true}<br>+---<br>+...<br>+s1.index[0]:bsize() == s2.index[0]:bsize()<br>+---<br>+- true<br>+...<br>+s2.index[0]:alter{hint=false}<br>+---<br>+...<br>+s1.index[0]:bsize() > s2.index[0]:bsize()<br>+---<br>+- true<br>+...<br>+s1.index[0]:alter{hint=false}<br>+---<br>+...<br>+s1.index[0]:bsize() == s2.index[0]:bsize()<br>+---<br>+- true<br>+...<br>+s1:drop()<br>+---<br>+...<br>+s2:drop()<br>+---<br>+...<br>+-- string hints<br>+s1 = box.schema.space.create('test1')<br>+---<br>+...<br>+s1:create_index('test', {type = 'tree', parts = {1, 'str'}, hint = false}).hint<br>+---<br>+- false<br>+...<br>+s2 = box.schema.space.create('test2')<br>+---<br>+...<br>+s2:create_index('test', {type = 'tree', parts = {1, 'str'}}).hint<br>+---<br>+- true<br>+...<br>+N = 1000<br>+---<br>+...<br>+box.begin() for i = 1,N do local v = ''..math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()<br>+---<br>+...<br>+s1:count() == s2:count()<br>+---<br>+- true<br>+...<br>+good = true r1 = s1:select{} r2 = s2:select{}<br>+---<br>+...<br>+if #r1 ~= #r2 then good = false end<br>+---<br>+...<br>+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end<br>+---<br>+...<br>+good<br>+---<br>+- true<br>+...<br>+r1 = nil r2 = nil<br>+---<br>+...<br>+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end<br>+---<br>+...<br>+for i = 1, N * 10 do v = ''..i if diff(s1:get{v}, s2:get{v}) then good = false end end<br>+---<br>+...<br>+good<br>+---<br>+- true<br>+...<br>+s1.index[0]:bsize() < s2.index[0]:bsize()<br>+---<br>+- true<br>+...<br>+s1.index[0]:alter{hint=true}<br>+---<br>+...<br>+s1.index[0]:bsize() == s2.index[0]:bsize()<br>+---<br>+- true<br>+...<br>+s2.index[0]:alter{hint=false}<br>+---<br>+...<br>+s1.index[0]:bsize() > s2.index[0]:bsize()<br>+---<br>+- true<br>+...<br>+s1.index[0]:alter{hint=false}<br>+---<br>+...<br>+s1.index[0]:bsize() == s2.index[0]:bsize()<br>+---<br>+- true<br>+...<br>+s1:drop()<br>+---<br>+...<br>+s2:drop()<br>+---<br>+...<br>+-- string with collation hints<br>+s1 = box.schema.space.create('test1')<br>+---<br>+...<br>+s1:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}, hint = false}).hint<br>+---<br>+- false<br>+...<br>+s2 = box.schema.space.create('test2')<br>+---<br>+...<br>+s2:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}}).hint<br>+---<br>+- true<br>+...<br>+N = 1000<br>+---<br>+...<br>+syms = {'a', 'B', 'c', 'D', 'ж', 'Ё', '~', '1', '%', '2'}<br>+---<br>+...<br>+syms_size = #syms<br>+---<br>+...<br>+len = 20<br>+---<br>+...<br>+vals = {}<br>+---<br>+...<br>+for i = 1,2*N do str = '' for j=1,len do str = str .. syms[math.random(syms_size)] end vals[i] = str end<br>+---<br>+...<br>+for i = 1,N do local v = vals[i] s1:replace{v} s2:replace{v} end<br>+---<br>+...<br>+s1:count() == s2:count()<br>+---<br>+- true<br>+...<br>+good = true r1 = s1:select{} r2 = s2:select{}<br>+---<br>+...<br>+if #r1 ~= #r2 then good = false end<br>+---<br>+...<br>+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end<br>+---<br>+...<br>+good<br>+---<br>+- true<br>+...<br>+r1 = nil r2 = nil<br>+---<br>+...<br>+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end<br>+---<br>+...<br>+for i = 1, N * 2 do v = vals[i] if diff(s1:get{v}, s2:get{v}) then good = false end end<br>+---<br>+...<br>+good<br>+---<br>+- true<br>+...<br>+s1.index[0]:bsize() < s2.index[0]:bsize()<br>+---<br>+- true<br>+...<br>+s1.index[0]:alter{hint=true}<br>+---<br>+...<br>+s1.index[0]:bsize() == s2.index[0]:bsize()<br>+---<br>+- true<br>+...<br>+s2.index[0]:alter{hint=false}<br>+---<br>+...<br>+s1.index[0]:bsize() > s2.index[0]:bsize()<br>+---<br>+- true<br>+...<br>+s1.index[0]:alter{hint=false}<br>+---<br>+...<br>+s1.index[0]:bsize() == s2.index[0]:bsize()<br>+---<br>+- true<br>+...<br>+s1:drop()<br>+---<br>+...<br>+s2:drop()<br>+---<br>+...<br>diff --git a/test/box/tree_pk.test.lua b/test/box/tree_pk.test.lua<br>index 1190ab4..2f22ed9 100644<br>--- a/test/box/tree_pk.test.lua<br>+++ b/test/box/tree_pk.test.lua<br>@@ -301,3 +301,118 @@ s:drop()<br> <br> box.internal.collation.drop('test')<br> box.internal.collation.drop('test-ci')<br>+<br>+-- hints<br>+s = box.schema.space.create('test')<br>+s:create_index('test', {type = 'tree', hint = 'true'} )<br>+s:create_index('test', {type = 'hash', hint = true} )<br>+s:create_index('test', {type = 'hash'}):alter({hint = true})<br>+s:create_index('multikey', {hint = true, parts = {{2, 'int', path = '[*]'}}})<br>+s:create_index('multikey', {parts = {{2, 'int', path = '[*]'}}}):alter({hint = true})<br>+lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]]<br>+box.schema.func.create('s', {body = lua_code, is_deterministic = true, is_sandboxed = true})<br>+s:create_index('func', {hint = true, func = box.func.s.id, parts = {{1, 'unsigned'}}})<br>+s:drop()<br>+<br>+s = box.schema.space.create('test', {engine = 'vinyl'})<br>+s:create_index('test', {type = 'tree', hint = true} )<br>+s:drop()<br>+<br>+-- numeric hints<br>+s1 = box.schema.space.create('test1')<br>+s1:create_index('test', {type = 'tree', hint = false}).hint<br>+s2 = box.schema.space.create('test2')<br>+s2:create_index('test', {type = 'tree'}).hint<br>+<br>+N = 2000<br>+box.begin() for i = 1,N do local v = math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()<br>+s1:count() == s2:count()<br>+<br>+good = true r1 = s1:select{} r2 = s2:select{}<br>+if #r1 ~= #r2 then good = false end<br>+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end<br>+good<br>+r1 = nil r2 = nil<br>+<br>+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end<br>+for i = 1, N * 10 do if diff(s1:get{i}, s2:get{i}) then good = false end end<br>+good<br>+<br>+s1.index[0]:bsize() < s2.index[0]:bsize()<br>+s1.index[0]:alter{hint=true}<br>+s1.index[0]:bsize() == s2.index[0]:bsize()<br>+s2.index[0]:alter{hint=false}<br>+s1.index[0]:bsize() > s2.index[0]:bsize()<br>+s1.index[0]:alter{hint=false}<br>+s1.index[0]:bsize() == s2.index[0]:bsize()<br>+<br>+s1:drop()<br>+s2:drop()<br>+<br>+-- string hints<br>+s1 = box.schema.space.create('test1')<br>+s1:create_index('test', {type = 'tree', parts = {1, 'str'}, hint = false}).hint<br>+s2 = box.schema.space.create('test2')<br>+s2:create_index('test', {type = 'tree', parts = {1, 'str'}}).hint<br>+<br>+N = 1000<br>+box.begin() for i = 1,N do local v = ''..math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()<br>+s1:count() == s2:count()<br>+<br>+good = true r1 = s1:select{} r2 = s2:select{}<br>+if #r1 ~= #r2 then good = false end<br>+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end<br>+good<br>+r1 = nil r2 = nil<br>+<br>+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end<br>+for i = 1, N * 10 do v = ''..i if diff(s1:get{v}, s2:get{v}) then good = false end end<br>+good<br>+<br>+s1.index[0]:bsize() < s2.index[0]:bsize()<br>+s1.index[0]:alter{hint=true}<br>+s1.index[0]:bsize() == s2.index[0]:bsize()<br>+s2.index[0]:alter{hint=false}<br>+s1.index[0]:bsize() > s2.index[0]:bsize()<br>+s1.index[0]:alter{hint=false}<br>+s1.index[0]:bsize() == s2.index[0]:bsize()<br>+<br>+s1:drop()<br>+s2:drop()<br>+<br>+-- string with collation hints<br>+s1 = box.schema.space.create('test1')<br>+s1:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}, hint = false}).hint<br>+s2 = box.schema.space.create('test2')<br>+s2:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}}).hint<br>+<br>+N = 1000<br>+syms = {'a', 'B', 'c', 'D', 'ж', 'Ё', '~', '1', '%', '2'}<br>+syms_size = #syms<br>+len = 20<br>+vals = {}<br>+for i = 1,2*N do str = '' for j=1,len do str = str .. syms[math.random(syms_size)] end vals[i] = str end<br>+<br>+for i = 1,N do local v = vals[i] s1:replace{v} s2:replace{v} end<br>+s1:count() == s2:count()<br>+<br>+good = true r1 = s1:select{} r2 = s2:select{}<br>+if #r1 ~= #r2 then good = false end<br>+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end<br>+good<br>+r1 = nil r2 = nil<br>+<br>+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end<br>+for i = 1, N * 2 do v = vals[i] if diff(s1:get{v}, s2:get{v}) then good = false end end<br>+good<br>+<br>+s1.index[0]:bsize() < s2.index[0]:bsize()<br>+s1.index[0]:alter{hint=true}<br>+s1.index[0]:bsize() == s2.index[0]:bsize()<br>+s2.index[0]:alter{hint=false}<br>+s1.index[0]:bsize() > s2.index[0]:bsize()<br>+s1.index[0]:alter{hint=false}<br>+s1.index[0]:bsize() == s2.index[0]:bsize()<br>+<br>+s1:drop()<br>+s2:drop()<br>diff --git a/test/box/tree_pk_multipart.result b/test/box/tree_pk_multipart.result<br>index 93219f6..3f6bb94 100644<br>--- a/test/box/tree_pk_multipart.result<br>+++ b/test/box/tree_pk_multipart.result<br>@@ -542,3 +542,156 @@ space:drop()<br> space = nil<br> ---<br> ...<br>+-- hints<br>+test_run:cmd("setopt delimiter ';'")<br>+---<br>+- true<br>+...<br>+function equal(res1, res2)<br>+ if #res1 ~= #res2 then<br>+ return false<br>+ end<br>+ for k,v in pairs(res1) do<br>+ if res2[k][1] ~= v[1] or res2[k][2] ~= v[2] then<br>+ return false<br>+ end<br>+ end<br>+ return true<br>+end<br>+test_run:cmd("setopt delimiter ''");<br>+---<br>+...<br>+-- num num<br>+N1 = 100<br>+---<br>+...<br>+t1 = {}<br>+---<br>+...<br>+for i = 1,N1*2 do t1[i] = math.random(10000) * 10000 + i end<br>+---<br>+...<br>+N2 = 5<br>+---<br>+...<br>+t2 = {}<br>+---<br>+...<br>+for i = 1,N2*2 do t2[i] = math.random(1000000) end<br>+---<br>+...<br>+s1 = box.schema.space.create('test1')<br>+---<br>+...<br>+s1:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = false}).hint<br>+---<br>+- false<br>+...<br>+s2 = box.schema.space.create('test2')<br>+---<br>+...<br>+s2:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = true}).hint<br>+---<br>+- true<br>+...<br>+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end<br>+---<br>+...<br>+s1:count() == s2:count()<br>+---<br>+- true<br>+...<br>+equal(s1:select{}, s2:select{})<br>+---<br>+- true<br>+...<br>+good = true<br>+---<br>+...<br>+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end<br>+---<br>+...<br>+good<br>+---<br>+- true<br>+...<br>+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end<br>+---<br>+...<br>+good<br>+---<br>+- true<br>+...<br>+s1:drop()<br>+---<br>+...<br>+s2:drop()<br>+---<br>+...<br>+-- str num<br>+N1 = 100<br>+---<br>+...<br>+t1 = {}<br>+---<br>+...<br>+for i = 1,N1*2 do t1[i] = ''..(math.random(10000) * 10000 + i) end<br>+---<br>+...<br>+N2 = 5<br>+---<br>+...<br>+t2 = {}<br>+---<br>+...<br>+for i = 1,N2*2 do t2[i] = math.random(1000000) end<br>+---<br>+...<br>+s1 = box.schema.space.create('test1')<br>+---<br>+...<br>+s1:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = false}).hint<br>+---<br>+- false<br>+...<br>+s2 = box.schema.space.create('test2')<br>+---<br>+...<br>+s2:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = true}).hint<br>+---<br>+- true<br>+...<br>+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end<br>+---<br>+...<br>+s1:count() == s2:count()<br>+---<br>+- true<br>+...<br>+equal(s1:select{}, s2:select{})<br>+---<br>+- true<br>+...<br>+good = true<br>+---<br>+...<br>+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end<br>+---<br>+...<br>+good<br>+---<br>+- true<br>+...<br>+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end<br>+---<br>+...<br>+good<br>+---<br>+- true<br>+...<br>+s1:drop()<br>+---<br>+...<br>+s2:drop()<br>+---<br>+...<br>diff --git a/test/box/tree_pk_multipart.test.lua b/test/box/tree_pk_multipart.test.lua<br>index 7d3405f..cba3a98 100644<br>--- a/test/box/tree_pk_multipart.test.lua<br>+++ b/test/box/tree_pk_multipart.test.lua<br>@@ -194,3 +194,67 @@ test_run:cmd("setopt delimiter ''");<br> space:drop()<br> <br> space = nil<br>+<br>+<br>+-- hints<br>+test_run:cmd("setopt delimiter ';'")<br>+function equal(res1, res2)<br>+ if #res1 ~= #res2 then<br>+ return false<br>+ end<br>+ for k,v in pairs(res1) do<br>+ if res2[k][1] ~= v[1] or res2[k][2] ~= v[2] then<br>+ return false<br>+ end<br>+ end<br>+ return true<br>+end<br>+test_run:cmd("setopt delimiter ''");<br>+<br>+-- num num<br>+N1 = 100<br>+t1 = {}<br>+for i = 1,N1*2 do t1[i] = math.random(10000) * 10000 + i end<br>+N2 = 5<br>+t2 = {}<br>+for i = 1,N2*2 do t2[i] = math.random(1000000) end<br>+<br>+s1 = box.schema.space.create('test1')<br>+s1:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = false}).hint<br>+s2 = box.schema.space.create('test2')<br>+s2:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = true}).hint<br>+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end<br>+s1:count() == s2:count()<br>+equal(s1:select{}, s2:select{})<br>+good = true<br>+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end<br>+good<br>+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end<br>+good<br>+<br>+s1:drop()<br>+s2:drop()<br>+<br>+-- str num<br>+N1 = 100<br>+t1 = {}<br>+for i = 1,N1*2 do t1[i] = ''..(math.random(10000) * 10000 + i) end<br>+N2 = 5<br>+t2 = {}<br>+for i = 1,N2*2 do t2[i] = math.random(1000000) end<br>+<br>+s1 = box.schema.space.create('test1')<br>+s1:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = false}).hint<br>+s2 = box.schema.space.create('test2')<br>+s2:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = true}).hint<br>+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end<br>+s1:count() == s2:count()<br>+equal(s1:select{}, s2:select{})<br>+good = true<br>+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end<br>+good<br>+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end<br>+good<br>+<br>+s1:drop()<br>+s2:drop()<br>--<br>2.7.4<br> </div></div></div></div></blockquote><br><div data-signature-widget="container"><div data-signature-widget="content"><div>--<br>Ilya Kosarev</div></div></div></div></BODY></HTML>