[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