[PATCH v3 6/6] decimal: allow to index decimals

Serge Petrenko sergepetrenko at tarantool.org
Tue Aug 20 20:10:04 MSK 2019


Closes #4333

@TarantoolBot document
Title: Document decimal field type.

Decimals may now be stored in spaces. A corresponding field type is
introduced: 'decimal'. Decimal values are also allowed in 'scalar',
'any' and 'number' fields.

'decimal' field type is appropriate for both memtx HASH and TREE
indices, as well as for vinyl TREE index.

```
To create an index 'pk' over a decimal field, say
```
tarantool> box.space.test:create_index('pk', {parts={1, 'decimal'}})
---
- unique: true
  parts:
  - type: decimal
    is_nullable: false
    fieldno: 1
  id: 0
  space_id: 512
  type: TREE
  name: pk
...
```
Now you can insert some decimal values:
```
tarantool> for i = 1,10 do
         > box.space.test:insert{decimal.new((i-5)/10)}
         > end
---
...
```
tarantool> box.space.test:select{}
---
- - [-0.4]
  - [-0.3]
  - [-0.2]
  - [-0.1]
  - [0]
  - [0.1]
  - [0.2]
  - [0.3]
  - [0.4]
  - [0.5]
...
```
Decimals may alse be inserted into `scalar` and `number` fields. In this
case all the number values are sorted correctly:
```
tarantool> box.schema.space.create('test')
tarantool> box.space.test:create_index('pk', {parts={1, 'number'}})
tarantool> box.space.test:insert{-1.0001, 'number'}
---
- [-1.0001, 'number']
...

tarantool> box.space.test:insert{decimal.new(-1.00001), 'decimal'}
---
- [-1.00001, 'decimal']
...

tarantool> box.space.test:insert{-1, 'number'}
---
- [-1, 'number']
...

tarantool> box.space.test:insert{decimal.new(-0.999), 'decimal'}
---
- [-0.999, 'decimal']
...

tarantool> box.space.test:insert{-0.998, 'number'}
---
- [-0.998, 'number']
...

tarantool> box.space.test:insert{-0.9, 'number'}
---
- [-0.9, 'number']
...

tarantool> box.space.test:insert{-0.95, 'number'}
---
- [-0.95, 'number']
...

tarantool> box.space.test:insert{decimal.new(-0.92), 'decimal'}
---
- [-0.92, 'decimal']
...

tarantool> box.space.test:insert{decimal.new(-0.971), 'decimal'}
---
- [-0.971, 'decimal']
...

tarantool> box.space.test:select{}
---
- - [-1.0001, 'number']
  - [-1.00001, 'decimal']
  - [-1, 'number']
  - [-0.999, 'decimal']
  - [-0.998, 'number']
  - [-0.971, 'decimal']
  - [-0.95, 'number']
  - [-0.92, 'decimal']
  - [-0.9, 'number']
...

```
Uniqueness is also preserved between decimals and other number types:
```
tarantool> box.space.test:insert{-0.92}
---
- error: Duplicate key exists in unique index 'pk' in space 'test'
...

tarantool> box.space.test:insert{decimal.new(-0.9)}
---
- error: Duplicate key exists in unique index 'pk' in space 'test'
...

```

You can also set decimal fields in space format:
```
tarantool> _ = box.schema.space.create('test')
---
...

tarantool> _ = box.space.test:create_index('pk')
---
...

tarantool> box.space.test:format{{name='id', type='unsigned'}, {name='balance', type='decimal'}}
---
...

tarantool> box.space.test:insert{1}
---
- error: Tuple field 2 required by space format is missing
...

tarantool> box.space.test:insert{1, 'string'}
---
- error: 'Tuple field 2 type does not match one required by operation: expected decimal'
...

tarantool> box.space.test:insert{1, 1.2345}
---
- error: 'Tuple field 2 type does not match one required by operation: expected decimal'
...

tarantool> box.space.test:insert{1, decimal.new('1337.420')}
---
- [1, 1337.420]
...

```
---
 src/box/field_def.c           |  45 ++-
 src/box/field_def.h           |  23 +-
 src/box/key_def.h             |   3 +-
 src/box/tuple_compare.cc      | 198 ++++++++-
 src/box/tuple_format.c        |   3 +-
 src/lib/core/decimal.h        |   8 +
 src/lib/core/mp_decimal.h     |   8 +
 test/engine/ddl.result        |  85 +++-
 test/engine/ddl.test.lua      |  40 +-
 test/engine/decimal.result    | 275 +++++++++++++
 test/engine/decimal.test.lua  |  83 ++++
 test/engine/iterator.result   | 730 ++++++++++++++++++++++++++++++++++
 test/engine/iterator.test.lua |  22 +
 13 files changed, 1496 insertions(+), 27 deletions(-)

diff --git a/src/box/field_def.c b/src/box/field_def.c
index 346042b98..da766d586 100644
--- a/src/box/field_def.c
+++ b/src/box/field_def.c
@@ -32,6 +32,7 @@
 #include "field_def.h"
 #include "trivia/util.h"
 #include "key_def.h"
+#include "mp_extension_types.h"
 
 const char *mp_type_strs[] = {
 	/* .MP_NIL    = */ "nil",
@@ -47,6 +48,11 @@ const char *mp_type_strs[] = {
 	/* .MP_EXT    = */ "extension",
 };
 
+/*
+ * messagepack types supported by given field types.
+ * MP_EXT requires to parse extension type to check
+ * compatibility with field type.
+ */
 const uint32_t field_mp_type[] = {
 	/* [FIELD_TYPE_ANY]      =  */ UINT32_MAX,
 	/* [FIELD_TYPE_UNSIGNED] =  */ 1U << MP_UINT,
@@ -59,10 +65,25 @@ const uint32_t field_mp_type[] = {
 	/* [FIELD_TYPE_SCALAR]   =  */ (1U << MP_UINT) | (1U << MP_INT) |
 		(1U << MP_FLOAT) | (1U << MP_DOUBLE) | (1U << MP_STR) |
 		(1U << MP_BIN) | (1U << MP_BOOL),
+	/* [FIELD_TYPE_DECIMAL]  =  */ 0, /* only MP_DECIMAL is supported */
 	/* [FIELD_TYPE_ARRAY]    =  */ 1U << MP_ARRAY,
 	/* [FIELD_TYPE_MAP]      =  */ (1U << MP_MAP),
 };
 
+const uint32_t field_ext_type[] = {
+	/* [FIELD_TYPE_ANY]       = */ UINT32_MAX ^ (1U << MP_UNKNOWN_EXTENSION),
+	/* [FIELD_TYPE_UNSIGNED]  = */ 0,
+	/* [FIELD_TYPE_STRING]    = */ 0,
+	/* [FIELD_TYPE_NUMBER]    = */ 1U << MP_DECIMAL,
+	/* [FIELD_TYPE_INTEGER]   = */ 0,
+	/* [FIELD_TYPE_BOOLEAN]   = */ 0,
+	/* [FIELD_TYPE_VARBINARY] = */ 0,
+	/* [FIELD_TYPE_SCALAR]    = */ 1U << MP_DECIMAL,
+	/* [FIELD_TYPE_DECIMAL]   = */ 1U << MP_DECIMAL,
+	/* [FIELD_TYPE_ARRAY]     = */ 0,
+	/* [FIELD_TYPE_MAP]       = */ 0,
+};
+
 const char *field_type_strs[] = {
 	/* [FIELD_TYPE_ANY]      = */ "any",
 	/* [FIELD_TYPE_UNSIGNED] = */ "unsigned",
@@ -72,6 +93,7 @@ const char *field_type_strs[] = {
 	/* [FIELD_TYPE_BOOLEAN]  = */ "boolean",
 	/* [FIELD_TYPE_VARBINARY] = */"varbinary",
 	/* [FIELD_TYPE_SCALAR]   = */ "scalar",
+	/* [FIELD_TYPE_DECIMAL]  = */ "decimal",
 	/* [FIELD_TYPE_ARRAY]    = */ "array",
 	/* [FIELD_TYPE_MAP]      = */ "map",
 };
@@ -98,17 +120,18 @@ field_type_by_name_wrapper(const char *str, uint32_t len)
  * values can be stored in the j type.
  */
 static const bool field_type_compatibility[] = {
-	   /*   ANY   UNSIGNED  STRING   NUMBER  INTEGER  BOOLEAN VARBINARY SCALAR   ARRAY     MAP */
-/*   ANY    */ true,   false,   false,   false,   false,   false,   false,  false,   false,   false,
-/* UNSIGNED */ true,   true,    false,   true,    true,    false,   false,  true,    false,   false,
-/*  STRING  */ true,   false,   true,    false,   false,   false,   false,  true,    false,   false,
-/*  NUMBER  */ true,   false,   false,   true,    false,   false,   false,  true,    false,   false,
-/*  INTEGER */ true,   false,   false,   true,    true,    false,   false,  true,    false,   false,
-/*  BOOLEAN */ true,   false,   false,   false,   false,   true,    false,  true,    false,   false,
-/* VARBINARY*/ true,   false,   false,   false,   false,   false,   true,   true,    false,   false,
-/*  SCALAR  */ true,   false,   false,   false,   false,   false,   false,  true,    false,   false,
-/*   ARRAY  */ true,   false,   false,   false,   false,   false,   false,  false,   true,    false,
-/*    MAP   */ true,   false,   false,   false,   false,   false,   false,  false,   false,   true,
+	   /*   ANY   UNSIGNED  STRING   NUMBER  INTEGER  BOOLEAN VARBINARY SCALAR  DECIMAL  ARRAY    MAP  */
+/*   ANY    */ true,   false,   false,   false,   false,   false,   false,  false,  false,  false,   false,
+/* UNSIGNED */ true,   true,    false,   true,    true,    false,   false,  true,   false,  false,   false,
+/*  STRING  */ true,   false,   true,    false,   false,   false,   false,  true,   false,  false,   false,
+/*  NUMBER  */ true,   false,   false,   true,    false,   false,   false,  true,   false,  false,   false,
+/*  INTEGER */ true,   false,   false,   true,    true,    false,   false,  true,   false,  false,   false,
+/*  BOOLEAN */ true,   false,   false,   false,   false,   true,    false,  true,   false,  false,   false,
+/* VARBINARY*/ true,   false,   false,   false,   false,   false,   true,   true,   false,  false,   false,
+/*  SCALAR  */ true,   false,   false,   false,   false,   false,   false,  true,   false,  false,   false,
+/*  DECIMAL */ true,   false,   false,   true,    false,   false,   false,  true,   true,   false,   false,
+/*   ARRAY  */ true,   false,   false,   false,   false,   false,   false,  false,  false,  true,    false,
+/*    MAP   */ true,   false,   false,   false,   false,   false,   false,  false,  false,  false,   true,
 };
 
 bool
diff --git a/src/box/field_def.h b/src/box/field_def.h
index c1a7ec0a9..fdedc9622 100644
--- a/src/box/field_def.h
+++ b/src/box/field_def.h
@@ -58,6 +58,7 @@ enum field_type {
 	FIELD_TYPE_BOOLEAN,
 	FIELD_TYPE_VARBINARY,
 	FIELD_TYPE_SCALAR,
+	FIELD_TYPE_DECIMAL,
 	FIELD_TYPE_ARRAY,
 	FIELD_TYPE_MAP,
 	field_type_MAX
@@ -109,8 +110,9 @@ field_type_by_name(const char *name, size_t len);
 /* MsgPack type names */
 extern const char *mp_type_strs[];
 
-/** A helper table for field_mp_type_is_compatible */
+/** Two helper tables for field_mp_type_is_compatible */
 extern const uint32_t field_mp_type[];
+extern const uint32_t field_ext_type[];
 
 extern const struct opt_def field_def_reg[];
 extern const struct field_def field_def_default;
@@ -144,13 +146,26 @@ struct field_def {
 
 /** Checks if mp_type (MsgPack) is compatible with field type. */
 static inline bool
-field_mp_type_is_compatible(enum field_type type, enum mp_type mp_type,
+field_mp_type_is_compatible(enum field_type type, const char *data,
 			    bool is_nullable)
 {
 	assert(type < field_type_MAX);
+	enum mp_type mp_type = mp_typeof(*data);
 	assert((size_t)mp_type < CHAR_BIT * sizeof(*field_mp_type));
-	uint32_t mask = field_mp_type[type] | (is_nullable * (1U << MP_NIL));
-	return (mask & (1U << mp_type)) != 0;
+	uint32_t mask;
+	if (mp_type != MP_EXT) {
+		mask = field_mp_type[type] | (is_nullable * (1U << MP_NIL));
+		return (mask & (1U << mp_type)) != 0;
+	} else {
+		int8_t ext_type;
+		mp_decode_extl(&data, &ext_type);
+		if (ext_type >= 0) {
+			mask = field_ext_type[type];
+			return (mask & (1U << ext_type)) != 0;
+		} else {
+			return false;
+		}
+	}
 }
 
 static inline bool
diff --git a/src/box/key_def.h b/src/box/key_def.h
index c6e33e4ad..f4d9e76f2 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -528,8 +528,7 @@ static inline int
 key_part_validate(enum field_type key_type, const char *key,
 		  uint32_t field_no, bool is_nullable)
 {
-	if (unlikely(!field_mp_type_is_compatible(key_type, mp_typeof(*key),
-						  is_nullable))) {
+	if (unlikely(!field_mp_type_is_compatible(key_type, key, is_nullable))) {
 		diag_set(ClientError, ER_KEY_PART_TYPE, field_no,
 			 field_type_strs[key_type]);
 		return -1;
diff --git a/src/box/tuple_compare.cc b/src/box/tuple_compare.cc
index b7b54e21a..e621f3c3c 100644
--- a/src/box/tuple_compare.cc
+++ b/src/box/tuple_compare.cc
@@ -33,6 +33,9 @@
 #include "coll/coll.h"
 #include "trivia/util.h" /* NOINLINE */
 #include <math.h>
+#include "lib/core/decimal.h"
+#include "lib/core/mp_decimal.h"
+#include "lib/core/mp_extension_types.h"
 
 /* {{{ tuple_compare */
 
@@ -87,7 +90,12 @@ static enum mp_class mp_classes[] = {
 	/* .MP_BOOL    = */ MP_CLASS_BOOL,
 	/* .MP_FLOAT   = */ MP_CLASS_NUMBER,
 	/* .MP_DOUBLE  = */ MP_CLASS_NUMBER,
-	/* .MP_BIN     = */ MP_CLASS_BIN
+	/* .MP_EXT     = */ mp_class_max,
+};
+
+static enum mp_class mp_ext_classes[] = {
+	/* .MP_UNKNOWN_EXTENSION = */ mp_class_max, /* unsupported */
+	/* .MP_DECIMAL		 = */ MP_CLASS_NUMBER,
 };
 
 #define COMPARE_RESULT(a, b) (a < b ? -1 : a > b)
@@ -98,6 +106,15 @@ mp_classof(enum mp_type type)
 	return mp_classes[type];
 }
 
+static enum mp_class
+mp_extension_class(const char *data)
+{
+	assert(mp_typeof(*data) == MP_EXT);
+	int8_t type;
+	mp_decode_extl(&data, &type);
+	return mp_ext_classes[type];
+}
+
 static int
 mp_compare_bool(const char *field_a, const char *field_b)
 {
@@ -264,13 +281,107 @@ mp_compare_double_any_number(double lhs, const char *rhs,
 	return k * COMPARE_RESULT(lqbit, rqbit);
 }
 
+static int
+mp_compare_decimal(const char *lhs, const char *rhs)
+{
+	decimal_t lhs_dec, rhs_dec;
+	decimal_t *ret;
+	ret = mp_decode_decimal(&lhs, &lhs_dec);
+	assert(ret != NULL);
+	ret = mp_decode_decimal(&rhs, &rhs_dec);
+	assert(ret != NULL);
+	(void)ret;
+	return decimal_compare(&lhs_dec, &rhs_dec);
+
+}
+
+static int
+mp_compare_decimal_any_number(decimal_t *lhs, const char *rhs,
+			      enum mp_type rhs_type, int k)
+{
+	decimal_t rhs_dec;
+	switch (rhs_type) {
+	case MP_FLOAT:
+	{
+		double d = mp_decode_float(&rhs);
+		decimal_from_double(&rhs_dec, d);
+		break;
+	}
+	case MP_DOUBLE:
+	{
+		double d = mp_decode_double(&rhs);
+		decimal_from_double(&rhs_dec, d);
+		break;
+	}
+	case MP_INT:
+	{
+		int64_t num = mp_decode_int(&rhs);
+		decimal_from_int64(&rhs_dec, num);
+		break;
+	}
+	case MP_UINT:
+	{
+		uint64_t num = mp_decode_uint(&rhs);
+		decimal_from_uint64(&rhs_dec, num);
+		break;
+	}
+	case MP_EXT:
+	{
+		int8_t ext_type;
+		uint32_t len = mp_decode_extl(&rhs, &ext_type);
+		switch (ext_type) {
+		case MP_DECIMAL:
+			decimal_unpack(&rhs, len, &rhs_dec);
+			break;
+		default:
+			unreachable();
+		}
+		break;
+	}
+	default:
+		unreachable();
+	}
+	return k * decimal_compare(lhs, &rhs_dec);
+}
+
 static int
 mp_compare_number_with_type(const char *lhs, enum mp_type lhs_type,
 			    const char *rhs, enum mp_type rhs_type)
 {
-	assert(mp_classof(lhs_type) == MP_CLASS_NUMBER);
-	assert(mp_classof(rhs_type) == MP_CLASS_NUMBER);
+	assert(mp_classof(lhs_type) == MP_CLASS_NUMBER ||
+	       mp_extension_class(lhs) == MP_CLASS_NUMBER);
+	assert(mp_classof(rhs_type) == MP_CLASS_NUMBER ||
+	       mp_extension_class(rhs) == MP_CLASS_NUMBER);
 
+	/*
+	 * Test decimals first, so that we don't have to
+	 * account for them in other comparators.
+	 */
+	decimal_t dec;
+	if (rhs_type == MP_EXT) {
+		int8_t ext_type;
+		uint32_t len = mp_decode_extl(&rhs, &ext_type);
+		switch (ext_type) {
+		case MP_DECIMAL:
+			return mp_compare_decimal_any_number(
+				decimal_unpack(&rhs, len, &dec), lhs, lhs_type, -1
+			);
+		default:
+			unreachable();
+		}
+	}
+	if (lhs_type == MP_EXT) {
+		int8_t ext_type;
+		uint32_t len = mp_decode_extl(&lhs, &ext_type);
+		switch (ext_type) {
+		case MP_DECIMAL:
+			return mp_compare_decimal_any_number(
+				decimal_unpack(&lhs, len, &dec), rhs, rhs_type, 1
+			);
+		default:
+			unreachable();
+		}
+	}
 	if (rhs_type == MP_FLOAT) {
 		return mp_compare_double_any_number(
 			mp_decode_float(&rhs), lhs, lhs_type, -1
@@ -348,8 +459,12 @@ static int
 mp_compare_scalar_with_type(const char *field_a, enum mp_type a_type,
 			    const char *field_b, enum mp_type b_type)
 {
-	enum mp_class a_class = mp_classof(a_type);
-	enum mp_class b_class = mp_classof(b_type);
+	enum mp_class a_class = mp_classof(a_type) < mp_class_max ?
+						      mp_classof(a_type) :
+						      mp_extension_class(field_a);
+	enum mp_class b_class = mp_classof(b_type) < mp_class_max ?
+						      mp_classof(b_type) :
+						      mp_extension_class(field_b);
 	if (a_class != b_class)
 		return COMPARE_RESULT(a_class, b_class);
 	mp_compare_f cmp = mp_class_comparators[a_class];
@@ -410,6 +525,8 @@ tuple_compare_field(const char *field_a, const char *field_b,
 		return coll != NULL ?
 		       mp_compare_scalar_coll(field_a, field_b, coll) :
 		       mp_compare_scalar(field_a, field_b);
+	case FIELD_TYPE_DECIMAL:
+		return mp_compare_decimal(field_a, field_b);
 	default:
 		unreachable();
 		return 0;
@@ -443,6 +560,9 @@ tuple_compare_field_with_type(const char *field_a, enum mp_type a_type,
 		       mp_compare_scalar_coll(field_a, field_b, coll) :
 		       mp_compare_scalar_with_type(field_a, a_type,
 						   field_b, b_type);
+	case FIELD_TYPE_DECIMAL:
+		return mp_compare_number_with_type(field_a, a_type,
+						   field_b, b_type);
 	default:
 		unreachable();
 		return 0;
@@ -1502,6 +1622,24 @@ hint_double(double d)
 	return hint_create(MP_CLASS_NUMBER, val);
 }
 
+static inline hint_t
+hint_decimal(decimal_t *dec)
+{
+	uint64_t val = 0;
+	int64_t num;
+	if (decimal_to_int64(dec, &num) &&
+	    num >= HINT_VALUE_INT_MIN && num <= HINT_VALUE_INT_MAX) {
+		val = num - HINT_VALUE_INT_MIN;
+	} else if (!(dec->bits & DECNEG)) {
+		val = HINT_VALUE_MAX;
+	}
+	/*
+	 * In case the number is negative and out of bounds, val
+	 * remains zero.
+	 */
+	return hint_create(MP_CLASS_NUMBER, val);
+}
+
 static inline uint64_t
 hint_str_raw(const char *s, uint32_t len)
 {
@@ -1578,12 +1716,43 @@ field_hint_number(const char *field)
 		return hint_double(mp_decode_float(&field));
 	case MP_DOUBLE:
 		return hint_double(mp_decode_double(&field));
+	case MP_EXT:
+	{
+		int8_t ext_type;
+		uint32_t len = mp_decode_extl(&field, &ext_type);
+		switch (ext_type) {
+		case MP_DECIMAL:
+		{
+			decimal_t dec;
+			return hint_decimal(decimal_unpack(&field, len, &dec));
+		}
+		default:
+			unreachable();
+		}
+	}
 	default:
 		unreachable();
 	}
 	return HINT_NONE;
 }
 
+static inline hint_t
+field_hint_decimal(const char *field)
+{
+	assert(mp_typeof(*field) == MP_EXT);
+	int8_t ext_type;
+	uint32_t len = mp_decode_extl(&field, &ext_type);
+	switch (ext_type) {
+	case MP_DECIMAL:
+	{
+		decimal_t dec;
+		return hint_decimal(decimal_unpack(&field, len, &dec));
+	}
+	default:
+		unreachable();
+	}
+}
+
 static inline hint_t
 field_hint_string(const char *field, struct coll *coll)
 {
@@ -1623,6 +1792,20 @@ field_hint_scalar(const char *field, struct coll *coll)
 	case MP_BIN:
 		len = mp_decode_binl(&field);
 		return hint_bin(field, len);
+	case MP_EXT:
+	{
+		int8_t ext_type;
+		uint32_t len = mp_decode_extl(&field, &ext_type);
+		switch (ext_type) {
+		case MP_DECIMAL:
+		{
+			decimal_t dec;
+			return hint_decimal(decimal_unpack(&field, len, &dec));
+		}
+		default:
+			unreachable();
+		}
+	}
 	default:
 		unreachable();
 	}
@@ -1650,6 +1833,8 @@ field_hint(const char *field, struct coll *coll)
 		return field_hint_varbinary(field);
 	case FIELD_TYPE_SCALAR:
 		return field_hint_scalar(field, coll);
+	case FIELD_TYPE_DECIMAL:
+		return field_hint_decimal(field);
 	default:
 		unreachable();
 	}
@@ -1755,6 +1940,9 @@ key_def_set_hint_func(struct key_def *def)
 	case FIELD_TYPE_SCALAR:
 		key_def_set_hint_func<FIELD_TYPE_SCALAR>(def);
 		break;
+	case FIELD_TYPE_DECIMAL:
+		key_def_set_hint_func<FIELD_TYPE_DECIMAL>(def);
+		break;
 	default:
 		/* Invalid key definition. */
 		def->key_hint = NULL;
diff --git a/src/box/tuple_format.c b/src/box/tuple_format.c
index 514d5d9c0..154535ab1 100644
--- a/src/box/tuple_format.c
+++ b/src/box/tuple_format.c
@@ -1169,8 +1169,7 @@ tuple_format_iterator_next(struct tuple_format_iterator *it,
 	 * defined in format.
 	 */
 	bool is_nullable = tuple_field_is_nullable(field);
-	if (!field_mp_type_is_compatible(field->type, mp_typeof(*entry->data),
-					 is_nullable) != 0) {
+	if (!field_mp_type_is_compatible(field->type, entry->data, is_nullable) != 0) {
 		diag_set(ClientError, ER_FIELD_TYPE,
 			 tuple_field_path(field),
 			 field_type_strs[field->type]);
diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h
index 77b6dbfd0..9a162c73c 100644
--- a/src/lib/core/decimal.h
+++ b/src/lib/core/decimal.h
@@ -37,6 +37,10 @@
 #include "third_party/decNumber/decNumber.h"
 #include <stdint.h>
 
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
 typedef decNumber decimal_t;
 
 /**
@@ -239,4 +243,8 @@ decimal_pack(char *data, const decimal_t *dec);
 decimal_t *
 decimal_unpack(const char **data, uint32_t len, decimal_t *dec);
 
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
 #endif /* TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED */
diff --git a/src/lib/core/mp_decimal.h b/src/lib/core/mp_decimal.h
index a991a5f16..778529068 100644
--- a/src/lib/core/mp_decimal.h
+++ b/src/lib/core/mp_decimal.h
@@ -34,6 +34,10 @@
 #include "decimal.h"
 #include <stdint.h>
 
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
 /**
  * \brief Calculate exact buffer size needed to store a decimal
  * pointed to by \a dec.
@@ -59,4 +63,8 @@ mp_decode_decimal(const char **data, decimal_t *dec);
 char *
 mp_encode_decimal(char *data, const decimal_t *dec);
 
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
 #endif
diff --git a/test/engine/ddl.result b/test/engine/ddl.result
index fa3f6051f..67b22ed9e 100644
--- a/test/engine/ddl.result
+++ b/test/engine/ddl.result
@@ -1034,6 +1034,9 @@ s:drop()
 --
 -- gh-2800: space formats checking is broken.
 --
+decimal = require('decimal')
+---
+...
 -- Ensure that vinyl correctly process field count change.
 s = box.schema.space.create('test', {engine = engine, field_count = 2})
 ---
@@ -1092,13 +1095,16 @@ format[9] = {name = 'field9', type = 'array'}
 format[10] = {name = 'field10', type = 'map'}
 ---
 ...
+format[11] = {name = 'field11', type = 'decimal'}
+---
+...
 s = box.schema.space.create('test', {engine = engine, format = format})
 ---
 ...
 pk = s:create_index('pk')
 ---
 ...
-t = s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val = 10}}
+t = s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val = 10}, decimal.new(-11.11)}
 ---
 ...
 inspector:cmd("setopt delimiter ';'")
@@ -1160,6 +1166,11 @@ fail_format_change(3, 'map')
 ---
 - 'Tuple field 3 type does not match one required by operation: expected map'
 ...
+-- unsigned --X--> decimal
+fail_format_change(3, 'decimal')
+---
+- 'Tuple field 3 type does not match one required by operation: expected decimal'
+...
 -- string -----> any
 ok_format_change(4, 'any')
 ---
@@ -1173,6 +1184,11 @@ fail_format_change(4, 'boolean')
 ---
 - 'Tuple field 4 type does not match one required by operation: expected boolean'
 ...
+-- string --X--> decimal
+fail_format_change(4, 'decimal')
+---
+- 'Tuple field 4 type does not match one required by operation: expected decimal'
+...
 -- number -----> any
 ok_format_change(5, 'any')
 ---
@@ -1186,6 +1202,11 @@ fail_format_change(5, 'integer')
 ---
 - 'Tuple field 5 type does not match one required by operation: expected integer'
 ...
+-- number --X--> decimal
+fail_format_change(5, 'decimal')
+---
+- 'Tuple field 5 type does not match one required by operation: expected decimal'
+...
 -- integer -----> any
 ok_format_change(6, 'any')
 ---
@@ -1203,6 +1224,11 @@ fail_format_change(6, 'unsigned')
 ---
 - 'Tuple field 6 type does not match one required by operation: expected unsigned'
 ...
+-- integer --X--> decimal
+fail_format_change(6, 'decimal')
+---
+- 'Tuple field 6 type does not match one required by operation: expected decimal'
+...
 -- boolean -----> any
 ok_format_change(7, 'any')
 ---
@@ -1216,6 +1242,11 @@ fail_format_change(7, 'string')
 ---
 - 'Tuple field 7 type does not match one required by operation: expected string'
 ...
+-- boolead --X--> decimal
+fail_format_change(7, 'decimal')
+---
+- 'Tuple field 7 type does not match one required by operation: expected decimal'
+...
 -- scalar -----> any
 ok_format_change(8, 'any')
 ---
@@ -1225,6 +1256,11 @@ fail_format_change(8, 'unsigned')
 ---
 - 'Tuple field 8 type does not match one required by operation: expected unsigned'
 ...
+-- scalar --X--> decimal
+fail_format_change(8, 'decimal')
+---
+- 'Tuple field 8 type does not match one required by operation: expected decimal'
+...
 -- array -----> any
 ok_format_change(9, 'any')
 ---
@@ -1234,6 +1270,11 @@ fail_format_change(9, 'scalar')
 ---
 - 'Tuple field 9 type does not match one required by operation: expected scalar'
 ...
+-- arary --X--> decimal
+fail_format_change(9, 'decimal')
+---
+- 'Tuple field 9 type does not match one required by operation: expected decimal'
+...
 -- map -----> any
 ok_format_change(10, 'any')
 ---
@@ -1243,6 +1284,48 @@ fail_format_change(10, 'scalar')
 ---
 - 'Tuple field 10 type does not match one required by operation: expected scalar'
 ...
+-- map --X--> decimal
+fail_format_change(10, 'decimal')
+---
+- 'Tuple field 10 type does not match one required by operation: expected decimal'
+...
+-- decimal ----> any
+ok_format_change(11, 'any')
+---
+...
+-- decimal ----> number
+ok_format_change(11, 'number')
+---
+...
+-- decimal ----> scalar
+ok_format_change(11, 'scalar')
+---
+...
+-- decimal --X--> string
+fail_format_change(11, 'string')
+---
+- 'Tuple field 11 type does not match one required by operation: expected string'
+...
+-- decimal --X--> integer
+fail_format_change(11, 'integer')
+---
+- 'Tuple field 11 type does not match one required by operation: expected integer'
+...
+-- decimal --X--> unsigned
+fail_format_change(11, 'unsigned')
+---
+- 'Tuple field 11 type does not match one required by operation: expected unsigned'
+...
+-- decimal --X--> map
+fail_format_change(11, 'map')
+---
+- 'Tuple field 11 type does not match one required by operation: expected map'
+...
+-- decimal --X--> array
+fail_format_change(11, 'array')
+---
+- 'Tuple field 11 type does not match one required by operation: expected array'
+...
 s:drop()
 ---
 ...
diff --git a/test/engine/ddl.test.lua b/test/engine/ddl.test.lua
index d15bf1f58..e761966d7 100644
--- a/test/engine/ddl.test.lua
+++ b/test/engine/ddl.test.lua
@@ -355,6 +355,8 @@ s:drop()
 -- gh-2800: space formats checking is broken.
 --
 
+decimal = require('decimal')
+
 -- Ensure that vinyl correctly process field count change.
 s = box.schema.space.create('test', {engine = engine, field_count = 2})
 pk = s:create_index('pk')
@@ -376,9 +378,11 @@ format[7] = {name = 'field7', type = 'boolean'}
 format[8] = {name = 'field8', type = 'scalar'}
 format[9] = {name = 'field9', type = 'array'}
 format[10] = {name = 'field10', type = 'map'}
+format[11] = {name = 'field11', type = 'decimal'}
+
 s = box.schema.space.create('test', {engine = engine, format = format})
 pk = s:create_index('pk')
-t = s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val = 10}}
+t = s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val = 10}, decimal.new(-11.11)}
 
 inspector:cmd("setopt delimiter ';'")
 function fail_format_change(fieldno, new_type)
@@ -415,6 +419,8 @@ ok_format_change(3, 'integer')
 ok_format_change(3, 'scalar')
 -- unsigned --X--> map
 fail_format_change(3, 'map')
+-- unsigned --X--> decimal
+fail_format_change(3, 'decimal')
 
 -- string -----> any
 ok_format_change(4, 'any')
@@ -422,6 +428,8 @@ ok_format_change(4, 'any')
 ok_format_change(4, 'scalar')
 -- string --X--> boolean
 fail_format_change(4, 'boolean')
+-- string --X--> decimal
+fail_format_change(4, 'decimal')
 
 -- number -----> any
 ok_format_change(5, 'any')
@@ -429,6 +437,8 @@ ok_format_change(5, 'any')
 ok_format_change(5, 'scalar')
 -- number --X--> integer
 fail_format_change(5, 'integer')
+-- number --X--> decimal
+fail_format_change(5, 'decimal')
 
 -- integer -----> any
 ok_format_change(6, 'any')
@@ -438,6 +448,8 @@ ok_format_change(6, 'number')
 ok_format_change(6, 'scalar')
 -- integer --X--> unsigned
 fail_format_change(6, 'unsigned')
+-- integer --X--> decimal
+fail_format_change(6, 'decimal')
 
 -- boolean -----> any
 ok_format_change(7, 'any')
@@ -445,22 +457,46 @@ ok_format_change(7, 'any')
 ok_format_change(7, 'scalar')
 -- boolean --X--> string
 fail_format_change(7, 'string')
+-- boolead --X--> decimal
+fail_format_change(7, 'decimal')
 
 -- scalar -----> any
 ok_format_change(8, 'any')
 -- scalar --X--> unsigned
 fail_format_change(8, 'unsigned')
+-- scalar --X--> decimal
+fail_format_change(8, 'decimal')
 
 -- array -----> any
 ok_format_change(9, 'any')
 -- array --X--> scalar
 fail_format_change(9, 'scalar')
+-- arary --X--> decimal
+fail_format_change(9, 'decimal')
 
 -- map -----> any
 ok_format_change(10, 'any')
 -- map --X--> scalar
 fail_format_change(10, 'scalar')
-
+-- map --X--> decimal
+fail_format_change(10, 'decimal')
+
+-- decimal ----> any
+ok_format_change(11, 'any')
+-- decimal ----> number
+ok_format_change(11, 'number')
+-- decimal ----> scalar
+ok_format_change(11, 'scalar')
+-- decimal --X--> string
+fail_format_change(11, 'string')
+-- decimal --X--> integer
+fail_format_change(11, 'integer')
+-- decimal --X--> unsigned
+fail_format_change(11, 'unsigned')
+-- decimal --X--> map
+fail_format_change(11, 'map')
+-- decimal --X--> array
+fail_format_change(11, 'array')
 s:drop()
 
 -- Check new fields adding.
diff --git a/test/engine/decimal.result b/test/engine/decimal.result
index f8888d7c9..415868c89 100644
--- a/test/engine/decimal.result
+++ b/test/engine/decimal.result
@@ -12,6 +12,9 @@ engine = test_run:get_cfg('engine')
 decimal = require('decimal')
  | ---
  | ...
+ffi = require('ffi')
+ | ---
+ | ...
 
 _ = box.schema.space.create('test', {engine=engine})
  | ---
@@ -76,6 +79,278 @@ box.space.test:select{}
  |   - [3, 3.3]
  |   - [4, 1234567890123456789.9876543210987654321, 1.2345]
  | ...
+
+box.space.test:drop()
+ | ---
+ | ...
+
+-- check decimal indexes
+_ = box.schema.space.create('test', {engine=engine})
+ | ---
+ | ...
+_ = box.space.test:create_index('pk', {parts={1,'decimal'}})
+ | ---
+ | ...
+
+test_run:cmd('setopt delimiter ";"')
+ | ---
+ | - true
+ | ...
+for i = 0,16 do
+    box.space.test:insert{decimal.new((i-8)/4)}
+end;
+ | ---
+ | ...
+test_run:cmd('setopt delimiter ""');
+ | ---
+ | - true
+ | ...
+
+box.space.test:select{}
+ | ---
+ | - - [-2]
+ |   - [-1.75]
+ |   - [-1.5]
+ |   - [-1.25]
+ |   - [-1]
+ |   - [-0.75]
+ |   - [-0.5]
+ |   - [-0.25]
+ |   - [0]
+ |   - [0.25]
+ |   - [0.5]
+ |   - [0.75]
+ |   - [1]
+ |   - [1.25]
+ |   - [1.5]
+ |   - [1.75]
+ |   - [2]
+ | ...
+
+-- check invalid values
+box.space.test:insert{1.23}
+ | ---
+ | - error: 'Tuple field 1 type does not match one required by operation: expected decimal'
+ | ...
+box.space.test:insert{'str'}
+ | ---
+ | - error: 'Tuple field 1 type does not match one required by operation: expected decimal'
+ | ...
+box.space.test:insert{ffi.new('uint64_t', 0)}
+ | ---
+ | - error: 'Tuple field 1 type does not match one required by operation: expected decimal'
+ | ...
+-- check duplicates
+box.space.test:insert{decimal.new(0)}
+ | ---
+ | - error: Duplicate key exists in unique index 'pk' in space 'test'
+ | ...
+
+box.space.test.index.pk:drop()
+ | ---
+ | ...
+
+_ = box.space.test:create_index('pk', {parts={1, 'number'}})
+ | ---
+ | ...
+
+test_run:cmd('setopt delimiter ";"')
+ | ---
+ | - true
+ | ...
+for i = 0, 32 do
+    local val = (i - 16) / 8
+    if i % 2 == 1 then val = decimal.new(val) end
+    box.space.test:insert{val}
+end;
+ | ---
+ | ...
+test_run:cmd('setopt delimiter ""');
+ | ---
+ | - true
+ | ...
+
+box.space.test:select{}
+ | ---
+ | - - [-2]
+ |   - [-1.875]
+ |   - [-1.75]
+ |   - [-1.625]
+ |   - [-1.5]
+ |   - [-1.375]
+ |   - [-1.25]
+ |   - [-1.125]
+ |   - [-1]
+ |   - [-0.875]
+ |   - [-0.75]
+ |   - [-0.625]
+ |   - [-0.5]
+ |   - [-0.375]
+ |   - [-0.25]
+ |   - [-0.125]
+ |   - [0]
+ |   - [0.125]
+ |   - [0.25]
+ |   - [0.375]
+ |   - [0.5]
+ |   - [0.625]
+ |   - [0.75]
+ |   - [0.875]
+ |   - [1]
+ |   - [1.125]
+ |   - [1.25]
+ |   - [1.375]
+ |   - [1.5]
+ |   - [1.625]
+ |   - [1.75]
+ |   - [1.875]
+ |   - [2]
+ | ...
+
+-- check duplicates
+box.space.test:insert{-2}
+ | ---
+ | - error: Duplicate key exists in unique index 'pk' in space 'test'
+ | ...
+box.space.test:insert{decimal.new(-2)}
+ | ---
+ | - error: Duplicate key exists in unique index 'pk' in space 'test'
+ | ...
+box.space.test:insert{decimal.new(-1.875)}
+ | ---
+ | - error: Duplicate key exists in unique index 'pk' in space 'test'
+ | ...
+box.space.test:insert{-1.875}
+ | ---
+ | - error: Duplicate key exists in unique index 'pk' in space 'test'
+ | ...
+
+box.space.test.index.pk:drop()
+ | ---
+ | ...
+
+_ = box.space.test:create_index('pk')
+ | ---
+ | ...
+test_run:cmd('setopt delimiter ";"')
+ | ---
+ | - true
+ | ...
+for i = 1,10 do
+    box.space.test:insert{i, decimal.new(i/10)}
+end;
+ | ---
+ | ...
+test_run:cmd('setopt delimiter ""');
+ | ---
+ | - true
+ | ...
+
+-- a bigger test with a secondary index this time.
+box.space.test:insert{11, 'str'}
+ | ---
+ | - [11, 'str']
+ | ...
+box.space.test:insert{12, 0.63}
+ | ---
+ | - [12, 0.63]
+ | ...
+box.space.test:insert{13, 0.57}
+ | ---
+ | - [13, 0.57]
+ | ...
+box.space.test:insert{14, 0.33}
+ | ---
+ | - [14, 0.33]
+ | ...
+box.space.test:insert{16, 0.71}
+ | ---
+ | - [16, 0.71]
+ | ...
+
+_ = box.space.test:create_index('sk', {parts={2, 'scalar'}})
+ | ---
+ | ...
+box.space.test.index.sk:select{}
+ | ---
+ | - - [1, 0.1]
+ |   - [2, 0.2]
+ |   - [3, 0.3]
+ |   - [14, 0.33]
+ |   - [4, 0.4]
+ |   - [5, 0.5]
+ |   - [13, 0.57]
+ |   - [6, 0.6]
+ |   - [12, 0.63]
+ |   - [7, 0.7]
+ |   - [16, 0.71]
+ |   - [8, 0.8]
+ |   - [9, 0.9]
+ |   - [10, 1]
+ |   - [11, 'str']
+ | ...
+
+box.space.test:drop()
+ | ---
+ | ...
+
+-- check index alter
+_ = box.schema.space.create('test', {engine=engine})
+ | ---
+ | ...
+_ = box.space.test:create_index('pk')
+ | ---
+ | ...
+_ = box.space.test:create_index('sk', {parts={2, 'number'}})
+ | ---
+ | ...
+box.space.test:insert{1, decimal.new(-2)}
+ | ---
+ | - [1, -2]
+ | ...
+box.space.test:insert{2, -5}
+ | ---
+ | - [2, -5]
+ | ...
+-- failure
+box.space.test.index.sk:alter{parts={2, 'decimal'}}
+ | ---
+ | - error: 'Tuple field 2 type does not match one required by operation: expected decimal'
+ | ...
+_ = box.space.test:delete{2}
+ | ---
+ | ...
+box.space.test.index.sk:alter{parts={2, 'decimal'}}
+ | ---
+ | ...
+box.space.test:insert{3, decimal.new(3)}
+ | ---
+ | - [3, 3]
+ | ...
+--failure
+box.space.test:insert{4, 'string'}
+ | ---
+ | - error: 'Tuple field 2 type does not match one required by operation: expected decimal'
+ | ...
+-- failure
+box.space.test:insert{2, -5}
+ | ---
+ | - error: 'Tuple field 2 type does not match one required by operation: expected decimal'
+ | ...
+box.space.test.index.sk:alter{parts={2, 'number'}}
+ | ---
+ | ...
+box.space.test:insert{2, -5}
+ | ---
+ | - [2, -5]
+ | ...
+box.space.test.index.sk:select{}
+ | ---
+ | - - [2, -5]
+ |   - [1, -2]
+ |   - [3, 3]
+ | ...
+
 box.space.test:drop()
  | ---
  | ...
diff --git a/test/engine/decimal.test.lua b/test/engine/decimal.test.lua
index 1b14871b0..3763bf0a3 100644
--- a/test/engine/decimal.test.lua
+++ b/test/engine/decimal.test.lua
@@ -3,6 +3,7 @@ test_run = env.new()
 engine = test_run:get_cfg('engine')
 
 decimal = require('decimal')
+ffi = require('ffi')
 
 _ = box.schema.space.create('test', {engine=engine})
 _ = box.space.test:create_index('pk')
@@ -18,4 +19,86 @@ a:next(2)
 a:slice(-2)
 box.space.test:replace{3, decimal.new(3.3)}
 box.space.test:select{}
+
+box.space.test:drop()
+
+-- check decimal indexes
+_ = box.schema.space.create('test', {engine=engine})
+_ = box.space.test:create_index('pk', {parts={1,'decimal'}})
+
+test_run:cmd('setopt delimiter ";"')
+for i = 0,16 do
+    box.space.test:insert{decimal.new((i-8)/4)}
+end;
+test_run:cmd('setopt delimiter ""');
+
+box.space.test:select{}
+
+-- check invalid values
+box.space.test:insert{1.23}
+box.space.test:insert{'str'}
+box.space.test:insert{ffi.new('uint64_t', 0)}
+-- check duplicates
+box.space.test:insert{decimal.new(0)}
+
+box.space.test.index.pk:drop()
+
+_ = box.space.test:create_index('pk', {parts={1, 'number'}})
+
+test_run:cmd('setopt delimiter ";"')
+for i = 0, 32 do
+    local val = (i - 16) / 8
+    if i % 2 == 1 then val = decimal.new(val) end
+    box.space.test:insert{val}
+end;
+test_run:cmd('setopt delimiter ""');
+
+box.space.test:select{}
+
+-- check duplicates
+box.space.test:insert{-2}
+box.space.test:insert{decimal.new(-2)}
+box.space.test:insert{decimal.new(-1.875)}
+box.space.test:insert{-1.875}
+
+box.space.test.index.pk:drop()
+
+_ = box.space.test:create_index('pk')
+test_run:cmd('setopt delimiter ";"')
+for i = 1,10 do
+    box.space.test:insert{i, decimal.new(i/10)}
+end;
+test_run:cmd('setopt delimiter ""');
+
+-- a bigger test with a secondary index this time.
+box.space.test:insert{11, 'str'}
+box.space.test:insert{12, 0.63}
+box.space.test:insert{13, 0.57}
+box.space.test:insert{14, 0.33}
+box.space.test:insert{16, 0.71}
+
+_ = box.space.test:create_index('sk', {parts={2, 'scalar'}})
+box.space.test.index.sk:select{}
+
+box.space.test:drop()
+
+-- check index alter
+_ = box.schema.space.create('test', {engine=engine})
+_ = box.space.test:create_index('pk')
+_ = box.space.test:create_index('sk', {parts={2, 'number'}})
+box.space.test:insert{1, decimal.new(-2)}
+box.space.test:insert{2, -5}
+-- failure
+box.space.test.index.sk:alter{parts={2, 'decimal'}}
+_ = box.space.test:delete{2}
+box.space.test.index.sk:alter{parts={2, 'decimal'}}
+box.space.test:insert{3, decimal.new(3)}
+--failure
+box.space.test:insert{4, 'string'}
+-- failure
+box.space.test:insert{2, -5}
+box.space.test.index.sk:alter{parts={2, 'number'}}
+box.space.test:insert{2, -5}
+box.space.test.index.sk:select{}
+
 box.space.test:drop()
diff --git a/test/engine/iterator.result b/test/engine/iterator.result
index 6e03fbbbd..63dc262bd 100644
--- a/test/engine/iterator.result
+++ b/test/engine/iterator.result
@@ -1471,6 +1471,736 @@ t
 space:drop()
 ---
 ...
+-- iterator (decimal)
+decimal = require('decimal')
+---
+...
+space = box.schema.space.create('test', { engine = engine })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'decimal'} })
+---
+...
+for key = 1, 100 do space:replace{decimal.new((key-50)/10)} end
+---
+...
+t = {} for state, v in index:pairs({}, {iterator = 'ALL'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [-4.9]
+  - [-4.8]
+  - [-4.7]
+  - [-4.6]
+  - [-4.5]
+  - [-4.4]
+  - [-4.3]
+  - [-4.2]
+  - [-4.1]
+  - [-4]
+  - [-3.9]
+  - [-3.8]
+  - [-3.7]
+  - [-3.6]
+  - [-3.5]
+  - [-3.4]
+  - [-3.3]
+  - [-3.2]
+  - [-3.1]
+  - [-3]
+  - [-2.9]
+  - [-2.8]
+  - [-2.7]
+  - [-2.6]
+  - [-2.5]
+  - [-2.4]
+  - [-2.3]
+  - [-2.2]
+  - [-2.1]
+  - [-2]
+  - [-1.9]
+  - [-1.8]
+  - [-1.7]
+  - [-1.6]
+  - [-1.5]
+  - [-1.4]
+  - [-1.3]
+  - [-1.2]
+  - [-1.1]
+  - [-1]
+  - [-0.9]
+  - [-0.8]
+  - [-0.7]
+  - [-0.6]
+  - [-0.5]
+  - [-0.4]
+  - [-0.3]
+  - [-0.2]
+  - [-0.1]
+  - [0]
+  - [0.1]
+  - [0.2]
+  - [0.3]
+  - [0.4]
+  - [0.5]
+  - [0.6]
+  - [0.7]
+  - [0.8]
+  - [0.9]
+  - [1]
+  - [1.1]
+  - [1.2]
+  - [1.3]
+  - [1.4]
+  - [1.5]
+  - [1.6]
+  - [1.7]
+  - [1.8]
+  - [1.9]
+  - [2]
+  - [2.1]
+  - [2.2]
+  - [2.3]
+  - [2.4]
+  - [2.5]
+  - [2.6]
+  - [2.7]
+  - [2.8]
+  - [2.9]
+  - [3]
+  - [3.1]
+  - [3.2]
+  - [3.3]
+  - [3.4]
+  - [3.5]
+  - [3.6]
+  - [3.7]
+  - [3.8]
+  - [3.9]
+  - [4]
+  - [4.1]
+  - [4.2]
+  - [4.3]
+  - [4.4]
+  - [4.5]
+  - [4.6]
+  - [4.7]
+  - [4.8]
+  - [4.9]
+  - [5]
+...
+t = {} for state, v in index:pairs({}, {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [-4.9]
+  - [-4.8]
+  - [-4.7]
+  - [-4.6]
+  - [-4.5]
+  - [-4.4]
+  - [-4.3]
+  - [-4.2]
+  - [-4.1]
+  - [-4]
+  - [-3.9]
+  - [-3.8]
+  - [-3.7]
+  - [-3.6]
+  - [-3.5]
+  - [-3.4]
+  - [-3.3]
+  - [-3.2]
+  - [-3.1]
+  - [-3]
+  - [-2.9]
+  - [-2.8]
+  - [-2.7]
+  - [-2.6]
+  - [-2.5]
+  - [-2.4]
+  - [-2.3]
+  - [-2.2]
+  - [-2.1]
+  - [-2]
+  - [-1.9]
+  - [-1.8]
+  - [-1.7]
+  - [-1.6]
+  - [-1.5]
+  - [-1.4]
+  - [-1.3]
+  - [-1.2]
+  - [-1.1]
+  - [-1]
+  - [-0.9]
+  - [-0.8]
+  - [-0.7]
+  - [-0.6]
+  - [-0.5]
+  - [-0.4]
+  - [-0.3]
+  - [-0.2]
+  - [-0.1]
+  - [0]
+  - [0.1]
+  - [0.2]
+  - [0.3]
+  - [0.4]
+  - [0.5]
+  - [0.6]
+  - [0.7]
+  - [0.8]
+  - [0.9]
+  - [1]
+  - [1.1]
+  - [1.2]
+  - [1.3]
+  - [1.4]
+  - [1.5]
+  - [1.6]
+  - [1.7]
+  - [1.8]
+  - [1.9]
+  - [2]
+  - [2.1]
+  - [2.2]
+  - [2.3]
+  - [2.4]
+  - [2.5]
+  - [2.6]
+  - [2.7]
+  - [2.8]
+  - [2.9]
+  - [3]
+  - [3.1]
+  - [3.2]
+  - [3.3]
+  - [3.4]
+  - [3.5]
+  - [3.6]
+  - [3.7]
+  - [3.8]
+  - [3.9]
+  - [4]
+  - [4.1]
+  - [4.2]
+  - [4.3]
+  - [4.4]
+  - [4.5]
+  - [4.6]
+  - [4.7]
+  - [4.8]
+  - [4.9]
+  - [5]
+...
+t = {} for state, v in index:pairs(decimal.new(-0.6), {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [-0.6]
+  - [-0.5]
+  - [-0.4]
+  - [-0.3]
+  - [-0.2]
+  - [-0.1]
+  - [0]
+  - [0.1]
+  - [0.2]
+  - [0.3]
+  - [0.4]
+  - [0.5]
+  - [0.6]
+  - [0.7]
+  - [0.8]
+  - [0.9]
+  - [1]
+  - [1.1]
+  - [1.2]
+  - [1.3]
+  - [1.4]
+  - [1.5]
+  - [1.6]
+  - [1.7]
+  - [1.8]
+  - [1.9]
+  - [2]
+  - [2.1]
+  - [2.2]
+  - [2.3]
+  - [2.4]
+  - [2.5]
+  - [2.6]
+  - [2.7]
+  - [2.8]
+  - [2.9]
+  - [3]
+  - [3.1]
+  - [3.2]
+  - [3.3]
+  - [3.4]
+  - [3.5]
+  - [3.6]
+  - [3.7]
+  - [3.8]
+  - [3.9]
+  - [4]
+  - [4.1]
+  - [4.2]
+  - [4.3]
+  - [4.4]
+  - [4.5]
+  - [4.6]
+  - [4.7]
+  - [4.8]
+  - [4.9]
+  - [5]
+...
+t = {} for state, v in index:pairs(decimal.new(-0.6), {iterator = 'GT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [-0.5]
+  - [-0.4]
+  - [-0.3]
+  - [-0.2]
+  - [-0.1]
+  - [0]
+  - [0.1]
+  - [0.2]
+  - [0.3]
+  - [0.4]
+  - [0.5]
+  - [0.6]
+  - [0.7]
+  - [0.8]
+  - [0.9]
+  - [1]
+  - [1.1]
+  - [1.2]
+  - [1.3]
+  - [1.4]
+  - [1.5]
+  - [1.6]
+  - [1.7]
+  - [1.8]
+  - [1.9]
+  - [2]
+  - [2.1]
+  - [2.2]
+  - [2.3]
+  - [2.4]
+  - [2.5]
+  - [2.6]
+  - [2.7]
+  - [2.8]
+  - [2.9]
+  - [3]
+  - [3.1]
+  - [3.2]
+  - [3.3]
+  - [3.4]
+  - [3.5]
+  - [3.6]
+  - [3.7]
+  - [3.8]
+  - [3.9]
+  - [4]
+  - [4.1]
+  - [4.2]
+  - [4.3]
+  - [4.4]
+  - [4.5]
+  - [4.6]
+  - [4.7]
+  - [4.8]
+  - [4.9]
+  - [5]
+...
+t = {} for state, v in index:pairs({}, {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [5]
+  - [4.9]
+  - [4.8]
+  - [4.7]
+  - [4.6]
+  - [4.5]
+  - [4.4]
+  - [4.3]
+  - [4.2]
+  - [4.1]
+  - [4]
+  - [3.9]
+  - [3.8]
+  - [3.7]
+  - [3.6]
+  - [3.5]
+  - [3.4]
+  - [3.3]
+  - [3.2]
+  - [3.1]
+  - [3]
+  - [2.9]
+  - [2.8]
+  - [2.7]
+  - [2.6]
+  - [2.5]
+  - [2.4]
+  - [2.3]
+  - [2.2]
+  - [2.1]
+  - [2]
+  - [1.9]
+  - [1.8]
+  - [1.7]
+  - [1.6]
+  - [1.5]
+  - [1.4]
+  - [1.3]
+  - [1.2]
+  - [1.1]
+  - [1]
+  - [0.9]
+  - [0.8]
+  - [0.7]
+  - [0.6]
+  - [0.5]
+  - [0.4]
+  - [0.3]
+  - [0.2]
+  - [0.1]
+  - [0]
+  - [-0.1]
+  - [-0.2]
+  - [-0.3]
+  - [-0.4]
+  - [-0.5]
+  - [-0.6]
+  - [-0.7]
+  - [-0.8]
+  - [-0.9]
+  - [-1]
+  - [-1.1]
+  - [-1.2]
+  - [-1.3]
+  - [-1.4]
+  - [-1.5]
+  - [-1.6]
+  - [-1.7]
+  - [-1.8]
+  - [-1.9]
+  - [-2]
+  - [-2.1]
+  - [-2.2]
+  - [-2.3]
+  - [-2.4]
+  - [-2.5]
+  - [-2.6]
+  - [-2.7]
+  - [-2.8]
+  - [-2.9]
+  - [-3]
+  - [-3.1]
+  - [-3.2]
+  - [-3.3]
+  - [-3.4]
+  - [-3.5]
+  - [-3.6]
+  - [-3.7]
+  - [-3.8]
+  - [-3.9]
+  - [-4]
+  - [-4.1]
+  - [-4.2]
+  - [-4.3]
+  - [-4.4]
+  - [-4.5]
+  - [-4.6]
+  - [-4.7]
+  - [-4.8]
+  - [-4.9]
+...
+t = {} for state, v in index:pairs(decimal.new(2.7), {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [2.7]
+  - [2.6]
+  - [2.5]
+  - [2.4]
+  - [2.3]
+  - [2.2]
+  - [2.1]
+  - [2]
+  - [1.9]
+  - [1.8]
+  - [1.7]
+  - [1.6]
+  - [1.5]
+  - [1.4]
+  - [1.3]
+  - [1.2]
+  - [1.1]
+  - [1]
+  - [0.9]
+  - [0.8]
+  - [0.7]
+  - [0.6]
+  - [0.5]
+  - [0.4]
+  - [0.3]
+  - [0.2]
+  - [0.1]
+  - [0]
+  - [-0.1]
+  - [-0.2]
+  - [-0.3]
+  - [-0.4]
+  - [-0.5]
+  - [-0.6]
+  - [-0.7]
+  - [-0.8]
+  - [-0.9]
+  - [-1]
+  - [-1.1]
+  - [-1.2]
+  - [-1.3]
+  - [-1.4]
+  - [-1.5]
+  - [-1.6]
+  - [-1.7]
+  - [-1.8]
+  - [-1.9]
+  - [-2]
+  - [-2.1]
+  - [-2.2]
+  - [-2.3]
+  - [-2.4]
+  - [-2.5]
+  - [-2.6]
+  - [-2.7]
+  - [-2.8]
+  - [-2.9]
+  - [-3]
+  - [-3.1]
+  - [-3.2]
+  - [-3.3]
+  - [-3.4]
+  - [-3.5]
+  - [-3.6]
+  - [-3.7]
+  - [-3.8]
+  - [-3.9]
+  - [-4]
+  - [-4.1]
+  - [-4.2]
+  - [-4.3]
+  - [-4.4]
+  - [-4.5]
+  - [-4.6]
+  - [-4.7]
+  - [-4.8]
+  - [-4.9]
+...
+t = {} for state, v in index:pairs({}, {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [5]
+  - [4.9]
+  - [4.8]
+  - [4.7]
+  - [4.6]
+  - [4.5]
+  - [4.4]
+  - [4.3]
+  - [4.2]
+  - [4.1]
+  - [4]
+  - [3.9]
+  - [3.8]
+  - [3.7]
+  - [3.6]
+  - [3.5]
+  - [3.4]
+  - [3.3]
+  - [3.2]
+  - [3.1]
+  - [3]
+  - [2.9]
+  - [2.8]
+  - [2.7]
+  - [2.6]
+  - [2.5]
+  - [2.4]
+  - [2.3]
+  - [2.2]
+  - [2.1]
+  - [2]
+  - [1.9]
+  - [1.8]
+  - [1.7]
+  - [1.6]
+  - [1.5]
+  - [1.4]
+  - [1.3]
+  - [1.2]
+  - [1.1]
+  - [1]
+  - [0.9]
+  - [0.8]
+  - [0.7]
+  - [0.6]
+  - [0.5]
+  - [0.4]
+  - [0.3]
+  - [0.2]
+  - [0.1]
+  - [0]
+  - [-0.1]
+  - [-0.2]
+  - [-0.3]
+  - [-0.4]
+  - [-0.5]
+  - [-0.6]
+  - [-0.7]
+  - [-0.8]
+  - [-0.9]
+  - [-1]
+  - [-1.1]
+  - [-1.2]
+  - [-1.3]
+  - [-1.4]
+  - [-1.5]
+  - [-1.6]
+  - [-1.7]
+  - [-1.8]
+  - [-1.9]
+  - [-2]
+  - [-2.1]
+  - [-2.2]
+  - [-2.3]
+  - [-2.4]
+  - [-2.5]
+  - [-2.6]
+  - [-2.7]
+  - [-2.8]
+  - [-2.9]
+  - [-3]
+  - [-3.1]
+  - [-3.2]
+  - [-3.3]
+  - [-3.4]
+  - [-3.5]
+  - [-3.6]
+  - [-3.7]
+  - [-3.8]
+  - [-3.9]
+  - [-4]
+  - [-4.1]
+  - [-4.2]
+  - [-4.3]
+  - [-4.4]
+  - [-4.5]
+  - [-4.6]
+  - [-4.7]
+  - [-4.8]
+  - [-4.9]
+...
+t = {} for state, v in index:pairs(decimal.new(2.7), {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [2.6]
+  - [2.5]
+  - [2.4]
+  - [2.3]
+  - [2.2]
+  - [2.1]
+  - [2]
+  - [1.9]
+  - [1.8]
+  - [1.7]
+  - [1.6]
+  - [1.5]
+  - [1.4]
+  - [1.3]
+  - [1.2]
+  - [1.1]
+  - [1]
+  - [0.9]
+  - [0.8]
+  - [0.7]
+  - [0.6]
+  - [0.5]
+  - [0.4]
+  - [0.3]
+  - [0.2]
+  - [0.1]
+  - [0]
+  - [-0.1]
+  - [-0.2]
+  - [-0.3]
+  - [-0.4]
+  - [-0.5]
+  - [-0.6]
+  - [-0.7]
+  - [-0.8]
+  - [-0.9]
+  - [-1]
+  - [-1.1]
+  - [-1.2]
+  - [-1.3]
+  - [-1.4]
+  - [-1.5]
+  - [-1.6]
+  - [-1.7]
+  - [-1.8]
+  - [-1.9]
+  - [-2]
+  - [-2.1]
+  - [-2.2]
+  - [-2.3]
+  - [-2.4]
+  - [-2.5]
+  - [-2.6]
+  - [-2.7]
+  - [-2.8]
+  - [-2.9]
+  - [-3]
+  - [-3.1]
+  - [-3.2]
+  - [-3.3]
+  - [-3.4]
+  - [-3.5]
+  - [-3.6]
+  - [-3.7]
+  - [-3.8]
+  - [-3.9]
+  - [-4]
+  - [-4.1]
+  - [-4.2]
+  - [-4.3]
+  - [-4.4]
+  - [-4.5]
+  - [-4.6]
+  - [-4.7]
+  - [-4.8]
+  - [-4.9]
+...
+space:drop()
+---
+...
 -- iterator multi-part (num, num)
 space = box.schema.space.create('test', { engine = engine })
 ---
diff --git a/test/engine/iterator.test.lua b/test/engine/iterator.test.lua
index 9ff51ebe1..2f83d19dd 100644
--- a/test/engine/iterator.test.lua
+++ b/test/engine/iterator.test.lua
@@ -48,6 +48,28 @@ t = {} for state, v in index:pairs(77, {iterator = 'LT'}) do table.insert(t, v)
 t
 space:drop()
 
+-- iterator (decimal)
+decimal = require('decimal')
+space = box.schema.space.create('test', { engine = engine })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'decimal'} })
+for key = 1, 100 do space:replace{decimal.new((key-50)/10)} end
+t = {} for state, v in index:pairs({}, {iterator = 'ALL'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({}, {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs(decimal.new(-0.6), {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs(decimal.new(-0.6), {iterator = 'GT'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({}, {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs(decimal.new(2.7), {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({}, {iterator = 'LT'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs(decimal.new(2.7), {iterator = 'LT'}) do table.insert(t, v) end
+t
+space:drop()
 
 -- iterator multi-part (num, num)
 space = box.schema.space.create('test', { engine = engine })
-- 
2.20.1 (Apple Git-117)




More information about the Tarantool-patches mailing list