[PATCH 5/5] decimal: allow to index decimals
Serge Petrenko
sergepetrenko at tarantool.org
Wed Jul 17 18:33:46 MSK 2019
Indices can now be built over decimal fields.
A new field type - 'decimal' is introduced.
Decimal values may be stored in 'decimal' columns, as well as in
'scalar' and 'any' columns.
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' and
'number' fields.
'decimal' field type is appropriate for both memtx HASH and TREE
indices, as well as for vinyl TREE index.
---
src/box/field_def.c | 43 +++++--
src/box/field_def.h | 16 ++-
src/box/key_def.h | 2 +-
src/box/tuple_compare.cc | 160 ++++++++++++++++++++++++-
src/box/tuple_format.c | 2 +-
src/lib/core/decimal.h | 8 ++
src/lib/core/mp_decimal.h | 8 ++
test/engine/decimal.result | 226 +++++++++++++++++++++++++++++++++++
test/engine/decimal.test.lua | 65 ++++++++++
9 files changed, 510 insertions(+), 20 deletions(-)
create mode 100644 test/engine/decimal.result
create mode 100644 test/engine/decimal.test.lua
diff --git a/src/box/field_def.c b/src/box/field_def.c
index 346042b98..da06e6bde 100644
--- a/src/box/field_def.c
+++ b/src/box/field_def.c
@@ -52,17 +52,32 @@ const uint32_t field_mp_type[] = {
/* [FIELD_TYPE_UNSIGNED] = */ 1U << MP_UINT,
/* [FIELD_TYPE_STRING] = */ 1U << MP_STR,
/* [FIELD_TYPE_NUMBER] = */ (1U << MP_UINT) | (1U << MP_INT) |
- (1U << MP_FLOAT) | (1U << MP_DOUBLE),
+ (1U << MP_FLOAT) | (1U << MP_DOUBLE) | (1U << MP_EXT),
/* [FIELD_TYPE_INTEGER] = */ (1U << MP_UINT) | (1U << MP_INT),
/* [FIELD_TYPE_BOOLEAN] = */ 1U << MP_BOOL,
/* [FIELD_TYPE_VARBINARY] = */ 1U << MP_BIN,
/* [FIELD_TYPE_SCALAR] = */ (1U << MP_UINT) | (1U << MP_INT) |
(1U << MP_FLOAT) | (1U << MP_DOUBLE) | (1U << MP_STR) |
- (1U << MP_BIN) | (1U << MP_BOOL),
+ (1U << MP_BIN) | (1U << MP_BOOL) | (1U << MP_EXT),
+ /* [FIELD_TYPE_DECIMAL] = */ 1U << MP_EXT,
/* [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),
+ /* [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 +87,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 +114,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..10dc9fc13 100644
--- a/src/box/field_def.h
+++ b/src/box/field_def.h
@@ -37,6 +37,7 @@
#include <limits.h>
#include <msgpuck.h>
#include "opt_def.h"
+#include "lib/core/mp_user_types.h"
#if defined(__cplusplus)
extern "C" {
@@ -58,6 +59,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 +111,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;
@@ -142,15 +145,20 @@ struct field_def {
struct Expr *default_value_expr;
};
-/** Checks if mp_type (MsgPack) is compatible with field type. */
+/** Checks if mp_typeof(data) 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)
{
+ enum mp_type mp_type = mp_typeof(*data);
+ int8_t ext_type = MP_UNKNOWN;
assert(type < field_type_MAX);
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;
+ if (mp_type != MP_EXT)
+ return (mask & (1U << mp_type)) != 0;
+ mp_decode_extl(&data, &ext_type);
+ return (field_ext_type[type] & (1U << ext_type)) != 0;
}
static inline bool
diff --git a/src/box/key_def.h b/src/box/key_def.h
index f4a1a8fd1..ed3354ade 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -505,7 +505,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),
+ 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]);
diff --git a/src/box/tuple_compare.cc b/src/box/tuple_compare.cc
index 95a0f58c9..6ab28f662 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_user_types.h"
/* {{{ tuple_compare */
@@ -87,7 +90,7 @@ 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_NUMBER /* requires additional parsing */
};
#define COMPARE_RESULT(a, b) (a < b ? -1 : a > b)
@@ -264,6 +267,50 @@ mp_compare_double_any_number(double lhs, const char *rhs,
return k * COMPARE_RESULT(lqbit, rqbit);
}
+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);
+ assert(ext_type == MP_DECIMAL);
+ decimal_unpack(&rhs, len, &rhs_dec);
+ 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)
@@ -271,6 +318,21 @@ mp_compare_number_with_type(const char *lhs, enum mp_type lhs_type,
assert(mp_classof(lhs_type) == MP_CLASS_NUMBER);
assert(mp_classof(rhs_type) == MP_CLASS_NUMBER);
+ /*
+ * test decimals first, so that we don't have to
+ * account for them in other comarators.
+ */
+ decimal_t dec;
+ if (rhs_type == MP_EXT) {
+ return mp_compare_decimal_any_number(
+ mp_decode_decimal(&rhs, &dec), lhs, lhs_type, -1
+ );
+ }
+ if (lhs_type == MP_EXT) {
+ return mp_compare_decimal_any_number(
+ mp_decode_decimal(&lhs, &dec), rhs, rhs_type, 1
+ );
+ }
if (rhs_type == MP_FLOAT) {
return mp_compare_double_any_number(
mp_decode_float(&rhs), lhs, lhs_type, -1
@@ -410,6 +472,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_number(field_a, field_b);
default:
unreachable();
return 0;
@@ -443,6 +507,8 @@ 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(field_a, field_b);
default:
unreachable();
return 0;
@@ -1356,6 +1422,25 @@ static const comparator_with_key_signature cmp_wk_arr[] = {
#define HINT_VALUE_DOUBLE_MAX (exp2(HINT_VALUE_BITS - 1) - 1)
#define HINT_VALUE_DOUBLE_MIN (-exp2(HINT_VALUE_BITS - 1))
+/**
+ * Max and min decimal numbers whose integral parts fit
+ * in a hint value.
+ */
+static const decimal_t HINT_VALUE_DECIMAL_MAX = {
+ 18, /* decimal digits */
+ 0, /* exponent */
+ 0, /* no special bits. */
+ {487, 423, 303, 752, 460, 576} /* 576,460,752,303,423,488 = 2^59 - 1 */
+ /* 59 == HINT_VALUE_BITS - 1 */
+};
+
+static const decimal_t HINT_VALUE_DECIMAL_MIN = {
+ 18, /* decimal digits */
+ 0, /* exponent */
+ 0x80, /* negative bit */
+ {488, 423, 303, 752, 460, 576} /* 576,460,752,303,423,488 = 2^59 */
+};
+
/*
* HINT_CLASS_BITS should be big enough to store any mp_class value.
* Note, ((1 << HINT_CLASS_BITS) - 1) is reserved for HINT_NONE.
@@ -1415,6 +1500,25 @@ 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_compare(dec, &HINT_VALUE_DECIMAL_MAX) >= 0)
+ val = HINT_VALUE_MAX;
+ else if (decimal_compare(dec, &HINT_VALUE_DECIMAL_MIN) <= 0)
+ val = 0;
+ else {
+ dec = decimal_to_int64(dec, &num);
+ /* We've checked boundaries above. */
+ assert(dec != NULL);
+ val = num - HINT_VALUE_INT_MIN;
+ }
+
+ return hint_create(MP_CLASS_NUMBER, val);
+}
+
static inline uint64_t
hint_str_raw(const char *s, uint32_t len)
{
@@ -1491,12 +1595,42 @@ 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;
+ decimal_t *res;
+ /*
+ * The effect of mp_decode_extl() +
+ * decimal_unpack() is the same that
+ * the one of mp_decode_decimal().
+ */
+ res = decimal_unpack(&field, len, &dec);
+ assert(res != NULL);
+ return hint_decimal(res);
+ }
+ default:
+ unreachable();
+ }
+ }
default:
unreachable();
}
return HINT_NONE;
}
+static inline hint_t
+field_hint_decimal(const char *field)
+{
+ assert(mp_typeof(*field) == MP_EXT);
+ decimal_t dec;
+ return hint_decimal(mp_decode_decimal(&field, &dec));
+}
+
static inline hint_t
field_hint_string(const char *field, struct coll *coll)
{
@@ -1536,6 +1670,25 @@ 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;
+ /*
+ * The effect of mp_decode_extl() +
+ * decimal_unpack() is the same that
+ * the one of mp_decode_decimal().
+ */
+ return hint_decimal(decimal_unpack(&field, len, &dec));
+ }
+ default:
+ unreachable();
+ }
+ }
default:
unreachable();
}
@@ -1563,6 +1716,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();
}
@@ -1668,6 +1823,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 02fadf1cf..5bb659b87 100644
--- a/src/box/tuple_format.c
+++ b/src/box/tuple_format.c
@@ -1165,7 +1165,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),
+ if (!field_mp_type_is_compatible(field->type, entry->data,
is_nullable) != 0) {
diag_set(ClientError, ER_FIELD_TYPE,
tuple_field_path(field),
diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h
index a4e7683c7..6a152b08c 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;
/**
@@ -207,4 +211,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/decimal.result b/test/engine/decimal.result
new file mode 100644
index 000000000..16cd335e0
--- /dev/null
+++ b/test/engine/decimal.result
@@ -0,0 +1,226 @@
+-- test-run result file version 2
+env = require('test_run')
+ | ---
+ | ...
+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', {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()
+ | ---
+ | ...
diff --git a/test/engine/decimal.test.lua b/test/engine/decimal.test.lua
new file mode 100644
index 000000000..52a300e72
--- /dev/null
+++ b/test/engine/decimal.test.lua
@@ -0,0 +1,65 @@
+env = require('test_run')
+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', {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()
--
2.20.1 (Apple Git-117)
More information about the Tarantool-patches
mailing list