From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Content-Type: text/plain; charset=utf-8 Mime-Version: 1.0 (Mac OS X Mail 12.4 \(3445.104.11\)) Subject: Re: [PATCH v2 8/8] decimal: allow to index decimals From: Serge Petrenko In-Reply-To: <8fb249e461a35b692113a02acbbaf61b32dc9739.1565263272.git.sergepetrenko@tarantool.org> Date: Thu, 8 Aug 2019 16:42:40 +0300 Content-Transfer-Encoding: quoted-printable Message-Id: References: <8fb249e461a35b692113a02acbbaf61b32dc9739.1565263272.git.sergepetrenko@tarantool.org> To: Vladimir Davydov Cc: tarantool-patches@freelists.org, kostja@tarantool.org List-ID: Sorry, a tiny fix for test. Didn=E2=80=99t 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=3D{2, = 'decimal'}} | --- | - error: 'Tuple field 2 type does not match one required by = operation: expected decimal' | ... -box.space.test:delete{2} +_ =3D box.space.test:delete{2} | --- | ... box.space.test.index.sk:alter{parts=3D{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=3D{2, 'decimal'}} -box.space.test:delete{2} +_ =3D box.space.test:delete{2} box.space.test.index.sk:alter{parts=3D{2, 'decimal'}} box.space.test:insert{3, decimal.new(3)} --failure -- Serge Petrenko sergepetrenko@tarantool.org > 8 =D0=B0=D0=B2=D0=B3. 2019 =D0=B3., =D0=B2 14:55, Serge Petrenko = =D0=BD=D0=B0=D0=BF=D0=B8=D1=81=D0=B0=D0=BB(=D0= =B0): >=20 > Closes #4333 >=20 > @TarantoolBot document > Title: Document decimal field type. >=20 > 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. >=20 > 'decimal' field type is appropriate for both memtx HASH and TREE > indices, as well as for vinyl TREE index. >=20 > ``` > To create an index 'pk' over a decimal field, say > ``` > tarantool> box.space.test:create_index('pk', {parts=3D{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 =3D 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=3D{1, 'number'}}) > tarantool> box.space.test:insert{-1.0001, 'number'} > --- > - [-1.0001, 'number'] > ... >=20 > tarantool> box.space.test:insert{decimal.new(-1.00001), 'decimal'} > --- > - [-1.00001, 'decimal'] > ... >=20 > tarantool> box.space.test:insert{-1, 'number'} > --- > - [-1, 'number'] > ... >=20 > tarantool> box.space.test:insert{decimal.new(-0.999), 'decimal'} > --- > - [-0.999, 'decimal'] > ... >=20 > tarantool> box.space.test:insert{-0.998, 'number'} > --- > - [-0.998, 'number'] > ... >=20 > tarantool> box.space.test:insert{-0.9, 'number'} > --- > - [-0.9, 'number'] > ... >=20 > tarantool> box.space.test:insert{-0.95, 'number'} > --- > - [-0.95, 'number'] > ... >=20 > tarantool> box.space.test:insert{decimal.new(-0.92), 'decimal'} > --- > - [-0.92, 'decimal'] > ... >=20 > tarantool> box.space.test:insert{decimal.new(-0.971), 'decimal'} > --- > - [-0.971, 'decimal'] > ... >=20 > 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'] > ... >=20 > ``` > 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' > ... >=20 > tarantool> box.space.test:insert{decimal.new(-0.9)} > --- > - error: Duplicate key exists in unique index 'pk' in space 'test' > ... >=20 > ``` >=20 > You can also set decimal fields in space format: > ``` > tarantool> _ =3D box.schema.space.create('test') > --- > ... >=20 > tarantool> _ =3D box.space.test:create_index('pk') > --- > ... >=20 > tarantool> box.space.test:format{{name=3D'id', type=3D'unsigned'}, = {name=3D'balance', type=3D'decimal'}} > --- > ... >=20 > tarantool> box.space.test:insert{1} > --- > - error: Tuple field 2 required by space format is missing > ... >=20 > tarantool> box.space.test:insert{1, 'string'} > --- > - error: 'Tuple field 2 type does not match one required by operation: = expected decimal' > ... >=20 > tarantool> box.space.test:insert{1, 1.2345} > --- > - error: 'Tuple field 2 type does not match one required by operation: = expected decimal' > ... >=20 > tarantool> box.space.test:insert{1, decimal.new('1337.420')} > --- > - [1, 1337.420] > ... >=20 > ``` > --- > 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(-) >=20 > 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" >=20 > const char *mp_type_strs[] =3D { > /* .MP_NIL =3D */ "nil", > @@ -52,13 +53,14 @@ const uint32_t field_mp_type[] =3D { > /* [FIELD_TYPE_UNSIGNED] =3D */ 1U << MP_FIELD_UINT, > /* [FIELD_TYPE_STRING] =3D */ 1U << MP_FIELD_STR, > /* [FIELD_TYPE_NUMBER] =3D */ (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] =3D */ (1U << MP_FIELD_UINT) | (1U << = MP_FIELD_INT), > /* [FIELD_TYPE_BOOLEAN] =3D */ 1U << MP_FIELD_BOOL, > /* [FIELD_TYPE_VARBINARY] =3D */ 1U << MP_FIELD_BIN, > /* [FIELD_TYPE_SCALAR] =3D */ (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] =3D */ 1U << MP_FIELD_DECIMAL, > /* [FIELD_TYPE_ARRAY] =3D */ 1U << MP_FIELD_ARRAY, > /* [FIELD_TYPE_MAP] =3D */ (1U << MP_FIELD_MAP), > }; > @@ -72,6 +74,7 @@ const char *field_type_strs[] =3D { > /* [FIELD_TYPE_BOOLEAN] =3D */ "boolean", > /* [FIELD_TYPE_VARBINARY] =3D */"varbinary", > /* [FIELD_TYPE_SCALAR] =3D */ "scalar", > + /* [FIELD_TYPE_DECIMAL] =3D */ "decimal", > /* [FIELD_TYPE_ARRAY] =3D */ "array", > /* [FIELD_TYPE_MAP] =3D */ "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[] =3D { > - /* 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, > }; >=20 > 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 > +#include "lib/core/decimal.h" > +#include "lib/core/mp_decimal.h" > +#include "lib/core/mp_user_types.h" >=20 > /* {{{ tuple_compare */ >=20 > @@ -88,7 +91,7 @@ static enum mp_class mp_classes[] =3D { > /* .MP_FIELD_BOOL =3D */ MP_CLASS_BOOL, > /* .MP_FIELD_FLOAT =3D */ MP_CLASS_NUMBER, > /* .MP_FIELD_DOUBLE =3D */ MP_CLASS_NUMBER, > - /* .MP_FIELD_BIN =3D */ MP_CLASS_BIN > + /* .MP_FIELD_DECIMAL =3D */ MP_CLASS_NUMBER > }; >=20 > #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); > } >=20 > +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 =3D mp_decode_float(&rhs); > + decimal_from_double(&rhs_dec, d); > + break; > + } > + case MP_FIELD_DOUBLE: > + { > + double d =3D mp_decode_double(&rhs); > + decimal_from_double(&rhs_dec, d); > + break; > + } > + case MP_FIELD_INT: > + { > + int64_t num =3D mp_decode_int(&rhs); > + decimal_from_int64(&rhs_dec, num); > + break; > + } > + case MP_FIELD_UINT: > + { > + uint64_t num =3D 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) =3D=3D MP_CLASS_NUMBER); > assert(mp_classof(rhs_type) =3D=3D MP_CLASS_NUMBER); >=20 > + /* > + * Test decimals first, so that we don't have to > + * account for them in other comparators. > + */ > + decimal_t dec; > + if (rhs_type =3D=3D MP_FIELD_DECIMAL) { > + return mp_compare_decimal_any_number( > + mp_decode_decimal(&rhs, &dec), lhs, lhs_type, -1 > + ); > + } > + if (lhs_type =3D=3D MP_FIELD_DECIMAL) { > + return mp_compare_decimal_any_number( > + mp_decode_decimal(&lhs, &dec), rhs, rhs_type, 1 > + ); > + } > if (rhs_type =3D=3D 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 !=3D 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); > } >=20 > +static inline hint_t > +hint_decimal(decimal_t *dec) > +{ > + uint64_t val =3D 0; > + int64_t num; > + if (decimal_to_int64(dec, &num) && > + num >=3D HINT_VALUE_INT_MIN && num <=3D HINT_VALUE_INT_MAX) = { > + val =3D num - HINT_VALUE_INT_MIN; > + } else if (!(dec->bits & DECNEG)) { > + val =3D 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; > } >=20 > +static inline hint_t > +field_hint_decimal(const char *field) > +{ > + assert(msgpack_to_field_type(field) =3D=3D 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 =3D 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(def); > break; > + case FIELD_TYPE_DECIMAL: > + key_def_set_hint_func(def); > + break; > default: > /* Invalid key definition. */ > def->key_hint =3D 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 >=20 > +#if defined(__cplusplus) > +extern "C" { > +#endif /* defined(__cplusplus) */ > + > typedef decNumber decimal_t; >=20 > /** > @@ -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); >=20 > +#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 >=20 > +#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); >=20 > +#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 =3D require('decimal') > +--- > +... > -- Ensure that vinyl correctly process field count change. > s =3D box.schema.space.create('test', {engine =3D engine, field_count = =3D 2}) > --- > @@ -1092,13 +1095,16 @@ format[9] =3D {name =3D 'field9', type =3D = 'array'} > format[10] =3D {name =3D 'field10', type =3D 'map'} > --- > ... > +format[11] =3D {name =3D 'field11', type =3D 'decimal'} > +--- > +... > s =3D box.schema.space.create('test', {engine =3D engine, format =3D = format}) > --- > ... > pk =3D s:create_index('pk') > --- > ... > -t =3D s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val =3D = 10}} > +t =3D s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val =3D = 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. > -- >=20 > +decimal =3D require('decimal') > + > -- Ensure that vinyl correctly process field count change. > s =3D box.schema.space.create('test', {engine =3D engine, field_count = =3D 2}) > pk =3D s:create_index('pk') > @@ -376,9 +378,11 @@ format[7] =3D {name =3D 'field7', type =3D = 'boolean'} > format[8] =3D {name =3D 'field8', type =3D 'scalar'} > format[9] =3D {name =3D 'field9', type =3D 'array'} > format[10] =3D {name =3D 'field10', type =3D 'map'} > +format[11] =3D {name =3D 'field11', type =3D 'decimal'} > + > s =3D box.schema.space.create('test', {engine =3D engine, format =3D = format}) > pk =3D s:create_index('pk') > -t =3D s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val =3D = 10}} > +t =3D s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val =3D = 10}, decimal.new(-11.11)} >=20 > 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') >=20 > -- 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') >=20 > -- 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') >=20 > -- 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') >=20 > -- 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') >=20 > -- scalar -----> any > ok_format_change(8, 'any') > -- scalar --X--> unsigned > fail_format_change(8, 'unsigned') > +-- scalar --X--> decimal > +fail_format_change(8, 'decimal') >=20 > -- array -----> any > ok_format_change(9, 'any') > -- array --X--> scalar > fail_format_change(9, 'scalar') > +-- arary --X--> decimal > +fail_format_change(9, 'decimal') >=20 > -- 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() >=20 > -- 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 =3D test_run:get_cfg('engine') > decimal =3D require('decimal') > | --- > | ... > +ffi =3D require('ffi') > + | --- > + | ... >=20 > _ =3D box.schema.space.create('test', {engine=3Dengine}) > | --- > @@ -76,6 +79,278 @@ box.space.test:select{} > | - [3, 3.3] > | - [4, 1234567890123456789.9876543210987654321, 1.2345] > | ... > + > +box.space.test:drop() > + | --- > + | ... > + > +-- check decimal indexes > +_ =3D box.schema.space.create('test', {engine=3Dengine}) > + | --- > + | ... > +_ =3D box.space.test:create_index('pk', {parts=3D{1,'decimal'}}) > + | --- > + | ... > + > +test_run:cmd('setopt delimiter ";"') > + | --- > + | - true > + | ... > +for i =3D 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() > + | --- > + | ... > + > +_ =3D box.space.test:create_index('pk', {parts=3D{1, 'number'}}) > + | --- > + | ... > + > +test_run:cmd('setopt delimiter ";"') > + | --- > + | - true > + | ... > +for i =3D 0, 32 do > + local val =3D (i - 16) / 8 > + if i % 2 =3D=3D 1 then val =3D 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() > + | --- > + | ... > + > +_ =3D box.space.test:create_index('pk') > + | --- > + | ... > +test_run:cmd('setopt delimiter ";"') > + | --- > + | - true > + | ... > +for i =3D 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] > + | ... > + > +_ =3D box.space.test:create_index('sk', {parts=3D{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 > +_ =3D box.schema.space.create('test', {engine=3Dengine}) > + | --- > + | ... > +_ =3D box.space.test:create_index('pk') > + | --- > + | ... > +_ =3D box.space.test:create_index('sk', {parts=3D{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=3D{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=3D{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=3D{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 =3D env.new() > engine =3D test_run:get_cfg('engine') >=20 > decimal =3D require('decimal') > +ffi =3D require('ffi') >=20 > _ =3D box.schema.space.create('test', {engine=3Dengine}) > _ =3D 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 > +_ =3D box.schema.space.create('test', {engine=3Dengine}) > +_ =3D box.space.test:create_index('pk', {parts=3D{1,'decimal'}}) > + > +test_run:cmd('setopt delimiter ";"') > +for i =3D 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() > + > +_ =3D box.space.test:create_index('pk', {parts=3D{1, 'number'}}) > + > +test_run:cmd('setopt delimiter ";"') > +for i =3D 0, 32 do > + local val =3D (i - 16) / 8 > + if i % 2 =3D=3D 1 then val =3D 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() > + > +_ =3D box.space.test:create_index('pk') > +test_run:cmd('setopt delimiter ";"') > +for i =3D 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} > + > +_ =3D box.space.test:create_index('sk', {parts=3D{2, 'scalar'}}) > +box.space.test.index.sk:select{} > + > +box.space.test:drop() > + > +-- check index alter > +_ =3D box.schema.space.create('test', {engine=3Dengine}) > +_ =3D box.space.test:create_index('pk') > +_ =3D box.space.test:create_index('sk', {parts=3D{2, 'number'}}) > +box.space.test:insert{1, decimal.new(-2)} > +box.space.test:insert{2, -5} > +-- failure > +box.space.test.index.sk:alter{parts=3D{2, 'decimal'}} > +box.space.test:delete{2} > +box.space.test.index.sk:alter{parts=3D{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=3D{2, 'number'}} > +box.space.test:insert{2, -5} > +box.space.test.index.sk:select{} > + > box.space.test:drop() > --=20 > 2.20.1 (Apple Git-117) >=20