From d957785556608f13db90f86fc81a1072c0b0e34f Mon Sep 17 00:00:00 2001
From: Ilya Verbin <iverbin@tarantool.org>
Date: Wed, 16 Aug 2023 16:18:38 +0300
Subject: [PATCH] box: always set index.parts.exclude_null to true or false

Currently the `exclude_null` field of `index_object.parts` is inconsistent
between local box and net.box connection. It returns the following values:
- locally: true / null
- net.box: true / false

This mismatch makes it difficult to compare schemas on the storage and via
the net.box connection on the router (see tarantool/crud#361).

Closes #8649

NO_DOC=minor
---
 ...lways-set-exclude_null-to-true-or-false.md |  4 ++
 src/box/lua/key_def.c                         |  6 +--
 .../gh_7356_index_parts_methods_test.lua      |  9 +++--
 test/box-tap/key_def.test.lua                 | 17 +++++---
 test/box/alter.result                         |  4 ++
 test/box/errinj.result                        |  2 +
 test/box/gh-5998-one-tx-for-ddl.result        |  1 +
 test/box/lua.result                           |  2 +
 test/box/misc.result                          | 25 +++++++-----
 test/box/rtree_misc.result                    |  1 +
 test/box/tx_man.result                        |  1 +
 test/engine/ddl.result                        | 17 ++++++++
 test/engine/null.result                       | 39 +++++++++++++------
 test/sql/types.result                         |  8 ++++
 test/vinyl/ddl.result                         |  1 +
 15 files changed, 104 insertions(+), 33 deletions(-)
 create mode 100644 changelogs/unreleased/gh-8649-always-set-exclude_null-to-true-or-false.md

diff --git a/changelogs/unreleased/gh-8649-always-set-exclude_null-to-true-or-false.md b/changelogs/unreleased/gh-8649-always-set-exclude_null-to-true-or-false.md
new file mode 100644
index 0000000000..c056cfd8e5
--- /dev/null
+++ b/changelogs/unreleased/gh-8649-always-set-exclude_null-to-true-or-false.md
@@ -0,0 +1,4 @@
+## bugfix/core
+
+* Now `index_object.parts.exclude_null` always contains `false` rather than
+  `null` when it is actually `false` (gh-8649).
diff --git a/src/box/lua/key_def.c b/src/box/lua/key_def.c
index d5dfa83695..2ea239f4a6 100644
--- a/src/box/lua/key_def.c
+++ b/src/box/lua/key_def.c
@@ -93,10 +93,8 @@ luaT_push_key_def_parts(struct lua_State *L, const struct key_def *key_def)
 		lua_pushboolean(L, key_part_is_nullable(part));
 		lua_setfield(L, -2, "is_nullable");
 
-		if (part->exclude_null) {
-			lua_pushboolean(L, true);
-			lua_setfield(L, -2, "exclude_null");
-		}
+		lua_pushboolean(L, part->exclude_null);
+		lua_setfield(L, -2, "exclude_null");
 
 		if (part->coll_id != COLL_NONE) {
 			const char *name;
diff --git a/test/box-luatest/gh_7356_index_parts_methods_test.lua b/test/box-luatest/gh_7356_index_parts_methods_test.lua
index 1ea3c26c7a..7bee5ca5c9 100644
--- a/test/box-luatest/gh_7356_index_parts_methods_test.lua
+++ b/test/box-luatest/gh_7356_index_parts_methods_test.lua
@@ -129,9 +129,12 @@ g.test_merge = function(cg)
                                                 {type = 'scalar', field = 2}}})
         local j = s:create_index('j', {parts = {{type = 'unsigned', field = 1},
                                                 {type = 'string', field = 3}}})
-        local exp = {{fieldno = 3, type = 'string', is_nullable = false},
-                     {fieldno = 2, type = 'scalar', is_nullable = false},
-                     {fieldno = 1, type = 'unsigned', is_nullable = false}}
+        local exp = {{fieldno = 3, type = 'string', is_nullable = false,
+                      exclude_null = false},
+                     {fieldno = 2, type = 'scalar', is_nullable = false,
+                      exclude_null = false},
+                     {fieldno = 1, type = 'unsigned', is_nullable = false,
+                      exclude_null = false}}
         t.assert_equals(i.parts:merge(j.parts):totable(), exp)
 
         t.assert_error_msg_content_equals(
diff --git a/test/box-tap/key_def.test.lua b/test/box-tap/key_def.test.lua
index 1b89dea196..3a83aea9f6 100755
--- a/test/box-tap/key_def.test.lua
+++ b/test/box-tap/key_def.test.lua
@@ -30,6 +30,9 @@ local function set_key_part_defaults(parts)
         if res[i].is_nullable == nil then
             res[i].is_nullable = false
         end
+        if res[i].exclude_null == nil then
+            res[i].exclude_null = false
+        end
     end
     return res
 end
@@ -519,14 +522,15 @@ test:test('merge()', function(test)
     local key_def_cb = key_def_c:merge(key_def_b)
     local exp_parts = key_def_c:totable()
     exp_parts[#exp_parts + 1] = {type = 'number', fieldno = 3,
-        is_nullable = false}
+        is_nullable = false, exclude_null = false}
     test:is_deeply(key_def_cb:totable(), exp_parts,
         'case 3: verify with :totable()')
     test:is_deeply(key_def_cb:extract_key(tuple_a):totable(),
         {1, 1, box.NULL, 22}, 'case 3: verify with :extract_key()')
 
     local parts_unsigned = {
-        {type = 'unsigned', fieldno = 1, is_nullable = false},
+        {type = 'unsigned', fieldno = 1, is_nullable = false,
+         exclude_null = false},
     }
     local key_def_unsigned = key_def_lib.new(parts_unsigned)
     local key_def_string = key_def_lib.new({
@@ -549,9 +553,12 @@ test:test('merge()', function(test)
 
     local key_def_array_map = key_def_array:merge(key_def_map)
     local exp_parts = {
-        {type = 'array', fieldno = 1, is_nullable = false},
-        {type = 'unsigned', fieldno = 2, is_nullable = false},
-        {type = 'map', fieldno = 3, is_nullable = true},
+        {type = 'array', fieldno = 1, is_nullable = false,
+         exclude_null = false},
+        {type = 'unsigned', fieldno = 2, is_nullable = false,
+         exclude_null = false},
+        {type = 'map', fieldno = 3, is_nullable = true,
+         exclude_null = false},
     }
     test:is_deeply(key_def_array_map:totable(), exp_parts,
         'composite case')
diff --git a/test/box/alter.result b/test/box/alter.result
index b2ac94d410..17549523fb 100644
--- a/test/box/alter.result
+++ b/test/box/alter.result
@@ -705,9 +705,11 @@ s.index.pk
 - unique: true
   parts:
   - type: unsigned
+    exclude_null: false
     is_nullable: false
     fieldno: 1
   - type: unsigned
+    exclude_null: false
     is_nullable: false
     fieldno: 2
   hint: true
@@ -739,6 +741,7 @@ s.index.pk
 - unique: true
   parts:
   - type: unsigned
+    exclude_null: false
     is_nullable: false
     fieldno: 1
   hint: true
@@ -752,6 +755,7 @@ s.index.secondary
 - unique: true
   parts:
   - type: unsigned
+    exclude_null: false
     is_nullable: false
     fieldno: 2
   hint: true
diff --git a/test/box/errinj.result b/test/box/errinj.result
index 78b893f46b..105b11dfe2 100644
--- a/test/box/errinj.result
+++ b/test/box/errinj.result
@@ -1721,6 +1721,7 @@ rtreespace:create_index('pk', {if_not_exists = true})
 - unique: true
   parts:
   - type: unsigned
+    exclude_null: false
     is_nullable: false
     fieldno: 1
   hint: true
@@ -1733,6 +1734,7 @@ rtreespace:create_index('target', {type='rtree', dimension = 3, parts={2, 'array
 ---
 - parts:
   - type: array
+    exclude_null: false
     is_nullable: false
     fieldno: 2
   dimension: 3
diff --git a/test/box/gh-5998-one-tx-for-ddl.result b/test/box/gh-5998-one-tx-for-ddl.result
index ed01fbc26e..5745b91643 100644
--- a/test/box/gh-5998-one-tx-for-ddl.result
+++ b/test/box/gh-5998-one-tx-for-ddl.result
@@ -40,6 +40,7 @@ s1:create_index('pk')
  | - unique: true
  |   parts:
  |   - type: unsigned
+ |     exclude_null: false
  |     is_nullable: false
  |     fieldno: 1
  |   hint: true
diff --git a/test/box/lua.result b/test/box/lua.result
index fda387fc78..7422393143 100644
--- a/test/box/lua.result
+++ b/test/box/lua.result
@@ -714,9 +714,11 @@ tmp = space:create_index('primary', { type = 'tree', parts = {3, 'unsigned', 2,
 space.index['primary'].parts
 ---
 - - type: unsigned
+    exclude_null: false
     is_nullable: false
     fieldno: 3
   - type: unsigned
+    exclude_null: false
     is_nullable: false
     fieldno: 2
 ...
diff --git a/test/box/misc.result b/test/box/misc.result
index 68fc4622dc..dc969b484a 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -185,6 +185,7 @@ end;
 t;
 ---
 - - 'type : unsigned'
+  - 'exclude_null : false'
   - 'is_nullable : false'
   - 'fieldno : 1'
 ...
@@ -849,15 +850,17 @@ not not s:create_index('test3', {parts = {{3, 'string', collation = 'UnIcOdE'}}}
 s:create_index('test4', {parts = {{4, 'string'}}}).parts
 ---
 - - type: string
+    exclude_null: false
     is_nullable: false
     fieldno: 4
 ...
 s:create_index('test5', {parts = {{5, 'string', collation = 'Unicode'}}}).parts
 ---
-- - type: string
-    is_nullable: false
+- - fieldno: 5
+    type: string
+    exclude_null: false
     collation: unicode
-    fieldno: 5
+    is_nullable: false
 ...
 s:drop()
 ---
@@ -1047,10 +1050,11 @@ i = s:create_index('test2', {parts = {{2, 'string', is_nullable = true, collatio
 ...
 i.parts
 ---
-- - type: string
-    is_nullable: true
+- - fieldno: 2
+    type: string
+    exclude_null: false
     collation: unicode
-    fieldno: 2
+    is_nullable: true
 ...
 i = s:create_index('test4', {parts = {3, 'string', is_nullable = true}})
 ---
@@ -1058,6 +1062,7 @@ i = s:create_index('test4', {parts = {3, 'string', is_nullable = true}})
 i.parts
 ---
 - - type: string
+    exclude_null: false
     is_nullable: true
     fieldno: 3
 ...
@@ -1066,10 +1071,11 @@ i = s:create_index('test5', {parts = {3, 'string', collation = 'unicode'}})
 ...
 i.parts
 ---
-- - type: string
-    is_nullable: false
+- - fieldno: 3
+    type: string
+    exclude_null: false
     collation: unicode
-    fieldno: 3
+    is_nullable: false
 ...
 i = s:create_index('test6', {parts = {4, 'string'}})
 ---
@@ -1077,6 +1083,7 @@ i = s:create_index('test6', {parts = {4, 'string'}})
 i.parts
 ---
 - - type: string
+    exclude_null: false
     is_nullable: false
     fieldno: 4
 ...
diff --git a/test/box/rtree_misc.result b/test/box/rtree_misc.result
index f93a597bac..007d354a41 100644
--- a/test/box/rtree_misc.result
+++ b/test/box/rtree_misc.result
@@ -543,6 +543,7 @@ i.dimension
 i.parts
 ---
 - - type: array
+    exclude_null: false
     is_nullable: false
     fieldno: 2
 ...
diff --git a/test/box/tx_man.result b/test/box/tx_man.result
index 53e8cff3fe..8eea6fd2a7 100644
--- a/test/box/tx_man.result
+++ b/test/box/tx_man.result
@@ -3880,6 +3880,7 @@ s:create_index('sk', {parts = {{2, 'unsigned'}}})
  | - unique: true
  |   parts:
  |   - type: unsigned
+ |     exclude_null: false
  |     is_nullable: false
  |     fieldno: 2
  |   hint: true
diff --git a/test/engine/ddl.result b/test/engine/ddl.result
index ddd01520aa..586684b925 100644
--- a/test/engine/ddl.result
+++ b/test/engine/ddl.result
@@ -311,6 +311,7 @@ pk = space:create_index('pk', {parts={{field = 1, type = 'unsigned'}}})
 pk.parts
 ---
 - - type: unsigned
+    exclude_null: false
     is_nullable: false
     fieldno: 1
 ...
@@ -323,6 +324,7 @@ pk = space:create_index('pk', {parts={{1, 'unsigned'}}})
 pk.parts
 ---
 - - type: unsigned
+    exclude_null: false
     is_nullable: false
     fieldno: 1
 ...
@@ -335,6 +337,7 @@ pk = space:create_index('pk', {parts={{1, type='unsigned'}}})
 pk.parts
 ---
 - - type: unsigned
+    exclude_null: false
     is_nullable: false
     fieldno: 1
 ...
@@ -726,6 +729,7 @@ s:format()
 s.index.sk3.parts
 ---
 - - type: string
+    exclude_null: false
     is_nullable: false
     fieldno: 2
 ...
@@ -953,6 +957,7 @@ s:create_index('test', {parts = {{field = 'test'}}})
 s:create_index('test', {parts = {1}}).parts
 ---
 - - type: scalar
+    exclude_null: false
     is_nullable: false
     fieldno: 1
 ...
@@ -980,6 +985,7 @@ s:create_index('test', {parts = {{field = 'test'}}})
 s:create_index('test1', {parts = {'test1'}}).parts
 ---
 - - type: integer
+    exclude_null: false
     is_nullable: false
     fieldno: 1
 ...
@@ -991,45 +997,54 @@ s:create_index('test2', {parts = {'test2'}}).parts
 s:create_index('test3', {parts = {{'test1', 'integer'}}}).parts
 ---
 - - type: integer
+    exclude_null: false
     is_nullable: false
     fieldno: 1
 ...
 s:create_index('test4', {parts = {{'test2', 'integer'}}}).parts
 ---
 - - type: integer
+    exclude_null: false
     is_nullable: false
     fieldno: 2
 ...
 s:create_index('test5', {parts = {{'test2', 'integer'}}}).parts
 ---
 - - type: integer
+    exclude_null: false
     is_nullable: false
     fieldno: 2
 ...
 s:create_index('test6', {parts = {1, 3}}).parts
 ---
 - - type: integer
+    exclude_null: false
     is_nullable: false
     fieldno: 1
   - type: integer
+    exclude_null: false
     is_nullable: false
     fieldno: 3
 ...
 s:create_index('test7', {parts = {'test1', 4}}).parts
 ---
 - - type: integer
+    exclude_null: false
     is_nullable: false
     fieldno: 1
   - type: scalar
+    exclude_null: false
     is_nullable: false
     fieldno: 4
 ...
 s:create_index('test8', {parts = {{1, 'integer'}, {'test4', 'scalar'}}}).parts
 ---
 - - type: integer
+    exclude_null: false
     is_nullable: false
     fieldno: 1
   - type: scalar
+    exclude_null: false
     is_nullable: false
     fieldno: 4
 ...
@@ -1668,6 +1683,7 @@ i = s:create_index('primary', { parts = {'field1'} })
 i.parts
 ---
 - - type: unsigned
+    exclude_null: false
     is_nullable: false
     fieldno: 1
 ...
@@ -1742,6 +1758,7 @@ i = s:create_index('secondary', { parts = {{2, 'string', is_nullable = true}} })
 i.parts
 ---
 - - type: string
+    exclude_null: false
     is_nullable: true
     fieldno: 2
 ...
diff --git a/test/engine/null.result b/test/engine/null.result
index 31abb9767f..d803612c4f 100644
--- a/test/engine/null.result
+++ b/test/engine/null.result
@@ -1959,6 +1959,7 @@ sk2 = s:create_index('sk2', {parts={{2, 'number', is_nullable=true, exclude_null
 sk2.parts
 ---
 - - type: number
+    exclude_null: false
     is_nullable: true
     fieldno: 2
 ...
@@ -2124,6 +2125,7 @@ sk1:alter({parts={{2, 'number', is_nullable=true, exclude_null=false}}})
 sk1.parts
 ---
 - - type: number
+    exclude_null: false
     is_nullable: true
     fieldno: 2
 ...
@@ -2455,6 +2457,7 @@ sk = s:create_index('sk', {parts = {2, is_nullable=true}})
 sk.parts
 ---
 - - type: scalar
+    exclude_null: false
     is_nullable: true
     fieldno: 2
 ...
@@ -2466,10 +2469,11 @@ sk = s:create_index('sk', {parts = {2, is_nullable=true, collation='unicode'}})
 ...
 sk.parts
 ---
-- - type: scalar
-    is_nullable: true
+- - fieldno: 2
+    type: scalar
+    exclude_null: false
     collation: unicode
-    fieldno: 2
+    is_nullable: true
 ...
 sk:drop()
 ---
@@ -2479,10 +2483,11 @@ sk = s:create_index('sk', {parts = {2, type='string', is_nullable=true, collatio
 ...
 sk.parts
 ---
-- - type: string
-    is_nullable: true
+- - fieldno: 2
+    type: string
+    exclude_null: false
     collation: unicode
-    fieldno: 2
+    is_nullable: true
 ...
 sk:drop()
 ---
@@ -2493,10 +2498,11 @@ sk = s:create_index('sk', {parts = {2, is_nullable=true, 3}})
 ...
 sk.parts
 ---
-- - type: string
-    is_nullable: true
+- - fieldno: 2
+    type: string
+    exclude_null: false
     collation: unicode
-    fieldno: 2
+    is_nullable: true
 ...
 sk:drop()
 ---
@@ -2507,9 +2513,11 @@ sk = s:create_index('sk', {parts = {2, 3}})
 sk.parts
 ---
 - - type: scalar
+    exclude_null: false
     is_nullable: false
     fieldno: 2
   - type: scalar
+    exclude_null: false
     is_nullable: false
     fieldno: 3
 ...
@@ -2522,12 +2530,15 @@ sk = s:create_index('sk', {parts = {2, 3, 4}})
 sk.parts
 ---
 - - type: scalar
+    exclude_null: false
     is_nullable: false
     fieldno: 2
   - type: scalar
+    exclude_null: false
     is_nullable: false
     fieldno: 3
   - type: scalar
+    exclude_null: false
     is_nullable: false
     fieldno: 4
 ...
@@ -2540,9 +2551,11 @@ sk = s:create_index('sk', {parts = {{2, 'int'}, {3, 'string'}}})
 sk.parts
 ---
 - - type: integer
+    exclude_null: false
     is_nullable: false
     fieldno: 2
   - type: string
+    exclude_null: false
     is_nullable: false
     fieldno: 3
 ...
@@ -2555,12 +2568,14 @@ sk = s:create_index('sk', {parts = {{2, is_nullable=true}, {3, collation='unicod
 sk.parts
 ---
 - - type: scalar
+    exclude_null: false
     is_nullable: true
     fieldno: 2
-  - type: scalar
-    is_nullable: false
+  - fieldno: 3
+    type: scalar
+    exclude_null: false
     collation: unicode
-    fieldno: 3
+    is_nullable: false
 ...
 sk:drop()
 ---
diff --git a/test/sql/types.result b/test/sql/types.result
index 2cfb8fe4a1..b83aedc912 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -82,36 +82,44 @@ box.execute("CREATE INDEX i4 ON t1 (id, c, b, a, d);")
 box.space.T1.index.I1.parts
 ---
 - - type: number
+    exclude_null: false
     is_nullable: true
     fieldno: 2
 ...
 box.space.T1.index.I2.parts
 ---
 - - type: integer
+    exclude_null: false
     is_nullable: true
     fieldno: 3
 ...
 box.space.T1.index.I3.parts
 ---
 - - type: string
+    exclude_null: false
     is_nullable: true
     fieldno: 4
 ...
 box.space.T1.index.I4.parts
 ---
 - - type: string
+    exclude_null: false
     is_nullable: false
     fieldno: 1
   - type: string
+    exclude_null: false
     is_nullable: true
     fieldno: 4
   - type: integer
+    exclude_null: false
     is_nullable: true
     fieldno: 3
   - type: number
+    exclude_null: false
     is_nullable: true
     fieldno: 2
   - type: scalar
+    exclude_null: false
     is_nullable: true
     fieldno: 5
 ...
diff --git a/test/vinyl/ddl.result b/test/vinyl/ddl.result
index 8cacc06d16..9968215fb3 100644
--- a/test/vinyl/ddl.result
+++ b/test/vinyl/ddl.result
@@ -571,6 +571,7 @@ box.space.test.index.pk
 - unique: true
   parts:
   - type: unsigned
+    exclude_null: false
     is_nullable: false
     fieldno: 1
   options:
-- 
GitLab