From 96fa495beadfbc4d837c344859c1e59fb4f73fc6 Mon Sep 17 00:00:00 2001
From: Ilya Verbin <iverbin@tarantool.org>
Date: Thu, 3 Aug 2023 17:44:19 +0300
Subject: [PATCH] box: move index_opts::hint check from Lua to
 space_check_index_def

The checks in box.schema.index.create() and box.schema.index.alter()
were case sensitive, also it was possible to insert incorrect index
options directly into `box.space._index`. Fixed by adding checks
to memtx_space_check_index_def() and vinyl_space_check_index_def().

Closes #8937

NO_DOC=bugfix

(cherry picked from commit 4e25384bca182d977ff2cd7326c11bf4ad0fa9db)
---
 ...ndex-doesnt-accept-true-option-for-hint.md |   4 +
 src/box/alter.cc                              |  11 ++
 src/box/index_def.c                           |  25 ++++-
 src/box/index_def.h                           |   9 +-
 src/box/lua/schema.lua                        |  11 --
 src/box/lua/space.cc                          |   2 +-
 src/box/memtx_space.c                         |  13 +++
 src/box/memtx_tree.cc                         |   2 +-
 src/box/vinyl.c                               |  11 ++
 .../gh_8937_data/00000000000000000004.snap    | Bin 0 -> 5108 bytes
 .../gh_8937_data/00000000000000000004.vylog   | Bin 0 -> 191 bytes
 test/box-luatest/gh_8937_data/gen.lua         |  14 +++
 ...h_8937_recover_wrong_hint_options_test.lua |  22 ++++
 test/box/tree_pk.result                       |  94 ++++++++++++++++--
 test/box/tree_pk.test.lua                     |  31 +++++-
 15 files changed, 220 insertions(+), 29 deletions(-)
 create mode 100644 changelogs/unreleased/gh-8937-memtx-tree-index-doesnt-accept-true-option-for-hint.md
 create mode 100644 test/box-luatest/gh_8937_data/00000000000000000004.snap
 create mode 100644 test/box-luatest/gh_8937_data/00000000000000000004.vylog
 create mode 100644 test/box-luatest/gh_8937_data/gen.lua
 create mode 100644 test/box-luatest/gh_8937_recover_wrong_hint_options_test.lua

diff --git a/changelogs/unreleased/gh-8937-memtx-tree-index-doesnt-accept-true-option-for-hint.md b/changelogs/unreleased/gh-8937-memtx-tree-index-doesnt-accept-true-option-for-hint.md
new file mode 100644
index 0000000000..2269910489
--- /dev/null
+++ b/changelogs/unreleased/gh-8937-memtx-tree-index-doesnt-accept-true-option-for-hint.md
@@ -0,0 +1,4 @@
+## bugfix/core
+
+* Fixed a bug because of which it was impossible to set the `hint` option
+  to `true` for TREE indexes (gh-8937).
diff --git a/src/box/alter.cc b/src/box/alter.cc
index d1c365c644..2aab9ccf97 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -350,6 +350,17 @@ index_def_new_from_tuple(struct tuple *tuple, struct space *space)
 		return NULL;
 	if (space_check_index_def(space, index_def) != 0)
 		return NULL;
+	/*
+	 * Set opts.hint to the unambiguous ON or OFF value. This
+	 * allows to compare opts.hint like in index_opts_cmp()
+	 * or memtx_index_def_change_requires_rebuild().
+	 */
+	if (index_def->opts.hint == INDEX_HINT_DEFAULT) {
+		if (space_is_memtx(space) && type == TREE)
+			index_def->opts.hint = INDEX_HINT_ON;
+		else
+			index_def->opts.hint = INDEX_HINT_OFF;
+	}
 	/*
 	 * In case of functional index definition, resolve a
 	 * function pointer to perform a complete index build
diff --git a/src/box/index_def.c b/src/box/index_def.c
index b0d8d86f4d..86fafbc206 100644
--- a/src/box/index_def.c
+++ b/src/box/index_def.c
@@ -51,9 +51,30 @@ const struct index_opts index_opts_default = {
 	/* .lsn                 = */ 0,
 	/* .stat                = */ NULL,
 	/* .func                = */ 0,
-	/* .hint                = */ true,
+	/* .hint                = */ INDEX_HINT_DEFAULT,
 };
 
+/**
+ * Parse index hint option from msgpack.
+ * Used as callback to parse a boolean value with 'hint' key in index options.
+ * Move @a data msgpack pointer to the end of msgpack value.
+ * By convention @a opts must point to corresponding struct index_opts.
+ * Return 0 on success or -1 on error (diag is set to IllegalParams).
+ */
+static int
+index_opts_parse_hint(const char **data, void *opts, struct region *region)
+{
+	(void)region;
+	struct index_opts *index_opts = (struct index_opts *)opts;
+	if (mp_typeof(**data) != MP_BOOL) {
+		diag_set(IllegalParams, "'hint' must be boolean");
+		return -1;
+	}
+	bool hint = mp_decode_bool(data);
+	index_opts->hint = hint ? INDEX_HINT_ON : INDEX_HINT_OFF;
+	return 0;
+}
+
 const struct opt_def index_opts_reg[] = {
 	OPT_DEF("unique", OPT_BOOL, struct index_opts, is_unique),
 	OPT_DEF("dimension", OPT_INT64, struct index_opts, dimension),
@@ -67,7 +88,7 @@ const struct opt_def index_opts_reg[] = {
 	OPT_DEF("lsn", OPT_INT64, struct index_opts, lsn),
 	OPT_DEF("func", OPT_UINT32, struct index_opts, func_id),
 	OPT_DEF_LEGACY("sql"),
-	OPT_DEF("hint", OPT_BOOL, struct index_opts, hint),
+	OPT_DEF_CUSTOM("hint", index_opts_parse_hint),
 	OPT_END,
 };
 
diff --git a/src/box/index_def.h b/src/box/index_def.h
index 2d6916bc44..f2c97a3f66 100644
--- a/src/box/index_def.h
+++ b/src/box/index_def.h
@@ -49,6 +49,13 @@ enum index_type {
 
 extern const char *index_type_strs[];
 
+/** Settings for the hint config option. */
+enum index_hint_cfg {
+	INDEX_HINT_DEFAULT = 0,
+	INDEX_HINT_ON,
+	INDEX_HINT_OFF
+};
+
 enum rtree_index_distance_type {
 	 /* Euclid distance, sqrt(dx*dx + dy*dy) */
 	RTREE_INDEX_DISTANCE_TYPE_EUCLID,
@@ -168,7 +175,7 @@ struct index_opts {
 	/**
 	 * Use hint optimization for tree index.
 	 */
-	bool hint;
+	enum index_hint_cfg hint;
 };
 
 extern const struct index_opts index_opts_default;
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 58ac9d2d4a..7bc4e2eae8 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -1559,11 +1559,6 @@ box.schema.index.create = function(space_id, name, options)
         options_defaults = {}
     end
     options = update_param_table(options, options_defaults)
-    if options.hint and
-            (options.type ~= 'tree' or box.space[space_id].engine ~= 'memtx') then
-        box.error(box.error.MODIFY_INDEX, name, space.name,
-                "hint is only reasonable with memtx tree index")
-    end
     if options.hint and options.func then
         box.error(box.error.MODIFY_INDEX, name, space.name,
                 "functional index can't use hints")
@@ -1751,12 +1746,6 @@ box.schema.index.alter = function(space_id, index_id, options)
             index_opts[k] = options[k]
         end
     end
-    if options.hint and
-       (options.type ~= 'tree' or box.space[space_id].engine ~= 'memtx') then
-        box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
-                                          space.name,
-            "hint is only reasonable with memtx tree index")
-    end
     if options.hint and options.func then
         box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
                                           space.name,
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index 688f6b969e..923cabd41b 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -416,7 +416,7 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i)
 			lua_setfield(L, -2, "dimension");
 		}
 		if (space_is_memtx(space) && index_def->type == TREE) {
-			lua_pushboolean(L, index_opts->hint);
+			lua_pushboolean(L, index_opts->hint == INDEX_HINT_ON);
 			lua_setfield(L, -2, "hint");
 		} else {
 			lua_pushnil(L);
diff --git a/src/box/memtx_space.c b/src/box/memtx_space.c
index af9d431b23..e36d5bf6e6 100644
--- a/src/box/memtx_space.c
+++ b/src/box/memtx_space.c
@@ -818,6 +818,19 @@ memtx_space_check_index_def(struct space *space, struct index_def *index_def)
 			 index_def->name, space_name(space));
 		return -1;
 	}
+
+	if (index_def->type != TREE && index_def->opts.hint == INDEX_HINT_ON &&
+	    recovery_state == FINISHED_RECOVERY) {
+		/*
+		 * The error is silenced during recovery to be able to recover
+		 * the indexes with incorrect hint options.
+		 */
+		diag_set(ClientError, ER_MODIFY_INDEX, index_def->name,
+			 space_name(space),
+			 "hint is only reasonable with memtx tree index");
+		return -1;
+	}
+
 	/* Only HASH and TREE indexes checks parts there */
 	/* Check that there are no ANY, ARRAY, MAP parts */
 	for (uint32_t i = 0; i < key_def->part_count; i++) {
diff --git a/src/box/memtx_tree.cc b/src/box/memtx_tree.cc
index 2bcdbc31ad..b9a285bba8 100644
--- a/src/box/memtx_tree.cc
+++ b/src/box/memtx_tree.cc
@@ -2156,7 +2156,7 @@ memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)
 	} else if (def->key_def->is_multikey) {
 		vtab = get_memtx_tree_index_vtab<MEMTX_TREE_VTAB_MULTIKEY>();
 		use_hint = true;
-	} else if (def->opts.hint) {
+	} else if (def->opts.hint == INDEX_HINT_ON) {
 		vtab = get_memtx_tree_index_vtab
 			<MEMTX_TREE_VTAB_GENERAL, true>();
 		use_hint = true;
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index dc2c053be9..2cb41a222d 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -670,6 +670,17 @@ vinyl_space_check_index_def(struct space *space, struct index_def *index_def)
 			 index_def->name, space_name(space));
 		return -1;
 	}
+	if (index_def->opts.hint == INDEX_HINT_ON &&
+	    recovery_state == FINISHED_RECOVERY) {
+		/*
+		 * The error is silenced during recovery to be able to recover
+		 * the indexes with incorrect hint options.
+		 */
+		diag_set(ClientError, ER_MODIFY_INDEX, index_def->name,
+			 space_name(space),
+			 "hint is only reasonable with memtx tree index");
+		return -1;
+	}
 
 	struct key_def *key_def = index_def->key_def;
 
diff --git a/test/box-luatest/gh_8937_data/00000000000000000004.snap b/test/box-luatest/gh_8937_data/00000000000000000004.snap
new file mode 100644
index 0000000000000000000000000000000000000000..89b02ddb73486fb85f838e913d55a7f3263754d2
GIT binary patch
literal 5108
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
W?>XpLsc}I+vM7oiBDw<A5UuSPXx5|v

literal 0
HcmV?d00001

diff --git a/test/box-luatest/gh_8937_data/00000000000000000004.vylog b/test/box-luatest/gh_8937_data/00000000000000000004.vylog
new file mode 100644
index 0000000000000000000000000000000000000000..3b74d07efa3ce3605f8b9240cbf580f3a36816ed
GIT binary patch
literal 191
zcmWHG^znD+GSD+L<_b$KD$dN$vr;hDGte{8P0T6CNHo+nGty1Bv@lLIPfa#5<nqib
zE=kNwPPI}nG_<g^Oi3}+HApiy)itq9OVUkBu}su8G_y=jHnFr!G&WA=3UkiMPtLYd
zs5Z1xFsbF@y1I*dwH3p;{{<B<moq>>BLh>T;v|L^wzEu(>=T=ore&t)q%bTgsVqoc
fQJPnrnVy%L!o$ML0#V1*$jAs`aW`BQSo;<Lka;;c

literal 0
HcmV?d00001

diff --git a/test/box-luatest/gh_8937_data/gen.lua b/test/box-luatest/gh_8937_data/gen.lua
new file mode 100644
index 0000000000..7c6a8a8719
--- /dev/null
+++ b/test/box-luatest/gh_8937_data/gen.lua
@@ -0,0 +1,14 @@
+-- This script can be used only with Tarantool without the fix for gh-8937 (e.g.
+-- version 3.0.0-alpha1-62-g983a7ec21).
+
+box.cfg{}
+
+local s = box.schema.create_space('gh_8937_memtx')
+box.space._index:insert{s.id, 0, "pk", "hash", {hint = true}, {{0, "unsigned"}}}
+
+s = box.schema.create_space('gh_8937_vinyl', {engine = 'vinyl'})
+box.space._index:insert{s.id, 0, "pk", "tree", {hint = true}, {{0, "unsigned"}}}
+
+box.snapshot()
+
+os.exit(0)
diff --git a/test/box-luatest/gh_8937_recover_wrong_hint_options_test.lua b/test/box-luatest/gh_8937_recover_wrong_hint_options_test.lua
new file mode 100644
index 0000000000..0cf552e2a8
--- /dev/null
+++ b/test/box-luatest/gh_8937_recover_wrong_hint_options_test.lua
@@ -0,0 +1,22 @@
+local t = require('luatest')
+local g = t.group('gh-8937')
+
+g.before_all(function(cg)
+    local server = require('luatest.server')
+    cg.server = server:new{datadir = 'test/box-luatest/gh_8937_data'}
+    cg.server:start()
+end)
+
+g.after_all(function(cg)
+    cg.server:drop()
+end)
+
+-- Check that indexes are recovered without errors like:
+-- "Can't create or modify index 'pk' in space 'gh_8937_memtx':
+-- hint is only reasonable with memtx tree index".
+g.test_recovery = function(cg)
+    cg.server:exec(function()
+        t.assert_equals(box.space.gh_8937_memtx.index[0].name, "pk")
+        t.assert_equals(box.space.gh_8937_vinyl.index[0].name, "pk")
+    end)
+end
diff --git a/test/box/tree_pk.result b/test/box/tree_pk.result
index 553235b905..0530ae7c0e 100644
--- a/test/box/tree_pk.result
+++ b/test/box/tree_pk.result
@@ -868,24 +868,77 @@ box.internal.collation.drop('test')
 box.internal.collation.drop('test-ci')
 ---
 ...
--- hints
+-- memtx hints
 s = box.schema.space.create('test')
 ---
 ...
-s:create_index('test', {type = 'tree', hint = 'true'} )
+s:create_index('t1', {type = 'TRee'}).hint
+---
+- true
+...
+s:create_index('h1', {type = 'hAsh'}).hint
+---
+- null
+...
+s:create_index('t2', {type = 'trEE', hint = 'true'})
 ---
 - error: Illegal parameters, options parameter 'hint' should be of type boolean
 ...
-s:create_index('test', {type = 'hash', hint = true} )
+s:create_index('t2', {type = 'TREE', hint = true}).hint
+---
+- true
+...
+s:create_index('t3', {type = 'tree', hint = false}).hint
+---
+- false
+...
+s:create_index('h2', {type = 'HASH', hint = true})
+---
+- error: 'Can''t create or modify index ''h2'' in space ''test'': hint is only reasonable
+    with memtx tree index'
+...
+s:create_index('h2', {type = 'hash', hint = false}).hint
+---
+- null
+...
+_ = s:create_index('t4', {type = 'TREE'}):alter({hint = true})
+---
+...
+s.index.t4.hint
+---
+- true
+...
+_ = s:create_index('t5', {type = 'tree'}):alter({hint = false})
+---
+...
+s.index.t5.hint
+---
+- false
+...
+s:create_index('h3', {type = 'haSH'}):alter({hint = true})
 ---
-- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable
+- error: 'Can''t create or modify index ''h3'' in space ''test'': hint is only reasonable
     with memtx tree index'
 ...
-s:create_index('test', {type = 'hash'}):alter({hint = true})
+s:create_index('h4', {type = 'hash'}):alter({hint = false})
+---
+...
+s.index.h4.hint
+---
+- null
+...
+box.space._index:insert{s.id, s.index.h4.id + 1, "h5", "hash", {hint = true}, {{0, "unsigned"}}}
 ---
-- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable
+- error: 'Can''t create or modify index ''h5'' in space ''test'': hint is only reasonable
     with memtx tree index'
 ...
+_ = box.space._index:insert{s.id, s.index.h4.id + 1, "h5", "hash", {hint = false}, {{0, "unsigned"}}}
+---
+...
+box.space._index:insert{s.id, s.index.h5.id + 1, "h6", "hash", {hint = "false"}, {{0, "unsigned"}}}
+---
+- error: 'Wrong index options: ''hint'' must be boolean'
+...
 s:create_index('multikey', {hint = true, parts = {{2, 'int', path = '[*]'}}})
 ---
 - error: 'Can''t create or modify index ''multikey'' in space ''test'': multikey index
@@ -910,14 +963,39 @@ s:create_index('func', {hint = true, func = box.func.s.id, parts = {{1, 'unsigne
 s:drop()
 ---
 ...
+-- vinyl hints
 s = box.schema.space.create('test', {engine = 'vinyl'})
 ---
 ...
-s:create_index('test', {type = 'tree', hint = true} )
+s:create_index('i1', {type = 'tree'}).hint
+---
+- null
+...
+s:create_index('i2', {type = 'TREE', hint = true})
 ---
-- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable
+- error: 'Can''t create or modify index ''i2'' in space ''test'': hint is only reasonable
     with memtx tree index'
 ...
+s:create_index('i2', {type = 'tree', hint = false}).hint
+---
+- null
+...
+s:create_index('i3', {type = 'TREE'}):alter({hint = true})
+---
+- error: 'Can''t create or modify index ''i3'' in space ''test'': hint is only reasonable
+    with memtx tree index'
+...
+_ = s:create_index('i4', {type = 'tree'}):alter({hint = false})
+---
+...
+box.space._index:insert{s.id, s.index.i4.id + 1, "i5", "tree", {hint = true}, {{0, "unsigned"}}}
+---
+- error: 'Can''t create or modify index ''i5'' in space ''test'': hint is only reasonable
+    with memtx tree index'
+...
+_ = box.space._index:insert{s.id, s.index.i4.id + 1, "i6", "tree", {hint = false}, {{0, "unsigned"}}}
+---
+...
 s:drop()
 ---
 ...
diff --git a/test/box/tree_pk.test.lua b/test/box/tree_pk.test.lua
index 4c036dd6ac..e3fd65187c 100644
--- a/test/box/tree_pk.test.lua
+++ b/test/box/tree_pk.test.lua
@@ -304,11 +304,25 @@ s:drop()
 box.internal.collation.drop('test')
 box.internal.collation.drop('test-ci')
 
--- hints
+-- memtx hints
 s = box.schema.space.create('test')
-s:create_index('test', {type = 'tree', hint = 'true'} )
-s:create_index('test', {type = 'hash', hint = true} )
-s:create_index('test', {type = 'hash'}):alter({hint = true})
+s:create_index('t1', {type = 'TRee'}).hint
+s:create_index('h1', {type = 'hAsh'}).hint
+s:create_index('t2', {type = 'trEE', hint = 'true'})
+s:create_index('t2', {type = 'TREE', hint = true}).hint
+s:create_index('t3', {type = 'tree', hint = false}).hint
+s:create_index('h2', {type = 'HASH', hint = true})
+s:create_index('h2', {type = 'hash', hint = false}).hint
+_ = s:create_index('t4', {type = 'TREE'}):alter({hint = true})
+s.index.t4.hint
+_ = s:create_index('t5', {type = 'tree'}):alter({hint = false})
+s.index.t5.hint
+s:create_index('h3', {type = 'haSH'}):alter({hint = true})
+s:create_index('h4', {type = 'hash'}):alter({hint = false})
+s.index.h4.hint
+box.space._index:insert{s.id, s.index.h4.id + 1, "h5", "hash", {hint = true}, {{0, "unsigned"}}}
+_ = box.space._index:insert{s.id, s.index.h4.id + 1, "h5", "hash", {hint = false}, {{0, "unsigned"}}}
+box.space._index:insert{s.id, s.index.h5.id + 1, "h6", "hash", {hint = "false"}, {{0, "unsigned"}}}
 s:create_index('multikey', {hint = true, parts = {{2, 'int', path = '[*]'}}})
 s:create_index('multikey', {parts = {{2, 'int', path = '[*]'}}}):alter({hint = true})
 lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]]
@@ -316,8 +330,15 @@ box.schema.func.create('s', {body = lua_code, is_deterministic = true, is_sandbo
 s:create_index('func', {hint = true, func = box.func.s.id, parts = {{1, 'unsigned'}}})
 s:drop()
 
+-- vinyl hints
 s = box.schema.space.create('test', {engine = 'vinyl'})
-s:create_index('test', {type = 'tree', hint = true} )
+s:create_index('i1', {type = 'tree'}).hint
+s:create_index('i2', {type = 'TREE', hint = true})
+s:create_index('i2', {type = 'tree', hint = false}).hint
+s:create_index('i3', {type = 'TREE'}):alter({hint = true})
+_ = s:create_index('i4', {type = 'tree'}):alter({hint = false})
+box.space._index:insert{s.id, s.index.i4.id + 1, "i5", "tree", {hint = true}, {{0, "unsigned"}}}
+_ = box.space._index:insert{s.id, s.index.i4.id + 1, "i6", "tree", {hint = false}, {{0, "unsigned"}}}
 s:drop()
 
 -- numeric hints
-- 
GitLab