From 51af059c10ffa451aaaa59053014f11cc64f8d5c Mon Sep 17 00:00:00 2001
From: Magomed Kostoev <mkostoevr@yandex.ru>
Date: Mon, 3 Jul 2023 17:44:22 +0300
Subject: [PATCH] box: compare and hash msgpack value of double key field as
 double
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. Make double-formatted fields accept integer and float values.
2. Make indexes compare the values as double if the field key type
   is FIELD_TYPE_DOUBLE.
3. Make hashers cast double key field to double before hashing, so
   we are able to insert and select any int, uint, float or double
   if their value casted to double is equal (for double keys).

Notes about tuple_compare.cc:

Since now `mp_compare_double` casts any value placed in field to
double it was renamed to `mp_compare_as_double` to not semantically
conflict with existing `mp_compare_double_*` functions.

Notes about tuple_hash.cc:

The hashee cast result is encoded in MP_DOUBLE and hashed for
backward compatibility reasons.

Since now the field hashing function (tuple_hash_field) requires
field type to hash the field correctly, a new parameter has been
introduced.

By the way added assertions to the generic `field_hash` to prevent
invalid hashing for new precompiled hashers and made
`key_hash_slowpath` static cause it's only used in this file.

Closes #7483
Closes #5933
Unblocks tarantool/crud#298

@TarantoolBot document
Title: It's not required to ffi-cast integral floating point to
double anymore.

The page describing tarantool data model states that:

> In Lua, fields of the double type can only contain non-integer
> numeric values...

If the patch is merged this isn't the case anymore, so this
statement and the code snippet below it should be updated.

Link to the document: [Data storage](https://www.tarantool.io/en/doc/latest/concepts/data_model/value_store/#field-type-details).

Affected segments:

> double. The double field type exists mainly to be equivalent
> to Tarantool/SQL’s DOUBLE data type. In msgpuck.h (Tarantool’s
> interface to MsgPack), the storage type is MP_DOUBLE and the
> size of the encoded value is always 9 bytes. In Lua, fields of
> the double type can only contain non-integer numeric values and
> cdata values with double floating-point numbers. Examples: 1.234,
> -44, 1.447e+44.
>
> To avoid using the wrong kind of values inadvertently, use
> ffi.cast() when searching or changing double fields. For example,
> instead of space_object:insert{value} use ffi = require('ffi')
> ... space_object:insert({ffi.cast('double',value)}). Example:
>
> ```
> s = box.schema.space.create('s', {format = {{'d', 'double'}}})
> s:create_index('ii')
> s:insert({1.1})
> ffi = require('ffi')
> s:insert({ffi.cast('double', 1)})
> s:insert({ffi.cast('double', tonumber('123'))})
> s:select(1.1)
> s:select({ffi.cast('double', 1)})
> ```
---
 .../unreleased/gh-7483-ignore-mpack-types.md  |  4 +
 src/box/field_def.c                           |  3 +-
 src/box/key_def.h                             |  3 +-
 src/box/tuple_bloom.c                         |  2 +
 src/box/tuple_compare.cc                      | 30 +++++--
 src/box/tuple_hash.cc                         | 55 ++++++++++--
 ...7483_insert_int_into_double_field_test.lua | 67 ++++++++++++++
 test/engine/insert.result                     | 87 +++++++++++--------
 test/engine/insert.test.lua                   | 30 ++++---
 test/sql-tap/cast.test.lua                    |  5 +-
 test/vinyl/upsert.result                      | 12 +--
 test/vinyl/upsert.test.lua                    |  4 +-
 12 files changed, 227 insertions(+), 75 deletions(-)
 create mode 100644 changelogs/unreleased/gh-7483-ignore-mpack-types.md
 create mode 100644 test/engine-luatest/gh_7483_insert_int_into_double_field_test.lua

diff --git a/changelogs/unreleased/gh-7483-ignore-mpack-types.md b/changelogs/unreleased/gh-7483-ignore-mpack-types.md
new file mode 100644
index 0000000000..780672671b
--- /dev/null
+++ b/changelogs/unreleased/gh-7483-ignore-mpack-types.md
@@ -0,0 +1,4 @@
+## bugfix/box
+
+* Fixed the inability to insert an integral number into a double-formatted
+  field (gh-7483).
diff --git a/src/box/field_def.c b/src/box/field_def.c
index 6b14c00920..469fd0fc22 100644
--- a/src/box/field_def.c
+++ b/src/box/field_def.c
@@ -69,7 +69,8 @@ const uint32_t field_mp_type[] = {
 	/* [FIELD_TYPE_STRING]   =  */ 1U << MP_STR,
 	/* [FIELD_TYPE_NUMBER]   =  */ (1U << MP_UINT) | (1U << MP_INT) |
 		(1U << MP_FLOAT) | (1U << MP_DOUBLE),
-	/* [FIELD_TYPE_DOUBLE]   =  */ 1U << MP_DOUBLE,
+	/* [FIELD_TYPE_DOUBLE]   =  */ (1U << MP_UINT) | (1U << MP_INT) |
+		(1U << MP_FLOAT) | (1U << MP_DOUBLE),
 	/* [FIELD_TYPE_INTEGER]  =  */ (1U << MP_UINT) | (1U << MP_INT),
 	/* [FIELD_TYPE_BOOLEAN]  =  */ 1U << MP_BOOL,
 	/* [FIELD_TYPE_VARBINARY] =  */ 1U << MP_BIN,
diff --git a/src/box/key_def.h b/src/box/key_def.h
index 03d6718a77..942e8868da 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -1071,6 +1071,7 @@ tuple_compare_with_key(struct tuple *tuple, hint_t tuple_hint,
 
 /**
  * Compute hash of a tuple field.
+ * @param type - type of the field key part
  * @param ph1 - pointer to running hash
  * @param pcarry - pointer to carry
  * @param field - pointer to field data
@@ -1082,7 +1083,7 @@ tuple_compare_with_key(struct tuple *tuple, hint_t tuple_hint,
  */
 uint32_t
 tuple_hash_field(uint32_t *ph1, uint32_t *pcarry, const char **field,
-		 struct coll *coll);
+		 enum field_type type, struct coll *coll);
 
 /**
  * Compute hash of a key part.
diff --git a/src/box/tuple_bloom.c b/src/box/tuple_bloom.c
index 25fca26861..f92ace38c4 100644
--- a/src/box/tuple_bloom.c
+++ b/src/box/tuple_bloom.c
@@ -141,6 +141,7 @@ tuple_bloom_builder_add_key(struct tuple_bloom_builder *builder,
 
 	for (uint32_t i = 0; i < key_def->part_count; i++) {
 		total_size += tuple_hash_field(&h, &carry, &key,
+					       key_def->parts[i].type,
 					       key_def->parts[i].coll);
 		uint32_t hash = PMurHash32_Result(h, carry, total_size);
 		if (tuple_hash_array_add(&builder->parts[i], hash) != 0)
@@ -249,6 +250,7 @@ tuple_bloom_maybe_has_key(const struct tuple_bloom *bloom,
 
 	for (uint32_t i = 0; i < part_count; i++) {
 		total_size += tuple_hash_field(&h, &carry, &key,
+					       key_def->parts[i].type,
 					       key_def->parts[i].coll);
 		uint32_t hash = PMurHash32_Result(h, carry, total_size);
 		if (!bloom_maybe_has(&bloom->parts[i], hash))
diff --git a/src/box/tuple_compare.cc b/src/box/tuple_compare.cc
index fc34797a42..83d0cc0b11 100644
--- a/src/box/tuple_compare.cc
+++ b/src/box/tuple_compare.cc
@@ -105,6 +105,23 @@ mp_extension_class(const char *data)
 	return mp_ext_classes[type];
 }
 
+/*
+ * @brief Read the msgpack value at \p field (whatever it is: int, uint, float
+ *        or double) as double.
+ * @param field the field to read the value from
+ * @retval the read-and-cast result
+ */
+static double
+mp_read_as_double(const char *field)
+{
+	double result = NAN;
+	assert(mp_classof(mp_typeof(*field)) == MP_CLASS_NUMBER);
+	/* This can only fail on non-numeric msgpack field, so it shouldn't. */
+	if (mp_read_double_lossy(&field, &result) == -1)
+		unreachable();
+	return result;
+}
+
 static int
 mp_compare_bool(const char *field_a, const char *field_b)
 {
@@ -114,10 +131,10 @@ mp_compare_bool(const char *field_a, const char *field_b)
 }
 
 static int
-mp_compare_double(const char *field_a, const char *field_b)
+mp_compare_as_double(const char *field_a, const char *field_b)
 {
-	double a_val = mp_decode_double(&field_a);
-	double b_val = mp_decode_double(&field_b);
+	double a_val = mp_read_as_double(field_a);
+	double b_val = mp_read_as_double(field_b);
 	return COMPARE_RESULT(a_val, b_val);
 }
 
@@ -492,7 +509,7 @@ tuple_compare_field(const char *field_a, const char *field_b,
 	case FIELD_TYPE_NUMBER:
 		return mp_compare_number(field_a, field_b);
 	case FIELD_TYPE_DOUBLE:
-		return mp_compare_double(field_a, field_b);
+		return mp_compare_as_double(field_a, field_b);
 	case FIELD_TYPE_BOOLEAN:
 		return mp_compare_bool(field_a, field_b);
 	case FIELD_TYPE_VARBINARY:
@@ -532,7 +549,7 @@ tuple_compare_field_with_type(const char *field_a, enum mp_type a_type,
 		return mp_compare_number_with_type(field_a, a_type,
 						   field_b, b_type);
 	case FIELD_TYPE_DOUBLE:
-		return mp_compare_double(field_a, field_b);
+		return mp_compare_as_double(field_a, field_b);
 	case FIELD_TYPE_BOOLEAN:
 		return mp_compare_bool(field_a, field_b);
 	case FIELD_TYPE_VARBINARY:
@@ -1837,8 +1854,7 @@ field_hint_integer(const char *field)
 static inline hint_t
 field_hint_double(const char *field)
 {
-	assert(mp_typeof(*field) == MP_DOUBLE);
-	return hint_double(mp_decode_double(&field));
+	return hint_double(mp_read_as_double(field));
 }
 
 static inline hint_t
diff --git a/src/box/tuple_hash.cc b/src/box/tuple_hash.cc
index 9d6f9ba10c..1c921dda12 100644
--- a/src/box/tuple_hash.cc
+++ b/src/box/tuple_hash.cc
@@ -46,6 +46,17 @@ template <int TYPE>
 static inline uint32_t
 field_hash(uint32_t *ph, uint32_t *pcarry, const char **field)
 {
+	/*
+	 * For string key field we should hash the string contents excluding
+	 * MsgPack format identifier. The overload is defined below.
+	 */
+	static_assert(TYPE != FIELD_TYPE_STRING, "See the comment above.");
+	/*
+	 * For double key field we should cast the MsgPack value (whatever it
+	 * is: int, uint, float or double) to double, encode it as msgpack
+	 * double and hash it. No overload for it now.
+	 */
+	static_assert(TYPE != FIELD_TYPE_DOUBLE, "See the comment above.");
 	/*
 	* (!) All fields, except TYPE_STRING hashed **including** MsgPack format
 	* identifier (e.g. 0xcc). This was done **intentionally**
@@ -221,7 +232,7 @@ template <bool has_optional_parts, bool has_json_paths>
 uint32_t
 tuple_hash_slowpath(struct tuple *tuple, struct key_def *key_def);
 
-uint32_t
+static uint32_t
 key_hash_slowpath(const char *key, struct key_def *key_def);
 
 void
@@ -276,12 +287,41 @@ key_def_set_hash_func(struct key_def *key_def) {
 
 uint32_t
 tuple_hash_field(uint32_t *ph1, uint32_t *pcarry, const char **field,
-		 struct coll *coll)
+		 enum field_type type, struct coll *coll)
 {
-	char buf[9]; /* enough to store MP_INT/MP_UINT */
+	char buf[9]; /* enough to store MP_INT/MP_UINT/MP_DOUBLE */
 	const char *f = *field;
 	uint32_t size;
 
+	/*
+	 * MsgPack values of double key field are casted to double, encoded
+	 * as msgpack double and hashed. This assures the same value being
+	 * written as int, uint, float or double has the same hash for this
+	 * type of key.
+	 *
+	 * We create and hash msgpack instead of just hashing the double itself
+	 * for backward compatibility: so a user having a vinyl database with
+	 * double-key index won't have to rebuild it after tarantool update.
+	 *
+	 * XXX: It looks like something like this should also be done for
+	 * number key fields so that e. g. zero given as int, uint, float,
+	 * double and decimal have the same hash.
+	 */
+	if (type == FIELD_TYPE_DOUBLE) {
+		double value;
+		/*
+		 * This will only fail if the mp_type is not numeric, which is
+		 * impossible here (see field_mp_plain_type_is_compatible).
+		 */
+		if (mp_read_double_lossy(&f, &value) == -1)
+			unreachable();
+		char *double_msgpack_end = mp_encode_double(buf, value);
+		size = double_msgpack_end - buf;
+		assert(size <= sizeof(buf));
+		PMurHash32_Process(ph1, pcarry, buf, size);
+		return size;
+	}
+
 	switch (mp_typeof(**field)) {
 	case MP_STR:
 		/*
@@ -355,7 +395,7 @@ tuple_hash_key_part(uint32_t *ph1, uint32_t *pcarry, struct tuple *tuple,
 	const char *field = tuple_field_by_part(tuple, part, multikey_idx);
 	if (field == NULL)
 		return tuple_hash_null(ph1, pcarry);
-	return tuple_hash_field(ph1, pcarry, &field, part->coll);
+	return tuple_hash_field(ph1, pcarry, &field, part->type, part->coll);
 }
 
 template <bool has_optional_parts, bool has_json_paths>
@@ -386,6 +426,7 @@ tuple_hash_slowpath(struct tuple *tuple, struct key_def *key_def)
 		total_size += tuple_hash_null(&h, &carry);
 	} else {
 		total_size += tuple_hash_field(&h, &carry, &field,
+					       key_def->parts[0].type,
 					       key_def->parts[0].coll);
 	}
 	for (uint32_t part_id = 1; part_id < key_def->part_count; part_id++) {
@@ -409,6 +450,7 @@ tuple_hash_slowpath(struct tuple *tuple, struct key_def *key_def)
 		} else {
 			total_size +=
 				tuple_hash_field(&h, &carry, &field,
+						 key_def->parts[part_id].type,
 						 key_def->parts[part_id].coll);
 		}
 		prev_fieldno = key_def->parts[part_id].fieldno;
@@ -417,7 +459,7 @@ tuple_hash_slowpath(struct tuple *tuple, struct key_def *key_def)
 	return PMurHash32_Result(h, carry, total_size);
 }
 
-uint32_t
+static uint32_t
 key_hash_slowpath(const char *key, struct key_def *key_def)
 {
 	uint32_t h = HASH_SEED;
@@ -426,7 +468,8 @@ key_hash_slowpath(const char *key, struct key_def *key_def)
 
 	for (struct key_part *part = key_def->parts;
 	     part < key_def->parts + key_def->part_count; part++) {
-		total_size += tuple_hash_field(&h, &carry, &key, part->coll);
+		total_size += tuple_hash_field(&h, &carry, &key, part->type,
+					       part->coll);
 	}
 
 	return PMurHash32_Result(h, carry, total_size);
diff --git a/test/engine-luatest/gh_7483_insert_int_into_double_field_test.lua b/test/engine-luatest/gh_7483_insert_int_into_double_field_test.lua
new file mode 100644
index 0000000000..56e1044c6f
--- /dev/null
+++ b/test/engine-luatest/gh_7483_insert_int_into_double_field_test.lua
@@ -0,0 +1,67 @@
+local server = require('luatest.server')
+local t = require('luatest')
+
+local g = t.group('gh-7483-insert-int-into-double-field-test', {
+    {engine = 'memtx', index_type = 'tree'},
+    {engine = 'memtx', index_type = 'hash'},
+    {engine = 'vinyl', index_type = 'tree'},
+})
+
+g.before_all(function(cg)
+    cg.server = server:new()
+    cg.server:start()
+end)
+
+g.after_all(function(cg)
+    cg.server:drop()
+end)
+
+g.after_each(function(cg)
+    cg.server:exec(function()
+        if box.space.s ~= nil then
+            box.space.s:drop()
+        end
+    end)
+end)
+
+g.test_insert_int_into_double_field = function(cg)
+    cg.server:exec(function (engine, index_type)
+        local ffi = require('ffi')
+
+        local s = box.schema.space.create('s', { engine = engine })
+        s:create_index('s_idx', {type = index_type, parts = {1, 'double'}})
+
+        -- For double-typed key we cast it to double before hashing.
+        -- So 1 and 1.0 are interpret as the same value.
+        local int_1 = 1
+        local double_1 = ffi.new('double', 1.0)
+        local errdup = 'Duplicate key exists in unique index "s_idx" in space '
+                       ..'"s" with old tuple - '
+
+        -- Ok, new integer (1).
+        s:insert({int_1})
+
+        -- Fail, duplicate of 1.
+        t.assert_error_msg_contains(errdup, s.insert, s, {double_1})
+
+        -- Select the integer.
+        t.assert_equals(s:select({double_1}), {{int_1}});
+
+        -- 2 ** 63 - 1 can't be directly converted to double,
+        -- it becomes 2 ** 63 on cast.
+        local int_2e63_minus_1 = 0x7fffffffffffffffULL
+        local double_2e63 = ffi.new('double', int_2e63_minus_1)
+
+        -- Successfully insert the integer into the double field.
+        s:insert({int_2e63_minus_1})
+
+        -- Insert of double-casted integer should fail since it's recognized as
+        -- a duplicate of the integer inserted above (despite the fact that
+        -- integer is not exactly equal to the resulting double, it becomes
+        -- equal on cast to double in index).
+        t.assert_error_msg_contains(errdup, s.insert, s, {double_2e63})
+
+        -- Select of the double should retrieve the inserted integer.
+        t.assert_equals(s:select({double_2e63}), {{int_2e63_minus_1}})
+    end, {cg.params.engine, cg.params.index_type})
+end
diff --git a/test/engine/insert.result b/test/engine/insert.result
index ffff3df85d..f8d128e0cb 100644
--- a/test/engine/insert.result
+++ b/test/engine/insert.result
@@ -746,8 +746,7 @@ _ = s:create_index('ii')
 ---
 ...
 --
--- If number of Lua type NUMBER is not integer, than it could be
--- inserted in DOUBLE field.
+-- A number of Lua type NUMBER can be inserted in DOUBLE field.
 --
 s:insert({1, 1.1})
 ---
@@ -762,33 +761,31 @@ s:insert({3, -3.0009})
 - [3, -3.0009]
 ...
 --
--- Integers of Lua type NUMBER and CDATA of type int64 or uint64
--- cannot be inserted into this field.
+-- Integers of Lua type NUMBER and CDATA of type int64 or uint64 can
+-- also be inserted even if they can't be loselessly converted to
+-- double. They're stored in the space "as is".
 --
 s:insert({4, 1})
 ---
-- error: 'Tuple field 2 (d) type does not match one required by operation: expected
-    double, got unsigned'
+- [4, 1]
 ...
-s:insert({5, -9223372036854775800ULL})
+s:insert({5, 9223372036854775800ULL})
 ---
-- error: 'Tuple field 2 (d) type does not match one required by operation: expected
-    double, got unsigned'
+- [5, 9223372036854775800]
 ...
 s:insert({6, 18000000000000000000ULL})
 ---
-- error: 'Tuple field 2 (d) type does not match one required by operation: expected
-    double, got unsigned'
+- [6, 18000000000000000000]
 ...
 --
--- To insert an integer, we must cast it to a CDATA of type DOUBLE
--- using ffi.cast(). Non-integers can also be inserted this way.
+-- One can also cast a value to CDATA of type DOUBLE using ffi.cast().
+-- Non-integers can also be inserted this way.
 --
 s:insert({7, ffi.cast('double', 1)})
 ---
 - [7, 1]
 ...
-s:insert({8, ffi.cast('double', -9223372036854775808)})
+s:insert({8, ffi.cast('double', -9223372036854775800LL)})
 ---
 - [8, -9223372036854775808]
 ...
@@ -813,6 +810,9 @@ s:select()
 - - [1, 1.1]
   - [2, 2.5]
   - [3, -3.0009]
+  - [4, 1]
+  - [5, 9223372036854775800]
+  - [6, 18000000000000000000]
   - [7, 1]
   - [8, -9223372036854775808]
   - [9, 123]
@@ -829,13 +829,13 @@ dd:select(1.1)
 - - [1, 1.1]
   - [11, 1.1]
 ...
-dd:select(1)
+dd:select(-9223372036854775800LL)
 ---
-- error: 'Supplied key type of part 0 does not match index part type: expected double'
+- - [8, -9223372036854775808]
 ...
-dd:select(ffi.cast('double', 1))
+dd:select(ffi.cast('double', -9223372036854775800LL))
 ---
-- - [7, 1]
+- - [8, -9223372036854775808]
 ...
 -- Make sure the comparisons work correctly.
 dd:select(1.1, {iterator = 'ge'})
@@ -844,6 +844,8 @@ dd:select(1.1, {iterator = 'ge'})
   - [11, 1.1]
   - [2, 2.5]
   - [9, 123]
+  - [5, 9223372036854775800]
+  - [6, 18000000000000000000]
   - [10, 18000000000000000000]
 ...
 dd:select(1.1, {iterator = 'le'})
@@ -851,6 +853,7 @@ dd:select(1.1, {iterator = 'le'})
 - - [11, 1.1]
   - [1, 1.1]
   - [7, 1]
+  - [4, 1]
   - [12, -3.0009]
   - [3, -3.0009]
   - [8, -9223372036854775808]
@@ -859,11 +862,14 @@ dd:select(ffi.cast('double', 1.1), {iterator = 'gt'})
 ---
 - - [2, 2.5]
   - [9, 123]
+  - [5, 9223372036854775800]
+  - [6, 18000000000000000000]
   - [10, 18000000000000000000]
 ...
 dd:select(ffi.cast('double', 1.1), {iterator = 'lt'})
 ---
 - - [7, 1]
+  - [4, 1]
   - [12, -3.0009]
   - [3, -3.0009]
   - [8, -9223372036854775808]
@@ -874,6 +880,8 @@ dd:select(1.1, {iterator = 'all'})
   - [11, 1.1]
   - [2, 2.5]
   - [9, 123]
+  - [5, 9223372036854775800]
+  - [6, 18000000000000000000]
   - [10, 18000000000000000000]
 ...
 dd:select(1.1, {iterator = 'eq'})
@@ -886,6 +894,14 @@ dd:select(1.1, {iterator = 'req'})
 - - [11, 1.1]
   - [1, 1.1]
 ...
+s:delete(4)
+---
+- [4, 1]
+...
+s:delete(6)
+---
+- [6, 18000000000000000000]
+...
 s:delete(11)
 ---
 - [11, 1.1]
@@ -900,31 +916,27 @@ ddd = s:create_index('ddd', {parts = {{2, 'double'}}})
 ...
 s:update(1, {{'=', 2, 2}})
 ---
-- error: 'Tuple field 2 (d) type does not match one required by operation: expected
-    double, got unsigned'
+- [1, 2]
 ...
 s:insert({22, 22})
 ---
-- error: 'Tuple field 2 (d) type does not match one required by operation: expected
-    double, got unsigned'
+- [22, 22]
 ...
 s:upsert({10, 100}, {{'=', 2, 2}})
 ---
-- error: 'Tuple field 2 (d) type does not match one required by operation: expected
-    double, got unsigned'
+- error: Duplicate key exists in unique index "ddd" in space "s" with old tuple -
+    [1, 2] and new tuple - [10, 2]
 ...
-s:upsert({100, 100}, {{'=', 2, 2}})
+s:upsert({101, 100}, {{'=', 2, 11}})
 ---
-- error: 'Tuple field 2 (d) type does not match one required by operation: expected
-    double, got unsigned'
 ...
 ddd:update(1, {{'=', 1, 70}})
 ---
-- error: 'Supplied key type of part 0 does not match index part type: expected double'
+- error: Attempt to modify a tuple field which is part of primary index in space 's'
 ...
 ddd:delete(1)
 ---
-- error: 'Supplied key type of part 0 does not match index part type: expected double'
+- [7, 1]
 ...
 s:update(2, {{'=', 2, 2.55}})
 ---
@@ -948,13 +960,13 @@ s:get(10)
 ---
 - [10, 2.2]
 ...
-ddd:update(1.1, {{'=', 3, 111}})
+ddd:update(2, {{'=', 3, 111}})
 ---
-- [1, 1.1, 111]
+- [1, 2, 111]
 ...
-ddd:delete(1.1)
+ddd:delete(2)
 ---
-- [1, 1.1, 111]
+- [1, 2, 111]
 ...
 s:update(2, {{'=', 2, ffi.cast('double', 255)}})
 ---
@@ -978,9 +990,9 @@ s:get(200)
 ---
 - [200, 222]
 ...
-ddd:update(ffi.cast('double', 1), {{'=', 3, 7}})
+ddd:update(22, {{'=', 3, 7}})
 ---
-- [7, 1, 7]
+- [22, 22, 7]
 ...
 ddd:delete(ffi.cast('double', 123))
 ---
@@ -990,11 +1002,12 @@ s:select()
 ---
 - - [2, 255]
   - [3, -3.0009]
-  - [7, 1, 7]
+  - [5, 9223372036854775800]
   - [8, -9223372036854775808]
   - [10, 2.2]
-  - [22, 22]
+  - [22, 22, 7]
   - [100, 100.5]
+  - [101, 100]
   - [200, 222]
 ...
 s:drop()
diff --git a/test/engine/insert.test.lua b/test/engine/insert.test.lua
index 2ffc8cd0a0..31b6bffc66 100644
--- a/test/engine/insert.test.lua
+++ b/test/engine/insert.test.lua
@@ -130,27 +130,27 @@ s = box.schema.space.create('s', {format = {{'i', 'integer'}, {'d', 'double'}}})
 _ = s:create_index('ii')
 
 --
--- If number of Lua type NUMBER is not integer, than it could be
--- inserted in DOUBLE field.
+-- A number of Lua type NUMBER can be inserted in DOUBLE field.
 --
 s:insert({1, 1.1})
 s:insert({2, 2.5})
 s:insert({3, -3.0009})
 
 --
--- Integers of Lua type NUMBER and CDATA of type int64 or uint64
--- cannot be inserted into this field.
+-- Integers of Lua type NUMBER and CDATA of type int64 or uint64 can
+-- also be inserted even if they can't be loselessly converted to
+-- double. They're stored in the space "as is".
 --
 s:insert({4, 1})
-s:insert({5, -9223372036854775800ULL})
+s:insert({5, 9223372036854775800ULL})
 s:insert({6, 18000000000000000000ULL})
 
 --
--- To insert an integer, we must cast it to a CDATA of type DOUBLE
--- using ffi.cast(). Non-integers can also be inserted this way.
+-- One can also cast a value to CDATA of type DOUBLE using ffi.cast().
+-- Non-integers can also be inserted this way.
 --
 s:insert({7, ffi.cast('double', 1)})
-s:insert({8, ffi.cast('double', -9223372036854775808)})
+s:insert({8, ffi.cast('double', -9223372036854775800LL)})
 s:insert({9, ffi.cast('double', tonumber('123'))})
 s:insert({10, ffi.cast('double', tonumber64('18000000000000000000'))})
 s:insert({11, ffi.cast('double', 1.1)})
@@ -161,8 +161,8 @@ s:select()
 -- The same rules apply to the key of this field:
 dd = s:create_index('dd', {unique = false, parts = {{2, 'double'}}})
 dd:select(1.1)
-dd:select(1)
-dd:select(ffi.cast('double', 1))
+dd:select(-9223372036854775800LL)
+dd:select(ffi.cast('double', -9223372036854775800LL))
 
 -- Make sure the comparisons work correctly.
 dd:select(1.1, {iterator = 'ge'})
@@ -173,6 +173,8 @@ dd:select(1.1, {iterator = 'all'})
 dd:select(1.1, {iterator = 'eq'})
 dd:select(1.1, {iterator = 'req'})
 
+s:delete(4)
+s:delete(6)
 s:delete(11)
 s:delete(12)
 
@@ -182,7 +184,7 @@ ddd = s:create_index('ddd', {parts = {{2, 'double'}}})
 s:update(1, {{'=', 2, 2}})
 s:insert({22, 22})
 s:upsert({10, 100}, {{'=', 2, 2}})
-s:upsert({100, 100}, {{'=', 2, 2}})
+s:upsert({101, 100}, {{'=', 2, 11}})
 
 ddd:update(1, {{'=', 1, 70}})
 ddd:delete(1)
@@ -194,8 +196,8 @@ s:get(100)
 s:upsert({10, 100.5}, {{'=', 2, 2.2}})
 s:get(10)
 
-ddd:update(1.1, {{'=', 3, 111}})
-ddd:delete(1.1)
+ddd:update(2, {{'=', 3, 111}})
+ddd:delete(2)
 
 s:update(2, {{'=', 2, ffi.cast('double', 255)}})
 s:replace({22, ffi.cast('double', 22)})
@@ -204,7 +206,7 @@ s:get(200)
 s:upsert({200, ffi.cast('double', 200)}, {{'=', 2, ffi.cast('double', 222)}})
 s:get(200)
 
-ddd:update(ffi.cast('double', 1), {{'=', 3, 7}})
+ddd:update(22, {{'=', 3, 7}})
 ddd:delete(ffi.cast('double', 123))
 
 s:select()
diff --git a/test/sql-tap/cast.test.lua b/test/sql-tap/cast.test.lua
index 55f2d26907..8b1f53d3d8 100755
--- a/test/sql-tap/cast.test.lua
+++ b/test/sql-tap/cast.test.lua
@@ -1145,12 +1145,15 @@ test:do_catchsql_test(
     })
 
 -- Make sure that search using index in field type number work right.
+-- NOTE: Both 9999999999999999 and 10000000000000001 become the same value
+-- in index comparison because of double-cast, so we use -or-equal variants
+-- of iterators. Don't expect to have precise comparisons in double indexes.
 test:do_execsql_test(
     "cast-11",
     [[
         CREATE TABLE t6(d DOUBLE PRIMARY KEY);
         INSERT INTO t6 VALUES(10000000000000000);
-        SELECT d FROM t6 WHERE d < 10000000000000001 and d > 9999999999999999;
+        SELECT d FROM t6 WHERE d <= 10000000000000001 and d >= 9999999999999999;
         DROP TABLE t6;
     ]], {
         10000000000000000
diff --git a/test/vinyl/upsert.result b/test/vinyl/upsert.result
index 2db448c56c..a2df181e37 100644
--- a/test/vinyl/upsert.result
+++ b/test/vinyl/upsert.result
@@ -1187,12 +1187,12 @@ box.snapshot()
 ---
 - ok
 ...
--- Can't assign integer to float field. First operation is still applied.
+-- Can assign integer to float field.
 --
-s:upsert({1, 1, 1, 2.5, decimal.new(1.1)}, {{'+', 4, 4}})
+s:upsert({1, 1, 1, 2.5, decimal.new(1.1)}, {{'=', 4, 4}})
 ---
 ...
-s:upsert({1, 1, 1, 2.5, decimal.new(1.1)}, {{'=', 4, 4}})
+s:upsert({1, 1, 1, 2.5, decimal.new(1.1)}, {{'+', 4, 4}})
 ---
 ...
 -- Can't add floating point to integer (result is floating point).
@@ -1209,7 +1209,7 @@ box.snapshot()
 ...
 s:select()
 ---
-- - [1, 1, 1, 5.1, 1.1]
+- - [1, 1, 1, 8, 1.1]
   - [2, 6, 1, 1.1, 1.1]
 ...
 -- Integer overflow check.
@@ -1234,7 +1234,7 @@ box.snapshot()
 ...
 s:select()
 ---
-- - [1, 1, 9223372036854775809, 5.1, 1.1]
+- - [1, 1, 9223372036854775809, 8, 1.1]
   - [2, 8, 1, 1.1, 1.1]
 ...
 -- Decimals do not fit into numerics and vice versa.
@@ -1257,7 +1257,7 @@ box.snapshot()
 ...
 s:select()
 ---
-- - [1, 1, 9223372036854775809, 5.1, 2.1]
+- - [1, 1, 9223372036854775809, 8, 2.1]
   - [2, 8, 1, 1.1, 1.1]
 ...
 s:drop()
diff --git a/test/vinyl/upsert.test.lua b/test/vinyl/upsert.test.lua
index 4d0e35920f..30bfa799ec 100644
--- a/test/vinyl/upsert.test.lua
+++ b/test/vinyl/upsert.test.lua
@@ -495,10 +495,10 @@ pk = s:create_index('pk')
 s:replace{1, 1, 1, 1.1, decimal.new(1.1) }
 s:replace{2, 1, 1, 1.1, decimal.new(1.1)}
 box.snapshot()
--- Can't assign integer to float field. First operation is still applied.
+-- Can assign integer to float field.
 --
-s:upsert({1, 1, 1, 2.5, decimal.new(1.1)}, {{'+', 4, 4}})
 s:upsert({1, 1, 1, 2.5, decimal.new(1.1)}, {{'=', 4, 4}})
+s:upsert({1, 1, 1, 2.5, decimal.new(1.1)}, {{'+', 4, 4}})
 -- Can't add floating point to integer (result is floating point).
 --
 s:upsert({2, 1, 1, 2.5, decimal.new(1.1)}, {{'+', 2, 5}})
-- 
GitLab