[PATCH v2 8/8] decimal: allow to index decimals
Serge Petrenko
sergepetrenko at tarantool.org
Thu Aug 8 14:55:59 MSK 2019
Closes #4333
@TarantoolBot document
Title: Document decimal field type.
Decimals may now be stored in spaces. A corresponding field type is
introduced: 'decimal'. Decimal values are also allowed in 'scalar',
'any' and 'number' fields.
'decimal' field type is appropriate for both memtx HASH and TREE
indices, as well as for vinyl TREE index.
```
To create an index 'pk' over a decimal field, say
```
tarantool> box.space.test:create_index('pk', {parts={1, 'decimal'}})
---
- unique: true
parts:
- type: decimal
is_nullable: false
fieldno: 1
id: 0
space_id: 512
type: TREE
name: pk
...
```
Now you can insert some decimal values:
```
tarantool> for i = 1,10 do
> box.space.test:insert{decimal.new((i-5)/10)}
> end
---
...
```
tarantool> box.space.test:select{}
---
- - [-0.4]
- [-0.3]
- [-0.2]
- [-0.1]
- [0]
- [0.1]
- [0.2]
- [0.3]
- [0.4]
- [0.5]
...
```
Decimals may alse be inserted into `scalar` and `number` fields. In this
case all the number values are sorted correctly:
```
tarantool> box.schema.space.create('test')
tarantool> box.space.test:create_index('pk', {parts={1, 'number'}})
tarantool> box.space.test:insert{-1.0001, 'number'}
---
- [-1.0001, 'number']
...
tarantool> box.space.test:insert{decimal.new(-1.00001), 'decimal'}
---
- [-1.00001, 'decimal']
...
tarantool> box.space.test:insert{-1, 'number'}
---
- [-1, 'number']
...
tarantool> box.space.test:insert{decimal.new(-0.999), 'decimal'}
---
- [-0.999, 'decimal']
...
tarantool> box.space.test:insert{-0.998, 'number'}
---
- [-0.998, 'number']
...
tarantool> box.space.test:insert{-0.9, 'number'}
---
- [-0.9, 'number']
...
tarantool> box.space.test:insert{-0.95, 'number'}
---
- [-0.95, 'number']
...
tarantool> box.space.test:insert{decimal.new(-0.92), 'decimal'}
---
- [-0.92, 'decimal']
...
tarantool> box.space.test:insert{decimal.new(-0.971), 'decimal'}
---
- [-0.971, 'decimal']
...
tarantool> box.space.test:select{}
---
- - [-1.0001, 'number']
- [-1.00001, 'decimal']
- [-1, 'number']
- [-0.999, 'decimal']
- [-0.998, 'number']
- [-0.971, 'decimal']
- [-0.95, 'number']
- [-0.92, 'decimal']
- [-0.9, 'number']
...
```
Uniqueness is also preserved between decimals and other number types:
```
tarantool> box.space.test:insert{-0.92}
---
- error: Duplicate key exists in unique index 'pk' in space 'test'
...
tarantool> box.space.test:insert{decimal.new(-0.9)}
---
- error: Duplicate key exists in unique index 'pk' in space 'test'
...
```
You can also set decimal fields in space format:
```
tarantool> _ = box.schema.space.create('test')
---
...
tarantool> _ = box.space.test:create_index('pk')
---
...
tarantool> box.space.test:format{{name='id', type='unsigned'}, {name='balance', type='decimal'}}
---
...
tarantool> box.space.test:insert{1}
---
- error: Tuple field 2 required by space format is missing
...
tarantool> box.space.test:insert{1, 'string'}
---
- error: 'Tuple field 2 type does not match one required by operation: expected decimal'
...
tarantool> box.space.test:insert{1, 1.2345}
---
- error: 'Tuple field 2 type does not match one required by operation: expected decimal'
...
tarantool> box.space.test:insert{1, decimal.new('1337.420')}
---
- [1, 1337.420]
...
```
---
src/box/field_def.c | 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