[PATCH v2 8/8] decimal: allow to index decimals
Serge Petrenko
sergepetrenko at tarantool.org
Thu Aug 8 16:42:40 MSK 2019
Sorry, a tiny fix for test. Didn’t notice the problem on my local machine.
diff --git a/test/engine/decimal.result b/test/engine/decimal.result
index 4ced88c24..415868c89 100644
--- a/test/engine/decimal.result
+++ b/test/engine/decimal.result
@@ -317,7 +317,7 @@ 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:delete{2}
| ---
| ...
box.space.test.index.sk:alter{parts={2, 'decimal'}}
diff --git a/test/engine/decimal.test.lua b/test/engine/decimal.test.lua
index 4e38650dd..3763bf0a3 100644
--- a/test/engine/decimal.test.lua
+++ b/test/engine/decimal.test.lua
@@ -90,7 +90,7 @@ 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:delete{2}
box.space.test.index.sk:alter{parts={2, 'decimal'}}
box.space.test:insert{3, decimal.new(3)}
--failure
--
Serge Petrenko
sergepetrenko at tarantool.org
> 8 авг. 2019 г., в 14:55, Serge Petrenko <sergepetrenko at tarantool.org> написал(а):
>
> 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 | 30 ++--
> src/box/field_def.h | 1 +
> src/box/tuple_compare.cc | 104 ++++++++++++-
> 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 +++++++++++
> 9 files changed, 617 insertions(+), 17 deletions(-)
>
> diff --git a/src/box/field_def.c b/src/box/field_def.c
> index 2fad81d42..b1ecb1f19 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 "lua/utils.h"
>
> const char *mp_type_strs[] = {
> /* .MP_NIL = */ "nil",
> @@ -52,13 +53,14 @@ const uint32_t field_mp_type[] = {
> /* [FIELD_TYPE_UNSIGNED] = */ 1U << MP_FIELD_UINT,
> /* [FIELD_TYPE_STRING] = */ 1U << MP_FIELD_STR,
> /* [FIELD_TYPE_NUMBER] = */ (1U << MP_FIELD_UINT) | (1U << MP_FIELD_INT) |
> - (1U << MP_FIELD_FLOAT) | (1U << MP_FIELD_DOUBLE),
> + (1U << MP_FIELD_FLOAT) | (1U << MP_FIELD_DOUBLE) | (1U << MP_FIELD_DECIMAL),
> /* [FIELD_TYPE_INTEGER] = */ (1U << MP_FIELD_UINT) | (1U << MP_FIELD_INT),
> /* [FIELD_TYPE_BOOLEAN] = */ 1U << MP_FIELD_BOOL,
> /* [FIELD_TYPE_VARBINARY] = */ 1U << MP_FIELD_BIN,
> /* [FIELD_TYPE_SCALAR] = */ (1U << MP_FIELD_UINT) | (1U << MP_FIELD_INT) |
> (1U << MP_FIELD_FLOAT) | (1U << MP_FIELD_DOUBLE) | (1U << MP_FIELD_STR) |
> - (1U << MP_FIELD_BIN) | (1U << MP_FIELD_BOOL),
> + (1U << MP_FIELD_BIN) | (1U << MP_FIELD_BOOL) | (1U << MP_FIELD_DECIMAL),
> + /* [FIELD_TYPE_DECIMAL] = */ 1U << MP_FIELD_DECIMAL,
> /* [FIELD_TYPE_ARRAY] = */ 1U << MP_FIELD_ARRAY,
> /* [FIELD_TYPE_MAP] = */ (1U << MP_FIELD_MAP),
> };
> @@ -72,6 +74,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 +101,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 2f21f3cfc..abd8ca28b 100644
> --- a/src/box/field_def.h
> +++ b/src/box/field_def.h
> @@ -59,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
> diff --git a/src/box/tuple_compare.cc b/src/box/tuple_compare.cc
> index b4932db6d..990b584c4 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 */
>
> @@ -88,7 +91,7 @@ static enum mp_class mp_classes[] = {
> /* .MP_FIELD_BOOL = */ MP_CLASS_BOOL,
> /* .MP_FIELD_FLOAT = */ MP_CLASS_NUMBER,
> /* .MP_FIELD_DOUBLE = */ MP_CLASS_NUMBER,
> - /* .MP_FIELD_BIN = */ MP_CLASS_BIN
> + /* .MP_FIELD_DECIMAL = */ MP_CLASS_NUMBER
> };
>
> #define COMPARE_RESULT(a, b) (a < b ? -1 : a > b)
> @@ -265,6 +268,45 @@ 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_field_type rhs_type, int k)
> +{
> + decimal_t rhs_dec;
> + switch (rhs_type) {
> + case MP_FIELD_FLOAT:
> + {
> + double d = mp_decode_float(&rhs);
> + decimal_from_double(&rhs_dec, d);
> + break;
> + }
> + case MP_FIELD_DOUBLE:
> + {
> + double d = mp_decode_double(&rhs);
> + decimal_from_double(&rhs_dec, d);
> + break;
> + }
> + case MP_FIELD_INT:
> + {
> + int64_t num = mp_decode_int(&rhs);
> + decimal_from_int64(&rhs_dec, num);
> + break;
> + }
> + case MP_FIELD_UINT:
> + {
> + uint64_t num = mp_decode_uint(&rhs);
> + decimal_from_uint64(&rhs_dec, num);
> + break;
> + }
> + case MP_FIELD_DECIMAL:
> + mp_decode_decimal(&rhs, &rhs_dec);
> + break;
> + default:
> + unreachable();
> + }
> + return k * decimal_compare(lhs, &rhs_dec);
> +}
> +
> static int
> mp_compare_number_with_type(const char *lhs, enum mp_field_type lhs_type,
> const char *rhs, enum mp_field_type rhs_type)
> @@ -272,6 +314,21 @@ mp_compare_number_with_type(const char *lhs, enum mp_field_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 comparators.
> + */
> + decimal_t dec;
> + if (rhs_type == MP_FIELD_DECIMAL) {
> + return mp_compare_decimal_any_number(
> + mp_decode_decimal(&rhs, &dec), lhs, lhs_type, -1
> + );
> + }
> + if (lhs_type == MP_FIELD_DECIMAL) {
> + return mp_compare_decimal_any_number(
> + mp_decode_decimal(&lhs, &dec), rhs, rhs_type, 1
> + );
> + }
> if (rhs_type == MP_FIELD_FLOAT) {
> return mp_compare_double_any_number(
> mp_decode_float(&rhs), lhs, lhs_type, -1
> @@ -412,6 +469,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;
> @@ -445,6 +504,8 @@ tuple_compare_field_with_type(const char *field_a, enum mp_field_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;
> @@ -1504,6 +1565,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)
> {
> @@ -1580,12 +1659,25 @@ field_hint_number(const char *field)
> return hint_double(mp_decode_float(&field));
> case MP_FIELD_DOUBLE:
> return hint_double(mp_decode_double(&field));
> + case MP_FIELD_DECIMAL:
> + {
> + decimal_t dec;
> + return hint_decimal(mp_decode_decimal(&field, &dec));
> + }
> default:
> unreachable();
> }
> return HINT_NONE;
> }
>
> +static inline hint_t
> +field_hint_decimal(const char *field)
> +{
> + assert(msgpack_to_field_type(field) == MP_FIELD_DECIMAL);
> + decimal_t dec;
> + return hint_decimal(mp_decode_decimal(&field, &dec));
> +}
> +
> static inline hint_t
> field_hint_string(const char *field, struct coll *coll)
> {
> @@ -1625,6 +1717,11 @@ field_hint_scalar(const char *field, struct coll *coll)
> case MP_FIELD_BIN:
> len = mp_decode_binl(&field);
> return hint_bin(field, len);
> + case MP_FIELD_DECIMAL:
> + {
> + decimal_t dec;
> + return hint_decimal(mp_decode_decimal(&field, &dec));
> + }
> default:
> unreachable();
> }
> @@ -1652,6 +1749,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();
> }
> @@ -1757,6 +1856,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/lib/core/decimal.h b/src/lib/core/decimal.h
> index cf41a5052..72c9fd4b5 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;
>
> /**
> @@ -233,4 +237,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..4ced88c24 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..4e38650dd 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()
> --
> 2.20.1 (Apple Git-117)
>
More information about the Tarantool-patches
mailing list