From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Serge Petrenko Message-Id: <6F071342-62B4-4122-BA02-46B1A518C51C@tarantool.org> Content-Type: multipart/alternative; boundary="Apple-Mail=_7402D318-73FE-40D4-8770-E2CD5272BA50" Mime-Version: 1.0 (Mac OS X Mail 12.4 \(3445.104.11\)) Subject: Re: [tarantool-patches] [PATCH v2 4/8] lua: rework luaL_field types to support msgpack extensions Date: Thu, 15 Aug 2019 11:27:07 +0300 In-Reply-To: <20190814222348.GF28533@atlas> References: <8732ff5bc9cfe6622a14e773a76053ee1a20fcf8.1565263272.git.sergepetrenko@tarantool.org> <20190812212329.GF32337@atlas> <20190814222348.GF28533@atlas> To: Konstantin Osipov Cc: Vladimir Davydov , tarantool-patches@freelists.org List-ID: --Apple-Mail=_7402D318-73FE-40D4-8770-E2CD5272BA50 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 > 15 =D0=B0=D0=B2=D0=B3. 2019 =D0=B3., =D0=B2 1:23, Konstantin Osipov = =D0=BD=D0=B0=D0=BF=D0=B8=D1=81=D0=B0=D0=BB(=D0=B0):= >=20 > * Serge Petrenko [19/08/13 16:44]: >> Sorry for the confusion. I guess mp_field_type is just a bad name. >> It has nothing to do with enum field_type. It just represents all the >> messagepack types, including the extension types. >=20 > No it doesn't. Please read here what messagepack types are: > https://en.wikipedia.org/wiki/MessagePack >=20 > It stores messagepack format codes and extension types. This is > confusing. >=20 >> It helps to eliminate a switch for extension type every time we get = an >> MP_EXT. So, it only deals with mp_type and mp_user_type. >=20 > I get that. >=20 >>> Look at mp_type when you can, and when you get MP_EXT or is >>> otherwise confused, look at field_type? >>=20 >> That=E2=80=99s what I=E2=80=99ve done in previous revision, but = Vladimir didn=E2=80=99t like it. >> And having implemented both variants, I agree that this enum = simplifies >> the code quite a lot. This is especially noticeable with patch 6. >>=20 >> So, my proposal is to change the name to something better. >> value_type? mp_value_type maybe? >=20 > Could you please show me the previous version of the patch? Here=E2=80=99s the old version of the patch on encoding/decoding = decimals in lua. This patch is based on top of the one introducing = mp_encode/decode_decimal, they=E2=80=99re not squashed yet in this revision. =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D It is now possible to insert decimals into spaces, but only into unindexed fields. Part-of #4333 --- extra/exports | 4 ++ src/lib/core/decimal.c | 10 +++- src/lib/core/mp_user_types.h | 3 +- src/lib/core/mpstream.c | 11 ++++ src/lib/core/mpstream.h | 4 ++ src/lua/decimal.c | 10 ++-- src/lua/decimal.h | 5 ++ src/lua/msgpack.c | 52 +++++++++++++---- src/lua/msgpackffi.lua | 33 +++++++++++ src/lua/utils.c | 16 ++++- src/lua/utils.h | 8 ++- test/app/decimal.result | 106 +++++++++++++++++----------------- test/app/msgpack.result | 41 +++++++++++++ test/app/msgpack.test.lua | 15 +++++ third_party/lua-yaml/lyaml.cc | 6 ++ 15 files changed, 248 insertions(+), 76 deletions(-) diff --git a/extra/exports b/extra/exports index b8c42c0df..7b84a1452 100644 --- a/extra/exports +++ b/extra/exports @@ -62,8 +62,12 @@ PMurHash32_Result crc32_calc mp_encode_double mp_encode_float +mp_encode_decimal mp_decode_double mp_decode_float +mp_decode_extl +mp_sizeof_decimal +decimal_unpack log_type say_set_log_level diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c index 1423ae418..2c69a773c 100644 --- a/src/lib/core/decimal.c +++ b/src/lib/core/decimal.c @@ -33,6 +33,7 @@ #include "third_party/decNumber/decContext.h" #include "third_party/decNumber/decPacked.h" #include "lib/core/tt_static.h" +#include "lib/msgpuck/msgpuck.h" #include #include #include /* DBL_DIG */ @@ -312,12 +313,15 @@ char * decimal_pack(char *data, const decimal_t *dec) { uint32_t len =3D decimal_len(dec); - *data++ =3D decimal_scale(dec); + /* reserve space for resulting scale */ + char *svp =3D data++; len--; int32_t scale; char *tmp =3D (char *)decPackedFromNumber((uint8_t *)data, len, = &scale, dec); assert(tmp =3D=3D data); - assert(scale =3D=3D (int32_t)decimal_scale(dec)); + /* scale may be negative, when exponent is > 0 */ + assert(scale =3D=3D (int32_t)decimal_scale(dec) || scale < 0); + mp_store_u8(svp, (int8_t)scale); (void)tmp; data +=3D len; return data; @@ -326,7 +330,7 @@ decimal_pack(char *data, const decimal_t *dec) decimal_t * decimal_unpack(const char **data, uint32_t len, decimal_t *dec) { - int32_t scale =3D *((*data)++); + int32_t scale =3D (int8_t)mp_load_u8(data); len--; decimal_t *res =3D decPackedToNumber((uint8_t *)*data, len, = &scale, dec); if (res) diff --git a/src/lib/core/mp_user_types.h b/src/lib/core/mp_user_types.h index 9158b40d3..8211e3e79 100644 --- a/src/lib/core/mp_user_types.h +++ b/src/lib/core/mp_user_types.h @@ -32,7 +32,8 @@ */ enum mp_user_type { - MP_DECIMAL =3D 0 + MP_UNKNOWN =3D 0, + MP_DECIMAL =3D 1 }; #endif diff --git a/src/lib/core/mpstream.c b/src/lib/core/mpstream.c index 8b7276ab1..a46b7962b 100644 --- a/src/lib/core/mpstream.c +++ b/src/lib/core/mpstream.c @@ -33,6 +33,7 @@ #include #include #include "msgpuck.h" +#include "mp_decimal.h" void mpstream_reserve_slow(struct mpstream *stream, size_t size) @@ -186,6 +187,16 @@ mpstream_encode_binl(struct mpstream *stream, = uint32_t len) mpstream_advance(stream, pos - data); } +void +mpstream_encode_decimal(struct mpstream *stream, decimal_t *val) +{ + char *data =3D mpstream_reserve(stream, mp_sizeof_decimal(val)); + if (data =3D=3D NULL) + return; + char *pos =3D mp_encode_decimal(data, val); + mpstream_advance(stream, pos - data); +} + void mpstream_memcpy(struct mpstream *stream, const void *src, uint32_t n) { diff --git a/src/lib/core/mpstream.h b/src/lib/core/mpstream.h index 69fa76f7f..44af28cb5 100644 --- a/src/lib/core/mpstream.h +++ b/src/lib/core/mpstream.h @@ -32,6 +32,7 @@ */ #include "diag.h" +#include "decimal.h" #if defined(__cplusplus) extern "C" { @@ -136,6 +137,9 @@ mpstream_encode_bool(struct mpstream *stream, bool = val); void mpstream_encode_binl(struct mpstream *stream, uint32_t len); +void +mpstream_encode_decimal(struct mpstream *stream, decimal_t *val); + /** Copies n bytes from memory area src to stream. */ void mpstream_memcpy(struct mpstream *stream, const void *src, uint32_t n); diff --git a/src/lua/decimal.c b/src/lua/decimal.c index e548cdb9d..ab8d85f75 100644 --- a/src/lua/decimal.c +++ b/src/lua/decimal.c @@ -31,7 +31,7 @@ #include "lua/decimal.h" #include "lib/core/decimal.h" -#include "lua/utils.h" +#include "lua/utils.h" /* CTID_DECIMAL, ... */ #include #include @@ -69,16 +69,18 @@ ldecimal_##name(struct lua_State *L) { = \ static int = \ ldecimal_##name(struct lua_State *L) { = \ assert(lua_gettop(L) =3D=3D 2); = \ + if (lua_isnil(L, 1) || lua_isnil(L, 2)) { = \ + lua_pushboolean(L, false); = \ + return 1; = \ + } = \ decimal_t *lhs =3D lua_todecimal(L, 1); = \ decimal_t *rhs =3D lua_todecimal(L, 2); = \ lua_pushboolean(L, decimal_compare(lhs, rhs) cmp 0); = \ return 1; = \ } -static uint32_t CTID_DECIMAL; - /** Push a new decimal on the stack and return a pointer to it. */ -static decimal_t * +decimal_t * lua_pushdecimal(struct lua_State *L) { decimal_t *res =3D luaL_pushcdata(L, CTID_DECIMAL); diff --git a/src/lua/decimal.h b/src/lua/decimal.h index 0485d11ef..b5c0e54b4 100644 --- a/src/lua/decimal.h +++ b/src/lua/decimal.h @@ -31,12 +31,17 @@ #ifndef TARANTOOL_LUA_DECIMAL_H_INCLUDED #define TARANTOOL_LUA_DECIMAL_H_INCLUDED +#include "lib/core/decimal.h" + #if defined(__cplusplus) extern "C" { #endif /* defined(__cplusplus) */ struct lua_State; +decimal_t * +lua_pushdecimal(struct lua_State *L); + void tarantool_lua_decimal_init(struct lua_State *L); diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c index 2126988eb..3cb7d7dcd 100644 --- a/src/lua/msgpack.c +++ b/src/lua/msgpack.c @@ -41,6 +41,10 @@ #include #include +#include "lua/decimal.h" +#include "lib/core/decimal.h" +#include "lib/core/mp_user_types.h" + #include void @@ -175,16 +179,23 @@ restart: /* used by MP_EXT */ assert(lua_gettop(L) =3D=3D top); return MP_ARRAY; case MP_EXT: - /* Run trigger if type can't be encoded */ - type =3D luamp_encode_extension(L, top, stream); - if (type !=3D MP_EXT) - return type; /* Value has been packed by the = trigger */ - /* Try to convert value to serializable type */ - luaL_convertfield(L, cfg, top, field); - /* handled by luaL_convertfield */ - assert(field->type !=3D MP_EXT); - assert(lua_gettop(L) =3D=3D top); - goto restart; + switch (field->ext_type) + { + case MP_DECIMAL: + mpstream_encode_decimal(stream, field->decval); + return MP_EXT; + case MP_UNKNOWN: + /* Run trigger if type can't be encoded */ + type =3D luamp_encode_extension(L, top, stream); + if (type !=3D MP_EXT) + return type; /* Value has been packed by = the trigger */ + /* Try to convert value to serializable type */ + luaL_convertfield(L, cfg, top, field); + /* handled by luaL_convertfield */ + assert(field->type !=3D MP_EXT || = field->ext_type !=3D MP_UNKNOWN); + assert(lua_gettop(L) =3D=3D top); + goto restart; + } } return MP_EXT; } @@ -283,9 +294,28 @@ luamp_decode(struct lua_State *L, struct = luaL_serializer *cfg, return; } case MP_EXT: - luamp_decode_extension(L, data); + { + uint32_t len; + int8_t type; + len =3D mp_decode_extl(data, &type); + switch (type) { + case MP_DECIMAL: + { + decimal_t *dec =3D lua_pushdecimal(L); + dec =3D decimal_unpack(data, len, dec); + if (dec =3D=3D NULL) { + lua_pop(L, -1); + luaL_error(L, "msgpack.decode: " + "invalid MsgPack."); + } + return; + } + default: + luamp_decode_extension(L, data); + } break; } + } } diff --git a/src/lua/msgpackffi.lua b/src/lua/msgpackffi.lua index bfeedbc4b..4c799eed0 100644 --- a/src/lua/msgpackffi.lua +++ b/src/lua/msgpackffi.lua @@ -17,10 +17,18 @@ char * mp_encode_float(char *data, float num); char * mp_encode_double(char *data, double num); +char * +mp_encode_decimal(char *data, decimal_t *dec); +uint32_t +mp_sizeof_decimal(const decimal_t *dec); float mp_decode_float(const char **data); double mp_decode_double(const char **data); +uint32_t +mp_decode_extl(const char **data, int8_t *type); +decimal_t * +decimal_unpack(const char **data, uint32_t len, decimal_t *dec); ]]) local strict_alignment =3D (jit.arch =3D=3D 'arm') @@ -117,6 +125,11 @@ local function encode_double(buf, num) builtin.mp_encode_double(p, num) end +local function encode_decimal(buf, num) + local p =3D buf:alloc(builtin.mp_sizeof_decimal(num)) + builtin.mp_encode_decimal(p, num) +end + local function encode_int(buf, num) if num >=3D 0 then if num <=3D 0x7f then @@ -294,6 +307,7 @@ on_encode(ffi.typeof('const unsigned char'), = encode_int) on_encode(ffi.typeof('bool'), encode_bool_cdata) on_encode(ffi.typeof('float'), encode_float) on_encode(ffi.typeof('double'), encode_double) +on_encode(ffi.typeof('decimal_t'), encode_decimal) = --------------------------------------------------------------------------= ------ -- Decoder @@ -473,6 +487,23 @@ local function decode_map(data, size) return setmetatable(map, msgpack.map_mt) end +local function decode_ext(data) + local t =3D ffi.new("int8_t[1]") + -- mp_decode_extl and mp_decode_decimal + -- need type code + data[0] =3D data[0] - 1 + local old_data =3D data[0] + local len =3D builtin.mp_decode_extl(data, t) + --MP_DECIMAL + if t[0] =3D=3D 1 then + local num =3D ffi.new("decimal_t") + builtin.decimal_unpack(data, len, num) + return num + else + error("Unsupported extension type") + end +end + local decoder_hint =3D { --[[{{{ MP_BIN]] [0xc4] =3D function(data) return decode_str(data, decode_u8(data)) = end; @@ -528,6 +559,8 @@ decode_r =3D function(data) return false elseif c =3D=3D 0xc3 then return true + elseif c >=3D 0xd4 and c <=3D 0xd8 or c >=3D 0xc7 and c <=3D 0xc9 = then + return decode_ext(data) else local fun =3D decoder_hint[c]; assert (type(fun) =3D=3D "function") diff --git a/src/lua/utils.c b/src/lua/utils.c index 0a4bcf517..47cf030ab 100644 --- a/src/lua/utils.c +++ b/src/lua/utils.c @@ -45,6 +45,8 @@ static uint32_t CTID_STRUCT_IBUF; static uint32_t CTID_STRUCT_IBUF_PTR; uint32_t CTID_CHAR_PTR; uint32_t CTID_CONST_CHAR_PTR; +uint32_t CTID_DECIMAL; + void * luaL_pushcdata(struct lua_State *L, uint32_t ctypeid) @@ -723,6 +725,12 @@ luaL_tofield(struct lua_State *L, struct = luaL_serializer *cfg, int index, /* Fall through */ default: field->type =3D MP_EXT; + if (cd->ctypeid =3D=3D CTID_DECIMAL) { + field->ext_type =3D MP_DECIMAL; + field->decval =3D (decimal_t *) cdata; + } else { + field->ext_type =3D MP_UNKNOWN; + } } return 0; } @@ -754,6 +762,7 @@ luaL_tofield(struct lua_State *L, struct = luaL_serializer *cfg, int index, /* Fall through */ default: field->type =3D MP_EXT; + field->ext_type =3D MP_UNKNOWN; } #undef CHECK_NUMBER return 0; @@ -765,7 +774,7 @@ luaL_convertfield(struct lua_State *L, struct = luaL_serializer *cfg, int idx, { if (idx < 0) idx =3D lua_gettop(L) + idx + 1; - assert(field->type =3D=3D MP_EXT); /* must be called after = tofield() */ + assert(field->type =3D=3D MP_EXT && field->ext_type =3D=3D = MP_UNKNOWN); /* must be called after tofield() */ if (cfg->encode_load_metatables) { int type =3D lua_type(L, idx); @@ -782,10 +791,11 @@ luaL_convertfield(struct lua_State *L, struct = luaL_serializer *cfg, int idx, } } - if (field->type =3D=3D MP_EXT && cfg->encode_use_tostring) + if (field->type =3D=3D MP_EXT && field->ext_type =3D=3D = MP_UNKNOWN && + cfg->encode_use_tostring) lua_field_tostring(L, cfg, idx, field); - if (field->type !=3D MP_EXT) + if (field->type !=3D MP_EXT || field->ext_type !=3D MP_UNKNOWN) return; if (cfg->encode_invalid_as_nil) { diff --git a/src/lua/utils.h b/src/lua/utils.h index 7e7cdc0c6..3ca43292b 100644 --- a/src/lua/utils.h +++ b/src/lua/utils.h @@ -54,6 +54,8 @@ extern "C" { #include #include "lua/error.h" +#include "lib/core/mp_user_types.h" +#include "lib/core/decimal.h" /* decimal_t */ struct lua_State; struct ibuf; @@ -69,6 +71,8 @@ extern struct ibuf *tarantool_lua_ibuf; extern uint32_t CTID_CONST_CHAR_PTR; extern uint32_t CTID_CHAR_PTR; +extern uint32_t CTID_DECIMAL; + /** \cond public */ @@ -286,8 +290,10 @@ struct luaL_field { bool bval; /* Array or map. */ uint32_t size; + decimal_t *decval; }; enum mp_type type; + enum mp_user_type ext_type; bool compact; /* a flag used by YAML serializer = */ }; @@ -373,7 +379,7 @@ luaL_checkfield(struct lua_State *L, struct = luaL_serializer *cfg, int idx, { if (luaL_tofield(L, cfg, idx, field) < 0) luaT_error(L); - if (field->type !=3D MP_EXT) + if (field->type !=3D MP_EXT || field->ext_type !=3D MP_UNKNOWN) return; luaL_convertfield(L, cfg, idx, field); } diff --git a/test/app/decimal.result b/test/app/decimal.result index 53ec73edc..334c9fbab 100644 --- a/test/app/decimal.result +++ b/test/app/decimal.result @@ -12,77 +12,77 @@ ffi =3D require('ffi') -- check various constructors decimal.new('1234.5678') | --- - | - '1234.5678' + | - 1234.5678 | ... decimal.new('1e6') | --- - | - '1000000' + | - 1000000 | ... decimal.new('-6.234612e2') | --- - | - '-623.4612' + | - -623.4612 | ... -- check (u)int16/32/64_t decimal.new(2ULL ^ 63) | --- - | - '9223372036854775808' + | - 9223372036854775808 | ... decimal.new(123456789123456789ULL) | --- - | - '123456789123456789' + | - 123456789123456789 | ... decimal.new(-123456789123456789LL) | --- - | - '-123456789123456789' + | - -123456789123456789 | ... decimal.new(ffi.new('uint8_t', 231)) | --- - | - '231' + | - 231 | ... decimal.new(ffi.new('int8_t', -113)) | --- - | - '-113' + | - -113 | ... decimal.new(ffi.new('uint16_t', 65535)) | --- - | - '65535' + | - 65535 | ... decimal.new(ffi.new('int16_t', -31263)) | --- - | - '-31263' + | - -31263 | ... decimal.new(ffi.new('uint32_t', 4123123123)) | --- - | - '4123123123' + | - 4123123123 | ... decimal.new(ffi.new('int32_t', -2123123123)) | --- - | - '-2123123123' + | - -2123123123 | ... decimal.new(ffi.new('float', 128.5)) | --- - | - '128.5' + | - 128.5 | ... decimal.new(ffi.new('double', 128.5)) | --- - | - '128.5' + | - 128.5 | ... decimal.new(1) | --- - | - '1' + | - 1 | ... decimal.new(-1) | --- - | - '-1' + | - -1 | ... decimal.new(2^64) | --- - | - '18446744073709600000' + | - 18446744073709600000 | ... decimal.new(2^(-20)) | --- - | - '0.00000095367431640625' + | - 0.00000095367431640625 | ... -- incorrect constructions @@ -128,38 +128,38 @@ a =3D decimal.new('10') | ... a | --- - | - '10' + | - 10 | ... b =3D decimal.new('0.1') | --- | ... b | --- - | - '0.1' + | - 0.1 | ... a + b | --- - | - '10.1' + | - 10.1 | ... a - b | --- - | - '9.9' + | - 9.9 | ... a * b | --- - | - '1.0' + | - 1.0 | ... a / b | --- - | - '100' + | - 100 | ... a ^ b | --- - | - '1.2589254117941672104239541063958006061' + | - 1.2589254117941672104239541063958006061 | ... b ^ a | --- - | - '0.0000000001' + | - 0.0000000001 | ... -a + -b =3D=3D -(a + b) | --- @@ -167,11 +167,11 @@ b ^ a | ... a | --- - | - '10' + | - 10 | ... b | --- - | - '0.1' + | - 0.1 | ... a < b @@ -216,28 +216,28 @@ a ~=3D b | ... a | --- - | - '10' + | - 10 | ... b | --- - | - '0.1' + | - 0.1 | ... decimal.sqrt(a) | --- - | - '3.1622776601683793319988935444327185337' + | - 3.1622776601683793319988935444327185337 | ... decimal.ln(a) | --- - | - '2.3025850929940456840179914546843642076' + | - 2.3025850929940456840179914546843642076 | ... decimal.log10(a) | --- - | - '1' + | - 1 | ... decimal.exp(a) | --- - | - '22026.465794806716516957900645284244366' + | - 22026.465794806716516957900645284244366 | ... a =3D=3D decimal.ln(decimal.exp(a)) | --- @@ -261,7 +261,7 @@ a + -a =3D=3D 0 | ... a | --- - | - '10' + | - 10 | ... a =3D decimal.new('1.1234567891234567891234567891234567891') @@ -269,7 +269,7 @@ a =3D = decimal.new('1.1234567891234567891234567891234567891') | ... a | --- - | - '1.1234567891234567891234567891234567891' + | - 1.1234567891234567891234567891234567891 | ... decimal.precision(a) | --- @@ -285,7 +285,7 @@ decimal.round(a, 37) =3D=3D a | ... a | --- - | - '1.1234567891234567891234567891234567891' + | - 1.1234567891234567891234567891234567891 | ... a =3D decimal.round(a, 36) | --- @@ -309,19 +309,19 @@ decimal.round(a, -5) =3D=3D a | ... decimal.round(a, 7) | --- - | - '1.1234568' + | - 1.1234568 | ... decimal.round(a, 3) | --- - | - '1.123' + | - 1.123 | ... decimal.round(a, 0) | --- - | - '1' + | - 1 | ... a | --- - | - '1.123456789123456789123456789123456789' + | - 1.123456789123456789123456789123456789 | ... decimal.ln(0) @@ -334,7 +334,7 @@ decimal.ln(-1) | ... decimal.ln(1) | --- - | - '0' + | - 0 | ... decimal.log10(0) | --- @@ -346,7 +346,7 @@ decimal.log10(-1) | ... decimal.log10(1) | --- - | - '0' + | - 0 | ... decimal.exp(88) | --- @@ -354,7 +354,7 @@ decimal.exp(88) | ... decimal.exp(87) | --- - | - '60760302250568721495223289381302760753' + | - 60760302250568721495223289381302760753 | ... decimal.sqrt(-5) | --- @@ -362,7 +362,7 @@ decimal.sqrt(-5) | ... decimal.sqrt(5) | --- - | - '2.2360679774997896964091736687312762354' + | - 2.2360679774997896964091736687312762354 | ... -- various incorrect operands @@ -408,11 +408,11 @@ a ^ 2 | ... a ^ 1.9 | --- - | - '1258925411794167210423954106395800606.1' + | - 1258925411794167210423954106395800606.1 | ... a * '1e18' | --- - | - '10000000000000000000000000000000000000' + | - 10000000000000000000000000000000000000 | ... a =3D decimal.new(string.rep('9', 38)) | --- @@ -435,7 +435,7 @@ a + '0.5' | ... a + '0.4' | --- - | - '99999999999999999999999999999999999999' + | - 99999999999999999999999999999999999999 | ... a / 0.5 | --- @@ -451,7 +451,7 @@ a =3D decimal.new('-13') | ... a ^ 2 | --- - | - '169' + | - 169 | ... -- fractional powers are allowed only for positive numbers a ^ 2.5 @@ -462,17 +462,17 @@ a ^ 2.5 -- check correct rounding when scale =3D 0 decimal.round(decimal.new(0.9), 0) | --- - | - '1' + | - 1 | ... decimal.round(decimal.new(9.9), 0) | --- - | - '10' + | - 10 | ... decimal.round(decimal.new(99.9), 0) | --- - | - '100' + | - 100 | ... decimal.round(decimal.new(99.4), 0) | --- - | - '99' + | - 99 | ... diff --git a/test/app/msgpack.result b/test/app/msgpack.result index a67c05d38..4b5aec784 100644 --- a/test/app/msgpack.result +++ b/test/app/msgpack.result @@ -252,3 +252,44 @@ msgpack.decode(ffi.cast('char *', '\x04\x05\x06'), = -1) --- - error: 'msgpack.decode: size can''t be negative' ... +-- +-- gh-4333: msgpack encode/decode decimals. +-- +decimal =3D require('decimal') +--- +... +a =3D decimal.new('1e37') +--- +... +b =3D decimal.new('1e-38') +--- +... +c =3D decimal.new('1') +--- +... +d =3D decimal.new('0.1234567') +--- +... +e =3D decimal.new('123.4567') +--- +... +msgpack.decode(msgpack.encode(a)) =3D=3D a +--- +- true +... +msgpack.decode(msgpack.encode(b)) =3D=3D b +--- +- true +... +msgpack.decode(msgpack.encode(c)) =3D=3D c +--- +- true +... +msgpack.decode(msgpack.encode(d)) =3D=3D d +--- +- true +... +msgpack.decode(msgpack.encode(e)) =3D=3D e +--- +- true +... diff --git a/test/app/msgpack.test.lua b/test/app/msgpack.test.lua index e0880ac22..9224d870a 100644 --- a/test/app/msgpack.test.lua +++ b/test/app/msgpack.test.lua @@ -84,3 +84,18 @@ size =3D msgpack.encode(100, buf) -- is not negative. -- msgpack.decode(ffi.cast('char *', '\x04\x05\x06'), -1) + +-- +-- gh-4333: msgpack encode/decode decimals. +-- +decimal =3D require('decimal') +a =3D decimal.new('1e37') +b =3D decimal.new('1e-38') +c =3D decimal.new('1') +d =3D decimal.new('0.1234567') +e =3D decimal.new('123.4567') +msgpack.decode(msgpack.encode(a)) =3D=3D a +msgpack.decode(msgpack.encode(b)) =3D=3D b +msgpack.decode(msgpack.encode(c)) =3D=3D c +msgpack.decode(msgpack.encode(d)) =3D=3D d +msgpack.decode(msgpack.encode(e)) =3D=3D e diff --git a/third_party/lua-yaml/lyaml.cc = b/third_party/lua-yaml/lyaml.cc index 46c98bde1..8ff2867e3 100644 --- a/third_party/lua-yaml/lyaml.cc +++ b/third_party/lua-yaml/lyaml.cc @@ -49,6 +49,7 @@ extern "C" { #include "b64.h" } /* extern "C" */ #include "lua/utils.h" +#include "lib/core/decimal.h" #define LUAYAML_TAG_PREFIX "tag:yaml.org ,2002:" @@ -693,6 +694,11 @@ static int dump_node(struct lua_yaml_dumper = *dumper) len =3D 4; break; case MP_EXT: + if (field.ext_type =3D=3D MP_DECIMAL) { + str =3D decimal_to_string(field.decval); + len =3D strlen(str); + break; + } assert(0); /* checked by luaL_checkfield() */ break; } --=20 2.20.1 (Apple Git-117) =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D Here=E2=80=99s the old version of the patch adding decimal indices =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D Indices can now be built over decimal fields. A new field type - 'decimal' is introduced. Decimal values may be stored in 'decimal' columns, as well as in 'scalar' and 'any' columns. Closes #4333 @TarantoolBot document Title: Document decimal field type. Decimals may now be stored in spaces. A corresponding field type is introduced: 'decimal'. Decimal values are also allowed in 'scalar' and 'number' fields. 'decimal' field type is appropriate for both memtx HASH and TREE indices, as well as for vinyl TREE index. --- src/box/field_def.c | 43 +++++-- src/box/field_def.h | 16 ++- src/box/key_def.h | 2 +- src/box/tuple_compare.cc | 160 = ++++++++++++++++++++++++- src/box/tuple_format.c | 2 +- src/lib/core/decimal.h | 8 ++ src/lib/core/mp_decimal.h | 8 ++ test/engine/decimal.result | 226 +++++++++++++++++++++++++++++++++++ test/engine/decimal.test.lua | 65 ++++++++++ 9 files changed, 510 insertions(+), 20 deletions(-) create mode 100644 test/engine/decimal.result create mode 100644 test/engine/decimal.test.lua diff --git a/src/box/field_def.c b/src/box/field_def.c index 346042b98..da06e6bde 100644 --- a/src/box/field_def.c +++ b/src/box/field_def.c @@ -52,17 +52,32 @@ const uint32_t field_mp_type[] =3D { /* [FIELD_TYPE_UNSIGNED] =3D */ 1U << MP_UINT, /* [FIELD_TYPE_STRING] =3D */ 1U << MP_STR, /* [FIELD_TYPE_NUMBER] =3D */ (1U << MP_UINT) | (1U << = MP_INT) | - (1U << MP_FLOAT) | (1U << MP_DOUBLE), + (1U << MP_FLOAT) | (1U << MP_DOUBLE) | (1U << MP_EXT), /* [FIELD_TYPE_INTEGER] =3D */ (1U << MP_UINT) | (1U << = MP_INT), /* [FIELD_TYPE_BOOLEAN] =3D */ 1U << MP_BOOL, /* [FIELD_TYPE_VARBINARY] =3D */ 1U << MP_BIN, /* [FIELD_TYPE_SCALAR] =3D */ (1U << MP_UINT) | (1U << = MP_INT) | (1U << MP_FLOAT) | (1U << MP_DOUBLE) | (1U << MP_STR) | - (1U << MP_BIN) | (1U << MP_BOOL), + (1U << MP_BIN) | (1U << MP_BOOL) | (1U << MP_EXT), + /* [FIELD_TYPE_DECIMAL] =3D */ 1U << MP_EXT, /* [FIELD_TYPE_ARRAY] =3D */ 1U << MP_ARRAY, /* [FIELD_TYPE_MAP] =3D */ (1U << MP_MAP), }; +const uint32_t field_ext_type[] =3D { + /* [FIELD_TYPE_ANY] =3D */ UINT32_MAX & ~(1U << = MP_UNKNOWN), + /* [FIELD_TYPE_UNSIGNED] =3D */ 0, + /* [FIELD_TYPE_STRING] =3D */ 0, + /* [FIELD_TYPE_NUMBER] =3D */ 1U << MP_DECIMAL, + /* [FIELD_TYPE_INTEGER] =3D */ 0, + /* [FIELD_TYPE_BOOLEAN] =3D */ 0, + /* [FIELD_TYPE_VARBINARY] =3D */ 0, + /* [FIELD_TYPE_SCALAR] =3D */ 1U << MP_DECIMAL, + /* [FIELD_TYPE_DECIMAL] =3D */ 1U << MP_DECIMAL, + /* [FIELD_TYPE_ARRAY] =3D */ 0, + /* [FIELD_TYPE_MAP] =3D */ 0, +}; + const char *field_type_strs[] =3D { /* [FIELD_TYPE_ANY] =3D */ "any", /* [FIELD_TYPE_UNSIGNED] =3D */ "unsigned", @@ -72,6 +87,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 +114,18 @@ field_type_by_name_wrapper(const char *str, = uint32_t len) * values can be stored in the j type. */ static const bool field_type_compatibility[] =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, }; bool diff --git a/src/box/field_def.h b/src/box/field_def.h index c1a7ec0a9..10dc9fc13 100644 --- a/src/box/field_def.h +++ b/src/box/field_def.h @@ -37,6 +37,7 @@ #include #include #include "opt_def.h" +#include "lib/core/mp_user_types.h" #if defined(__cplusplus) extern "C" { @@ -58,6 +59,7 @@ enum field_type { FIELD_TYPE_BOOLEAN, FIELD_TYPE_VARBINARY, FIELD_TYPE_SCALAR, + FIELD_TYPE_DECIMAL, FIELD_TYPE_ARRAY, FIELD_TYPE_MAP, field_type_MAX @@ -109,8 +111,9 @@ field_type_by_name(const char *name, size_t len); /* MsgPack type names */ extern const char *mp_type_strs[]; -/** A helper table for field_mp_type_is_compatible */ +/** Two helper tables for field_mp_type_is_compatible */ extern const uint32_t field_mp_type[]; +extern const uint32_t field_ext_type[]; extern const struct opt_def field_def_reg[]; extern const struct field_def field_def_default; @@ -142,15 +145,20 @@ struct field_def { struct Expr *default_value_expr; }; -/** Checks if mp_type (MsgPack) is compatible with field type. */ +/** Checks if mp_typeof(data) is compatible with field type. */ static inline bool -field_mp_type_is_compatible(enum field_type type, enum mp_type mp_type, +field_mp_type_is_compatible(enum field_type type, const char *data, bool is_nullable) { + enum mp_type mp_type =3D mp_typeof(*data); + int8_t ext_type =3D MP_UNKNOWN; assert(type < field_type_MAX); assert((size_t)mp_type < CHAR_BIT * sizeof(*field_mp_type)); uint32_t mask =3D field_mp_type[type] | (is_nullable * (1U << = MP_NIL)); - return (mask & (1U << mp_type)) !=3D 0; + if (mp_type !=3D MP_EXT) + return (mask & (1U << mp_type)) !=3D 0; + mp_decode_extl(&data, &ext_type); + return (field_ext_type[type] & (1U << ext_type)) !=3D 0; } static inline bool diff --git a/src/box/key_def.h b/src/box/key_def.h index f4a1a8fd1..ed3354ade 100644 --- a/src/box/key_def.h +++ b/src/box/key_def.h @@ -505,7 +505,7 @@ static inline int key_part_validate(enum field_type key_type, const char *key, uint32_t field_no, bool is_nullable) { - if (unlikely(!field_mp_type_is_compatible(key_type, = mp_typeof(*key), + if (unlikely(!field_mp_type_is_compatible(key_type, key, is_nullable))) { diag_set(ClientError, ER_KEY_PART_TYPE, field_no, field_type_strs[key_type]); diff --git a/src/box/tuple_compare.cc = b/src/box/tuple_compare.cc index 95a0f58c9..6ab28f662 100644 --- a/src/box/tuple_compare.cc +++ b/src/box/tuple_compare.cc @@ -33,6 +33,9 @@ #include "coll/coll.h" #include "trivia/util.h" /* NOINLINE */ #include +#include "lib/core/decimal.h" +#include "lib/core/mp_decimal.h" +#include "lib/core/mp_user_types.h" /* {{{ tuple_compare */ @@ -87,7 +90,7 @@ static enum mp_class mp_classes[] =3D { /* .MP_BOOL =3D */ MP_CLASS_BOOL, /* .MP_FLOAT =3D */ MP_CLASS_NUMBER, /* .MP_DOUBLE =3D */ MP_CLASS_NUMBER, - /* .MP_BIN =3D */ MP_CLASS_BIN + /* .MP_EXT =3D */ MP_CLASS_NUMBER /* requires additional = parsing */ }; #define COMPARE_RESULT(a, b) (a < b ? -1 : a > b) @@ -264,6 +267,50 @@ mp_compare_double_any_number(double lhs, const char = *rhs, return k * COMPARE_RESULT(lqbit, rqbit); } +static int +mp_compare_decimal_any_number(decimal_t *lhs, const char *rhs, + enum mp_type rhs_type, int k) +{ + decimal_t rhs_dec; + switch (rhs_type) { + case MP_FLOAT: + { + double d =3D mp_decode_float(&rhs); + decimal_from_double(&rhs_dec, d); + break; + } + case MP_DOUBLE: + { + double d =3D mp_decode_double(&rhs); + decimal_from_double(&rhs_dec, d); + break; + } + case MP_INT: + { + int64_t num =3D mp_decode_int(&rhs); + decimal_from_int64(&rhs_dec, num); + break; + } + case MP_UINT: + { + uint64_t num =3D mp_decode_uint(&rhs); + decimal_from_uint64(&rhs_dec, num); + break; + } + case MP_EXT: + { + int8_t ext_type; + uint32_t len =3D mp_decode_extl(&rhs, &ext_type); + assert(ext_type =3D=3D MP_DECIMAL); + decimal_unpack(&rhs, len, &rhs_dec); + break; + } + default: + unreachable(); + } + return k * decimal_compare(lhs, &rhs_dec); +} + static int mp_compare_number_with_type(const char *lhs, enum mp_type lhs_type, const char *rhs, enum mp_type rhs_type) @@ -271,6 +318,21 @@ mp_compare_number_with_type(const char *lhs, enum = mp_type lhs_type, assert(mp_classof(lhs_type) =3D=3D MP_CLASS_NUMBER); assert(mp_classof(rhs_type) =3D=3D MP_CLASS_NUMBER); + /* + * test decimals first, so that we don't have to + * account for them in other comarators. + */ + decimal_t dec; + if (rhs_type =3D=3D MP_EXT) { + return mp_compare_decimal_any_number( + mp_decode_decimal(&rhs, &dec), lhs, lhs_type, -1 + ); + } + if (lhs_type =3D=3D MP_EXT) { + return mp_compare_decimal_any_number( + mp_decode_decimal(&lhs, &dec), rhs, rhs_type, 1 + ); + } if (rhs_type =3D=3D MP_FLOAT) { return mp_compare_double_any_number( mp_decode_float(&rhs), lhs, lhs_type, -1 @@ -410,6 +472,8 @@ tuple_compare_field(const char *field_a, const char = *field_b, return coll !=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; @@ -443,6 +507,8 @@ tuple_compare_field_with_type(const char *field_a, = enum mp_type a_type, mp_compare_scalar_coll(field_a, field_b, coll) : mp_compare_scalar_with_type(field_a, a_type, field_b, b_type); + case FIELD_TYPE_DECIMAL: + return mp_compare_number(field_a, field_b); default: unreachable(); return 0; @@ -1356,6 +1422,25 @@ static const comparator_with_key_signature = cmp_wk_arr[] =3D { #define HINT_VALUE_DOUBLE_MAX (exp2(HINT_VALUE_BITS - 1) - 1) #define HINT_VALUE_DOUBLE_MIN (-exp2(HINT_VALUE_BITS - 1)) +/** + * Max and min decimal numbers whose integral parts fit + * in a hint value. + */ +static const decimal_t HINT_VALUE_DECIMAL_MAX =3D { + 18, /* decimal digits */ + 0, /* exponent */ + 0, /* no special bits. */ + {487, 423, 303, 752, 460, 576} /* 576,460,752,303,423,488 =3D = 2^59 - 1 */ + /* 59 =3D=3D HINT_VALUE_BITS - 1 = */ +}; + +static const decimal_t HINT_VALUE_DECIMAL_MIN =3D { + 18, /* decimal digits */ + 0, /* exponent */ + 0x80, /* negative bit */ + {488, 423, 303, 752, 460, 576} /* 576,460,752,303,423,488 =3D = 2^59 */ +}; + /* * HINT_CLASS_BITS should be big enough to store any mp_class value. * Note, ((1 << HINT_CLASS_BITS) - 1) is reserved for HINT_NONE. @@ -1415,6 +1500,25 @@ hint_double(double d) return hint_create(MP_CLASS_NUMBER, val); } +static inline hint_t +hint_decimal(decimal_t *dec) +{ + uint64_t val =3D 0; + int64_t num; + if (decimal_compare(dec, &HINT_VALUE_DECIMAL_MAX) >=3D 0) + val =3D HINT_VALUE_MAX; + else if (decimal_compare(dec, &HINT_VALUE_DECIMAL_MIN) <=3D 0) + val =3D 0; + else { + dec =3D decimal_to_int64(dec, &num); + /* We've checked boundaries above. */ + assert(dec !=3D NULL); + val =3D num - HINT_VALUE_INT_MIN; + } + + return hint_create(MP_CLASS_NUMBER, val); +} + static inline uint64_t hint_str_raw(const char *s, uint32_t len) { @@ -1491,12 +1595,42 @@ field_hint_number(const char *field) return hint_double(mp_decode_float(&field)); case MP_DOUBLE: return hint_double(mp_decode_double(&field)); + case MP_EXT: + { + int8_t ext_type; + uint32_t len =3D mp_decode_extl(&field, &ext_type); + switch(ext_type) { + case MP_DECIMAL: + { + decimal_t dec; + decimal_t *res; + /* + * The effect of mp_decode_extl() + + * decimal_unpack() is the same that + * the one of mp_decode_decimal(). + */ + res =3D decimal_unpack(&field, len, &dec); + assert(res !=3D NULL); + return hint_decimal(res); + } + default: + unreachable(); + } + } default: unreachable(); } return HINT_NONE; } +static inline hint_t +field_hint_decimal(const char *field) +{ + assert(mp_typeof(*field) =3D=3D MP_EXT); + decimal_t dec; + return hint_decimal(mp_decode_decimal(&field, &dec)); +} + static inline hint_t field_hint_string(const char *field, struct coll *coll) { @@ -1536,6 +1670,25 @@ field_hint_scalar(const char *field, struct coll = *coll) case MP_BIN: len =3D mp_decode_binl(&field); return hint_bin(field, len); + case MP_EXT: + { + int8_t ext_type; + uint32_t len =3D mp_decode_extl(&field, &ext_type); + switch(ext_type) { + case MP_DECIMAL: + { + decimal_t dec; + /* + * The effect of mp_decode_extl() + + * decimal_unpack() is the same that + * the one of mp_decode_decimal(). + */ + return hint_decimal(decimal_unpack(&field, len, = &dec)); + } + default: + unreachable(); + } + } default: unreachable(); } @@ -1563,6 +1716,8 @@ field_hint(const char *field, struct coll *coll) return field_hint_varbinary(field); case FIELD_TYPE_SCALAR: return field_hint_scalar(field, coll); + case FIELD_TYPE_DECIMAL: + return field_hint_decimal(field); default: unreachable(); } @@ -1668,6 +1823,9 @@ key_def_set_hint_func(struct key_def *def) case FIELD_TYPE_SCALAR: key_def_set_hint_func(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/box/tuple_format.c b/src/box/tuple_format.c index 02fadf1cf..5bb659b87 100644 --- a/src/box/tuple_format.c +++ b/src/box/tuple_format.c @@ -1165,7 +1165,7 @@ tuple_format_iterator_next(struct = tuple_format_iterator *it, * defined in format. */ bool is_nullable =3D tuple_field_is_nullable(field); - if (!field_mp_type_is_compatible(field->type, = mp_typeof(*entry->data), + if (!field_mp_type_is_compatible(field->type, entry->data, is_nullable) !=3D 0) { diag_set(ClientError, ER_FIELD_TYPE, tuple_field_path(field), diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h index a4e7683c7..6a152b08c 100644 --- a/src/lib/core/decimal.h +++ b/src/lib/core/decimal.h @@ -37,6 +37,10 @@ #include "third_party/decNumber/decNumber.h" #include +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + typedef decNumber decimal_t; /** @@ -207,4 +211,8 @@ decimal_pack(char *data, const decimal_t *dec); decimal_t * decimal_unpack(const char **data, uint32_t len, decimal_t *dec); +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ + #endif /* TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED */ diff --git a/src/lib/core/mp_decimal.h b/src/lib/core/mp_decimal.h index a991a5f16..778529068 100644 --- a/src/lib/core/mp_decimal.h +++ b/src/lib/core/mp_decimal.h @@ -34,6 +34,10 @@ #include "decimal.h" #include +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + /** * \brief Calculate exact buffer size needed to store a decimal * pointed to by \a dec. @@ -59,4 +63,8 @@ mp_decode_decimal(const char **data, decimal_t *dec); char * mp_encode_decimal(char *data, const decimal_t *dec); +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ + #endif diff --git a/test/engine/decimal.result b/test/engine/decimal.result new file mode 100644 index 000000000..16cd335e0 --- /dev/null +++ b/test/engine/decimal.result @@ -0,0 +1,226 @@ +-- test-run result file version 2 +env =3D require('test_run') + | --- + | ... +test_run =3D env.new() + | --- + | ... +engine =3D test_run:get_cfg('engine') + | --- + | ... + +decimal =3D require('decimal') + | --- + | ... +ffi =3D require('ffi') + | --- + | ... + +_ =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() + | --- + | ... diff --git a/test/engine/decimal.test.lua b/test/engine/decimal.test.lua new file mode 100644 index 000000000..52a300e72 --- /dev/null +++ b/test/engine/decimal.test.lua @@ -0,0 +1,65 @@ +env =3D require('test_run') +test_run =3D env.new() +engine =3D test_run:get_cfg('engine') + +decimal =3D require('decimal') +ffi =3D require('ffi') + +_ =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() --=20 2.20.1 (Apple Git-117) >=20 > --=20 > Konstantin Osipov, Moscow, Russia -- Serge Petrenko sergepetrenko@tarantool.org --Apple-Mail=_7402D318-73FE-40D4-8770-E2CD5272BA50 Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=utf-8
15 = =D0=B0=D0=B2=D0=B3. 2019 =D0=B3., =D0=B2 1:23, Konstantin Osipov <kostja@tarantool.org> =D0=BD=D0=B0=D0=BF=D0=B8=D1=81=D0=B0= =D0=BB(=D0=B0):

* Serge Petrenko <sergepetrenko@tarantool.org> [19/08/13 16:44]:
Sorry for the confusion. = I guess mp_field_type is just a bad name.
It has nothing = to do with enum field_type. It just represents all the
messagepack types, including the extension types.

No it doesn't. Please read here = what messagepack types are:
https://en.wikipedia.org/wiki/MessagePack
It stores messagepack format codes and extension types. This = is
confusing.

It helps to eliminate a switch for extension = type every time we get an
MP_EXT. So, it only deals with = mp_type and mp_user_type.

I = get that.

Look at mp_type when you = can, and when you get MP_EXT or is
otherwise confused, = look at field_type?

That=E2=80=99= s what I=E2=80=99ve done in previous revision, but Vladimir didn=E2=80=99t= like it.
And having implemented both variants, I agree = that this enum simplifies
the code quite a lot. This is = especially noticeable with patch 6.

So, my = proposal is to change the name to something better.
value_type? mp_value_type maybe?
Could you please show me the previous version of the = patch?

Here=E2=80=99s the old version of the patch on = encoding/decoding decimals in lua.
This patch is based on top = of the one introducing mp_encode/decode_decimal,
they=E2=80=99re= not squashed yet in this revision.

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D
It is now possible to insert decimals into = spaces, but only into
unindexed fields.

Part-of #4333
---
extra/exports =             &n= bsp;   |   4 ++
src/lib/core/decimal.c =        |  10 +++-
src/lib/core/mp_user_types.h  |   3 +-
src/lib/core/mpstream.c       | =  11 ++++
src/lib/core/mpstream.h =       |   4 ++
src/lua/decimal.c =             | =  10 ++--
src/lua/decimal.h =             | =   5 ++
src/lua/msgpack.c =             | =  52 +++++++++++++----
src/lua/msgpackffi.lua =        |  33 +++++++++++
src/lua/utils.c =             &n= bsp; |  16 ++++-
src/lua/utils.h =             &n= bsp; |   8 ++-
test/app/decimal.result =       | 106 = +++++++++++++++++-----------------
test/app/msgpack.result =       |  41 +++++++++++++
test/app/msgpack.test.lua     |  15 = +++++
third_party/lua-yaml/lyaml.cc |   6 ++
15 files = changed, 248 insertions(+), 76 deletions(-)

diff --git a/extra/exports b/extra/exports
index = b8c42c0df..7b84a1452 100644
--- a/extra/exports
+++ b/extra/exports
@@ -62,8 +62,12 @@ = PMurHash32_Result
crc32_calc
mp_encode_double
mp_encode_float
+mp_encode_decimal
mp_decode_double
mp_decode_float
+mp_decode_extl
+mp_sizeof_decimal
+decimal_unpack

log_type
say_set_log_level
diff --git a/src/lib/core/decimal.c = b/src/lib/core/decimal.c
index 1423ae418..2c69a773c = 100644
--- a/src/lib/core/decimal.c
+++ = b/src/lib/core/decimal.c
@@ -33,6 +33,7 @@
#include "third_party/decNumber/decContext.h"
#include "third_party/decNumber/decPacked.h"
#include "lib/core/tt_static.h"
+#include = "lib/msgpuck/msgpuck.h"
#include <stddef.h>
#include <stdlib.h>
#include = <float.h> /* DBL_DIG */
@@ -312,12 +313,15 @@ char = *
decimal_pack(char *data, const decimal_t *dec)
{
uint32_t len =3D = decimal_len(dec);
- *data++ =3D = decimal_scale(dec);
+ /* reserve space for resulting = scale */
+ char *svp =3D data++;
= len--;
int32_t scale;
char *tmp = =3D (char *)decPackedFromNumber((uint8_t *)data, len, &scale, = dec);
assert(tmp =3D=3D data);
- = assert(scale =3D=3D (int32_t)decimal_scale(dec));
+ = /* scale may be negative, when exponent is > 0 */
+ = assert(scale =3D=3D (int32_t)decimal_scale(dec) || scale < = 0);
+ mp_store_u8(svp, (int8_t)scale);
= (void)tmp;
data +=3D len;
return = data;
@@ -326,7 +330,7 @@ decimal_pack(char *data, const = decimal_t *dec)
decimal_t *
decimal_unpack(const char **data, uint32_t len, decimal_t = *dec)
{
- int32_t scale =3D = *((*data)++);
+ int32_t scale =3D = (int8_t)mp_load_u8(data);
len--;
decimal_t = *res =3D decPackedToNumber((uint8_t *)*data, len, &scale, dec);
= if (res)
diff --git a/src/lib/core/mp_user_types.h = b/src/lib/core/mp_user_types.h
index 9158b40d3..8211e3e79 = 100644
--- a/src/lib/core/mp_user_types.h
+++ = b/src/lib/core/mp_user_types.h
@@ -32,7 +32,8 @@
 */

enum mp_user_type {
-    MP_DECIMAL =3D 0
+ =    MP_UNKNOWN =3D 0,
+ =    MP_DECIMAL =3D 1
};

#endif
diff --git a/src/lib/core/mpstream.c = b/src/lib/core/mpstream.c
index 8b7276ab1..a46b7962b = 100644
--- a/src/lib/core/mpstream.c
+++ = b/src/lib/core/mpstream.c
@@ -33,6 +33,7 @@
#include <assert.h>
#include = <stdint.h>
#include "msgpuck.h"
+#include "mp_decimal.h"

void
mpstream_reserve_slow(struct mpstream *stream, size_t = size)
@@ -186,6 +187,16 @@ mpstream_encode_binl(struct = mpstream *stream, uint32_t len)
= mpstream_advance(stream, pos - data);
}

+void
+mpstream_encode_decimal(struct mpstream *stream, decimal_t = *val)
+{
+ char *data =3D = mpstream_reserve(stream, mp_sizeof_decimal(val));
+ if (data = =3D=3D NULL)
+ return;
+ char *pos = =3D mp_encode_decimal(data, val);
+ = mpstream_advance(stream, pos - data);
+}
+
void
mpstream_memcpy(struct = mpstream *stream, const void *src, uint32_t n)
{
diff --git a/src/lib/core/mpstream.h = b/src/lib/core/mpstream.h
index 69fa76f7f..44af28cb5 = 100644
--- a/src/lib/core/mpstream.h
+++ = b/src/lib/core/mpstream.h
@@ -32,6 +32,7 @@
 */

#include "diag.h"
+#include "decimal.h"

#if = defined(__cplusplus)
extern "C" {
@@ -136,6 = +137,9 @@ mpstream_encode_bool(struct mpstream *stream, bool val);
void
mpstream_encode_binl(struct mpstream = *stream, uint32_t len);

+void
+mpstream_encode_decimal(struct mpstream *stream, decimal_t = *val);
+
/** Copies n bytes from memory area = src to stream. */
void
mpstream_memcpy(struct = mpstream *stream, const void *src, uint32_t n);
diff --git = a/src/lua/decimal.c b/src/lua/decimal.c
index = e548cdb9d..ab8d85f75 100644
--- a/src/lua/decimal.c
+++ b/src/lua/decimal.c
@@ -31,7 +31,7 @@

#include "lua/decimal.h"
#include = "lib/core/decimal.h"
-#include "lua/utils.h"
+#include "lua/utils.h" /* CTID_DECIMAL, ... */

#include <lua.h>
#include = <lauxlib.h>
@@ -69,16 +69,18 @@ = ldecimal_##name(struct lua_State *L) { \
static int \
ldecimal_##name(struct lua_State *L) { \
= assert(lua_gettop(L) =3D=3D 2); \
+ if = (lua_isnil(L, 1) || lua_isnil(L, 2)) { \
+ = lua_pushboolean(L, false); \
+ return = 1; = = = = = = = \
+ } \
decimal_t = *lhs =3D lua_todecimal(L, 1); \
decimal_t = *rhs =3D lua_todecimal(L, 2); \
= lua_pushboolean(L, decimal_compare(lhs, rhs) cmp 0); \
= return 1; \
}

-static = uint32_t CTID_DECIMAL;
-
/** Push a new = decimal on the stack and return a pointer to it. */
-static = decimal_t *
+decimal_t *
lua_pushdecimal(struct lua_State *L)
{
= decimal_t *res =3D luaL_pushcdata(L, CTID_DECIMAL);
diff --git a/src/lua/decimal.h b/src/lua/decimal.h
index 0485d11ef..b5c0e54b4 100644
--- = a/src/lua/decimal.h
+++ b/src/lua/decimal.h
@@= -31,12 +31,17 @@
#ifndef = TARANTOOL_LUA_DECIMAL_H_INCLUDED
#define = TARANTOOL_LUA_DECIMAL_H_INCLUDED

+#include = "lib/core/decimal.h"
+
#if = defined(__cplusplus)
extern "C" {
#endif /* = defined(__cplusplus) */

struct = lua_State;

+decimal_t *
+lua_pushdecimal(struct lua_State *L);
+
void
tarantool_lua_decimal_init(struct = lua_State *L);

diff --git = a/src/lua/msgpack.c b/src/lua/msgpack.c
index = 2126988eb..3cb7d7dcd 100644
--- a/src/lua/msgpack.c
+++ b/src/lua/msgpack.c
@@ -41,6 +41,10 @@
#include <small/region.h>
#include = <small/ibuf.h>

+#include = "lua/decimal.h"
+#include "lib/core/decimal.h"
+#include "lib/core/mp_user_types.h"
+
#include <fiber.h>

void
@@ -175,16 +179,23 @@ restart: /* used by MP_EXT */
= = assert(lua_gettop(L) =3D=3D top);
return = MP_ARRAY;
case MP_EXT:
- /* Run = trigger if type can't be encoded */
- type =3D = luamp_encode_extension(L, top, stream);
- if (type = !=3D MP_EXT)
- return type; /* Value has been = packed by the trigger */
- /* Try to convert value to = serializable type */
- luaL_convertfield(L, cfg, top, = field);
- /* handled by luaL_convertfield = */
- assert(field->type !=3D MP_EXT);
- = assert(lua_gettop(L) =3D=3D top);
- goto = restart;
+ switch (field->ext_type)
+ = = {
+ case MP_DECIMAL:
+ = = = mpstream_encode_decimal(stream, field->decval);
+ = = = return MP_EXT;
+ case MP_UNKNOWN:
+ = = = /* Run trigger if type can't be encoded */
+ type =3D = luamp_encode_extension(L, top, stream);
+ if (type = !=3D MP_EXT)
+ return type; /* Value has been = packed by the trigger */
+ /* Try to convert value to = serializable type */
+ luaL_convertfield(L, cfg, top, = field);
+ /* handled by luaL_convertfield = */
+ assert(field->type !=3D MP_EXT || field->ext_type = !=3D MP_UNKNOWN);
+ assert(lua_gettop(L) =3D=3D = top);
+ goto restart;
+ }
}
= return MP_EXT;
}
@@ -283,9 +294,28 @@ = luamp_decode(struct lua_State *L, struct luaL_serializer *cfg,
= = return;
}
case = MP_EXT:
- luamp_decode_extension(L, = data);
+ {
+ uint32_t len;
+ int8_t = type;
+ len =3D mp_decode_extl(data, &type);
+ = = switch (type) {
+ case MP_DECIMAL:
+ = = {
+ decimal_t *dec =3D = lua_pushdecimal(L);
+ dec =3D decimal_unpack(data, len, = dec);
+ if (dec =3D=3D NULL) {
+ = lua_pop(L, -1);
+ luaL_error(L, "msgpack.decode: = "
+       "invalid = MsgPack.");
+ }
+ = return;
+ }
+ = default:
+ luamp_decode_extension(L, = data);
+ }
break;
}
+ = }
}


diff = --git a/src/lua/msgpackffi.lua b/src/lua/msgpackffi.lua
index bfeedbc4b..4c799eed0 100644
--- = a/src/lua/msgpackffi.lua
+++ b/src/lua/msgpackffi.lua
@@ -17,10 +17,18 @@ char *
mp_encode_float(char = *data, float num);
char *
mp_encode_double(char *data, double num);
+char = *
+mp_encode_decimal(char *data, decimal_t *dec);
+uint32_t
+mp_sizeof_decimal(const decimal_t = *dec);
float
mp_decode_float(const char = **data);
double
mp_decode_double(const char = **data);
+uint32_t
+mp_decode_extl(const = char **data, int8_t *type);
+decimal_t *
+decimal_unpack(const char **data, uint32_t len, decimal_t = *dec);
]])

local = strict_alignment =3D (jit.arch =3D=3D 'arm')
@@ -117,6 = +125,11 @@ local function encode_double(buf, num)
    builtin.mp_encode_double(p, num)
end

+local function = encode_decimal(buf, num)
+    local p =3D = buf:alloc(builtin.mp_sizeof_decimal(num))
+ =    builtin.mp_encode_decimal(p, num)
+end
+
local function encode_int(buf, num)
    if num >=3D 0 then
        if num <=3D = 0x7f then
@@ -294,6 +307,7 @@ on_encode(ffi.typeof('const = unsigned char'), encode_int)
on_encode(ffi.typeof('bool'), = encode_bool_cdata)
on_encode(ffi.typeof('float'), = encode_float)
on_encode(ffi.typeof('double'), = encode_double)
+on_encode(ffi.typeof('decimal_t'), = encode_decimal)

---------------------------------------------------------------= -----------------
-- Decoder
@@ -473,6 = +487,23 @@ local function decode_map(data, size)
    return setmetatable(map, = msgpack.map_mt)
end

+local = function decode_ext(data)
+    local t =3D = ffi.new("int8_t[1]")
+    -- mp_decode_extl = and mp_decode_decimal
+    -- need type = code
+    data[0] =3D data[0] - 1
+    local old_data =3D data[0]
+ =    local len =3D builtin.mp_decode_extl(data, t)
+    --MP_DECIMAL
+ =    if t[0] =3D=3D 1 then
+ =        local num =3D = ffi.new("decimal_t")
+ =        builtin.decimal_unpack(data, = len, num)
+ =        return num
+ =    else
+ =        error("Unsupported extension = type")
+    end
+end
+
local decoder_hint =3D {
    --[[{{{ MP_BIN]]
    [0xc4] =3D function(data) return = decode_str(data, decode_u8(data)) end;
@@ -528,6 +559,8 @@ = decode_r =3D function(data)
        return = false
    elseif c =3D=3D 0xc3 then
        return = true
+    elseif c >=3D 0xd4 and c <=3D= 0xd8 or c >=3D 0xc7 and c <=3D 0xc9 then
+ =        return decode_ext(data)
    else
        local fun =3D = decoder_hint[c];
        assert = (type(fun) =3D=3D "function")
diff --git a/src/lua/utils.c = b/src/lua/utils.c
index 0a4bcf517..47cf030ab 100644
--- a/src/lua/utils.c
+++ b/src/lua/utils.c
@@ -45,6 +45,8 @@ static uint32_t CTID_STRUCT_IBUF;
static uint32_t CTID_STRUCT_IBUF_PTR;
uint32_t = CTID_CHAR_PTR;
uint32_t CTID_CONST_CHAR_PTR;
+uint32_t CTID_DECIMAL;
+

void *
luaL_pushcdata(struct lua_State *L, = uint32_t ctypeid)
@@ -723,6 +725,12 @@ luaL_tofield(struct = lua_State *L, struct luaL_serializer *cfg, int index,
/* Fall = through */
default:
= field->type =3D MP_EXT;
+ if = (cd->ctypeid =3D=3D CTID_DECIMAL) {
+ = field->ext_type =3D MP_DECIMAL;
+ = field->decval =3D (decimal_t *) cdata;
+ } else = {
+ field->ext_type =3D MP_UNKNOWN;
+ }
= = }
return 0;
}
@@ -754,6 +762,7 @@ luaL_tofield(struct lua_State *L, struct = luaL_serializer *cfg, int index,
/* Fall = through */
default:
= field->type =3D MP_EXT;
+ = field->ext_type =3D MP_UNKNOWN;
}
#undef CHECK_NUMBER
return = 0;
@@ -765,7 +774,7 @@ luaL_convertfield(struct lua_State = *L, struct luaL_serializer *cfg, int idx,
{
= if (idx < 0)
idx =3D lua_gettop(L) + idx + = 1;
- assert(field->type =3D=3D MP_EXT); /* must be called = after tofield() */
+ assert(field->type =3D=3D = MP_EXT && field->ext_type =3D=3D MP_UNKNOWN); /* must be = called after tofield() */

if = (cfg->encode_load_metatables) {
int type = =3D lua_type(L, idx);
@@ -782,10 +791,11 @@ = luaL_convertfield(struct lua_State *L, struct luaL_serializer *cfg, int = idx,
}
}

- = if (field->type =3D=3D MP_EXT && = cfg->encode_use_tostring)
+ if (field->type =3D=3D MP_EXT = && field->ext_type =3D=3D MP_UNKNOWN &&
+ =     cfg->encode_use_tostring)
= = lua_field_tostring(L, cfg, idx, field);

- = if (field->type !=3D MP_EXT)
+ if = (field->type !=3D MP_EXT || field->ext_type !=3D MP_UNKNOWN)
= = return;

if = (cfg->encode_invalid_as_nil) {
diff --git = a/src/lua/utils.h b/src/lua/utils.h
index = 7e7cdc0c6..3ca43292b 100644
--- a/src/lua/utils.h
+++ b/src/lua/utils.h
@@ -54,6 +54,8 @@ extern = "C" {
#include <lj_meta.h>

#include "lua/error.h"
+#include = "lib/core/mp_user_types.h"
+#include "lib/core/decimal.h" = /* decimal_t */

struct lua_State;
struct ibuf;
@@ -69,6 +71,8 @@ extern struct = ibuf *tarantool_lua_ibuf;

extern uint32_t = CTID_CONST_CHAR_PTR;
extern uint32_t CTID_CHAR_PTR;
+extern uint32_t CTID_DECIMAL;
+

/** \cond public */

@@ -286,8 +290,10 @@ struct luaL_field {
bool = bval;
/* Array or map. */
uint32_t = size;
+ decimal_t *decval;
};
= enum mp_type type;
+ enum mp_user_type ext_type;
= bool compact; =             &n= bsp;  /* a flag used by YAML serializer */
};

@@ -373,7 +379,7 @@ luaL_checkfield(struct = lua_State *L, struct luaL_serializer *cfg, int idx,
{
= if (luaL_tofield(L, cfg, idx, field) < 0)
= luaT_error(L);
- if (field->type !=3D = MP_EXT)
+ if (field->type !=3D MP_EXT || = field->ext_type !=3D MP_UNKNOWN)
= return;
luaL_convertfield(L, cfg, idx, = field);
}
diff --git = a/test/app/decimal.result b/test/app/decimal.result
index = 53ec73edc..334c9fbab 100644
--- = a/test/app/decimal.result
+++ b/test/app/decimal.result
@@ -12,77 +12,77 @@ ffi =3D require('ffi')
-- = check various constructors
decimal.new('1234.5678')
 | ---
- | - '1234.5678'
+ | = - 1234.5678
 | ...
decimal.new('1e6')
 | ---
- | - '1000000'
+ | - = 1000000
 | ...
decimal.new('-6.234612e2')
 | ---
- | - '-623.4612'
+ | - -623.4612
 | ...
-- check (u)int16/32/64_t
decimal.new(2ULL ^ 63)
 | ---
- | - '9223372036854775808'
+ | - = 9223372036854775808
 | ...
decimal.new(123456789123456789ULL)
 | = ---
- | - '123456789123456789'
+ | - = 123456789123456789
 | ...
decimal.new(-123456789123456789LL)
 | = ---
- | - '-123456789123456789'
+ | - = -123456789123456789
 | ...
decimal.new(ffi.new('uint8_t', 231))
 | = ---
- | - '231'
+ | - 231
 | ...
decimal.new(ffi.new('int8_t', = -113))
 | ---
- | - '-113'
+ | - -113
 | ...
decimal.new(ffi.new('uint16_t', 65535))
 | = ---
- | - '65535'
+ | - 65535
 | ...
decimal.new(ffi.new('int16_t', = -31263))
 | ---
- | - '-31263'
+ | - -31263
 | ...
decimal.new(ffi.new('uint32_t', 4123123123))
 | ---
- | - '4123123123'
+ = | - 4123123123
 | ...
decimal.new(ffi.new('int32_t', -2123123123))
 | ---
- | - '-2123123123'
+ = | - -2123123123
 | ...
decimal.new(ffi.new('float', 128.5))
 | = ---
- | - '128.5'
+ | - 128.5
 | ...
decimal.new(ffi.new('double', = 128.5))
 | ---
- | - '128.5'
+ | - 128.5
 | ...

decimal.new(1)
 | ---
- | - = '1'
+ | - 1
 | ...
decimal.new(-1)
 | ---
- | - = '-1'
+ | - -1
 | ...
decimal.new(2^64)
 | ---
- | = - '18446744073709600000'
+ | - 18446744073709600000
 | ...
decimal.new(2^(-20))
 | ---
- | - '0.00000095367431640625'
+ | - 0.00000095367431640625
 | ...

-- incorrect constructions
@@ = -128,38 +128,38 @@ a =3D decimal.new('10')
 | ...
a
 | ---
- | - '10'
+ | - 10
 | ...
b =3D = decimal.new('0.1')
 | ---
 | = ...
b
 | ---
- | - = '0.1'
+ | - 0.1
 | ...
a = + b
 | ---
- | - '10.1'
+ = | - 10.1
 | ...
a - b
 | ---
- | - '9.9'
+ | - = 9.9
 | ...
a * b
 | = ---
- | - '1.0'
+ | - 1.0
 | ...
a / b
 | ---
- | - '100'
+ | - 100
 | = ...
a ^ b
 | ---
- | - = '1.2589254117941672104239541063958006061'
+ | - = 1.2589254117941672104239541063958006061
 | ...
b ^ a
 | ---
- | - = '0.0000000001'
+ | - 0.0000000001
 | = ...
-a + -b =3D=3D -(a + b)
 | ---
@@ -167,11 +167,11 @@ b ^ a
 | ...
a
 | ---
- | - '10'
+ | - 10
 | ...
b
 | ---
- | - '0.1'
+ | - = 0.1
 | ...

a < b
@@ -216,28 +216,28 @@ a ~=3D b
 | ...
a
 | ---
- | - '10'
+ | - 10
 | ...
b
 | ---
- | - '0.1'
+ | - = 0.1
 | ...

decimal.sqrt(a)
 | ---
- | - = '3.1622776601683793319988935444327185337'
+ | - = 3.1622776601683793319988935444327185337
 | ...
decimal.ln(a)
 | ---
- | - = '2.3025850929940456840179914546843642076'
+ | - = 2.3025850929940456840179914546843642076
 | ...
decimal.log10(a)
 | ---
- | = - '1'
+ | - 1
 | ...
decimal.exp(a)
 | ---
- | - = '22026.465794806716516957900645284244366'
+ | - = 22026.465794806716516957900645284244366
 | ...
a =3D=3D decimal.ln(decimal.exp(a))
 | = ---
@@ -261,7 +261,7 @@ a + -a =3D=3D 0
 | ...
a
 | ---
- | - '10'
+ | - 10
 | = ...

a =3D = decimal.new('1.1234567891234567891234567891234567891')
@@ = -269,7 +269,7 @@ a =3D = decimal.new('1.1234567891234567891234567891234567891')
 | ...
a
 | ---
- | - '1.1234567891234567891234567891234567891'
+= | - 1.1234567891234567891234567891234567891
 | = ...
decimal.precision(a)
 | ---
@@ -285,7 +285,7 @@ decimal.round(a, 37) =3D=3D a
 | ...
a
 | ---
- | - '1.1234567891234567891234567891234567891'
+= | - 1.1234567891234567891234567891234567891
 | = ...
a =3D decimal.round(a, 36)
 | = ---
@@ -309,19 +309,19 @@ decimal.round(a, -5) =3D=3D a
 | ...
decimal.round(a, 7)
 | ---
- | - '1.1234568'
+ | = - 1.1234568
 | ...
decimal.round(a, = 3)
 | ---
- | - '1.123'
+ = | - 1.123
 | ...
decimal.round(a, 0)
 | ---
- | - '1'
+ | - 1
 | ...
a
 | ---
- | - '1.123456789123456789123456789123456789'
+ = | - 1.123456789123456789123456789123456789
 | ...

decimal.ln(0)
@@ -334,7 +334,7 = @@ decimal.ln(-1)
 | ...
decimal.ln(1) | ---
- | - '0'
+ | - 0
 | ...
decimal.log10(0)
 | ---
@@ -346,7 +346,7 @@ = decimal.log10(-1)
 | ...
decimal.log10(1)
 | ---
- | = - '0'
+ | - 0
 | ...
decimal.exp(88)
 | ---
@@ = -354,7 +354,7 @@ decimal.exp(88)
 | ...
decimal.exp(87)
 | ---
- | - = '60760302250568721495223289381302760753'
+ | - = 60760302250568721495223289381302760753
 | ...
decimal.sqrt(-5)
 | ---
@@ = -362,7 +362,7 @@ decimal.sqrt(-5)
 | ...
decimal.sqrt(5)
 | ---
- | - = '2.2360679774997896964091736687312762354'
+ | - = 2.2360679774997896964091736687312762354
 | ...

-- various incorrect operands
@@ = -408,11 +408,11 @@ a ^ 2
 | ...
a ^ = 1.9
 | ---
- | - = '1258925411794167210423954106395800606.1'
+ | - = 1258925411794167210423954106395800606.1
 | ...
a * '1e18'
 | ---
- | - = '10000000000000000000000000000000000000'
+ | - = 10000000000000000000000000000000000000
 | ...
a =3D decimal.new(string.rep('9', 38))
 | = ---
@@ -435,7 +435,7 @@ a + '0.5'
 | = ...
a + '0.4'
 | ---
- | = - '99999999999999999999999999999999999999'
+ | - = 99999999999999999999999999999999999999
 | ...
a / 0.5
 | ---
@@ -451,7 = +451,7 @@ a =3D decimal.new('-13')
 | ...
a ^ 2
 | ---
- | - '169'
+ | - 169
 | ...
-- = fractional powers are allowed only for positive numbers
a = ^ 2.5
@@ -462,17 +462,17 @@ a ^ 2.5
-- check = correct rounding when scale =3D 0
decimal.round(decimal.new(0.9), 0)
 | = ---
- | - '1'
+ | - 1
 | = ...
decimal.round(decimal.new(9.9), 0)
 |= ---
- | - '10'
+ | - 10
 |= ...
decimal.round(decimal.new(99.9), 0)
 | ---
- | - '100'
+ | - = 100
 | ...
decimal.round(decimal.new(99.4), 0)
 | = ---
- | - '99'
+ | - 99
 | = ...
diff --git a/test/app/msgpack.result = b/test/app/msgpack.result
index a67c05d38..4b5aec784 = 100644
--- a/test/app/msgpack.result
+++ = b/test/app/msgpack.result
@@ -252,3 +252,44 @@ = msgpack.decode(ffi.cast('char *', '\x04\x05\x06'), -1)
---
- error: 'msgpack.decode: size can''t be = negative'
...
+--
+-- gh-4333: = msgpack encode/decode decimals.
+--
+decimal = =3D require('decimal')
+---
+...
+a =3D decimal.new('1e37')
+---
+...
+b =3D decimal.new('1e-38')
+---
+...
+c =3D = decimal.new('1')
+---
+...
+d = =3D decimal.new('0.1234567')
+---
+...
+e =3D decimal.new('123.4567')
+---
+...
+msgpack.decode(msgpack.encode(a)) =3D=3D = a
+---
+- true
+...
+msgpack.decode(msgpack.encode(b)) =3D=3D b
+---
+- true
+...
+msgpack.decode(msgpack.encode(c)) =3D=3D c
+---
+- true
+...
+msgpack.decode(msgpack.encode(d)) =3D=3D d
+---
+- true
+...
+msgpack.decode(msgpack.encode(e)) =3D=3D e
+---
+- true
+...
diff --git a/test/app/msgpack.test.lua = b/test/app/msgpack.test.lua
index e0880ac22..9224d870a = 100644
--- a/test/app/msgpack.test.lua
+++ = b/test/app/msgpack.test.lua
@@ -84,3 +84,18 @@ size =3D = msgpack.encode(100, buf)
-- is not negative.
--
msgpack.decode(ffi.cast('char *', = '\x04\x05\x06'), -1)
+
+--
+-- = gh-4333: msgpack encode/decode decimals.
+--
+decimal =3D require('decimal')
+a =3D = decimal.new('1e37')
+b =3D decimal.new('1e-38')
+c =3D decimal.new('1')
+d =3D = decimal.new('0.1234567')
+e =3D decimal.new('123.4567')
+msgpack.decode(msgpack.encode(a)) =3D=3D a
+msgpack.decode(msgpack.encode(b)) =3D=3D b
+msgpack.decode(msgpack.encode(c)) =3D=3D c
+msgpack.decode(msgpack.encode(d)) =3D=3D d
+msgpack.decode(msgpack.encode(e)) =3D=3D e
diff = --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
index = 46c98bde1..8ff2867e3 100644
--- a/third_party/lua-yaml/lyaml.cc
+++ = b/third_party/lua-yaml/lyaml.cc
@@ -49,6 +49,7 @@ extern "C" {
#include "b64.h"
} /* extern "C" */
#include "lua/utils.h"
+#include = "lib/core/decimal.h"

#define = LUAYAML_TAG_PREFIX "tag:yaml.org,2002:"

@@ -693,6 = +694,11 @@ static int dump_node(struct lua_yaml_dumper *dumper)
      len =3D 4;
      break;
   case MP_EXT:
+ =      if (field.ext_type =3D=3D MP_DECIMAL) {
+         str =3D = decimal_to_string(field.decval);
+  len = =3D strlen(str);
+ =         break;
+ =      }
      assert(0); /* checked by = luaL_checkfield() */
      break;
    }
-- 
2.20.1 (Apple Git-117)




=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
=
Here=E2=80=99s the old version of the patch = adding decimal indices

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
Ind= ices can now be built over decimal fields.
A new field = type - 'decimal' is introduced.
Decimal values may be = stored in 'decimal' columns, as well as in
'scalar' and = 'any' columns.

Closes #4333
@TarantoolBot document
Title: Document decimal = field type.

Decimals may now be stored in = spaces. A corresponding field type is
introduced: = 'decimal'. Decimal values are also allowed in 'scalar' and
'number' fields.

'decimal' field = type is appropriate for both memtx HASH and TREE
indices, = as well as for vinyl TREE index.
---
src/box/field_def.c =          |  43 = +++++--
src/box/field_def.h =          |  16 ++-
src/box/key_def.h =            | =   2 +-
src/box/tuple_compare.cc     | 160 = ++++++++++++++++++++++++-
src/box/tuple_format.c =       |   2 +-
src/lib/core/decimal.h       | =   8 ++
src/lib/core/mp_decimal.h =    |   8 ++
test/engine/decimal.result   | 226 = +++++++++++++++++++++++++++++++++++
test/engine/decimal.test.lua |  65 ++++++++++
9 files changed, 510 insertions(+), 20 deletions(-)
create mode 100644 test/engine/decimal.result
create mode 100644 test/engine/decimal.test.lua

diff --git a/src/box/field_def.c = b/src/box/field_def.c
index 346042b98..da06e6bde 100644
--- a/src/box/field_def.c
+++ = b/src/box/field_def.c
@@ -52,17 +52,32 @@ const uint32_t = field_mp_type[] =3D {
/* [FIELD_TYPE_UNSIGNED] =3D =  */ 1U << MP_UINT,
/* [FIELD_TYPE_STRING] =   =3D  */ 1U << MP_STR,
/* = [FIELD_TYPE_NUMBER]   =3D  */ (1U << MP_UINT) | (1U = << MP_INT) |
- (1U << MP_FLOAT) | (1U = << MP_DOUBLE),
+ (1U << MP_FLOAT) | (1U = << MP_DOUBLE) | (1U << MP_EXT),
/* = [FIELD_TYPE_INTEGER]  =3D  */ (1U << MP_UINT) | (1U = << MP_INT),
/* [FIELD_TYPE_BOOLEAN]  =3D =  */ 1U << MP_BOOL,
/* [FIELD_TYPE_VARBINARY] =3D =  */ 1U << MP_BIN,
/* [FIELD_TYPE_SCALAR] =   =3D  */ (1U << MP_UINT) | (1U << MP_INT) = |
(1U << MP_FLOAT) | (1U << MP_DOUBLE) | (1U = << MP_STR) |
- (1U << MP_BIN) | (1U = << MP_BOOL),
+ (1U << MP_BIN) | (1U = << MP_BOOL) | (1U << MP_EXT),
+ /* = [FIELD_TYPE_DECIMAL]  =3D  */ 1U << MP_EXT,
= /* [FIELD_TYPE_ARRAY]    =3D  */ 1U << = MP_ARRAY,
/* [FIELD_TYPE_MAP] =      =3D  */ (1U << MP_MAP),
};

+const uint32_t = field_ext_type[] =3D {
+ /* [FIELD_TYPE_ANY] =      =3D  */ UINT32_MAX & ~(1U = << MP_UNKNOWN),
+ /* [FIELD_TYPE_UNSIGNED] =3D =  */ 0,
+ /* [FIELD_TYPE_STRING] =   =3D  */ 0,
+ /* [FIELD_TYPE_NUMBER] =   =3D  */ 1U << MP_DECIMAL,
+ /* = [FIELD_TYPE_INTEGER]  =3D  */ 0,
+ /* = [FIELD_TYPE_BOOLEAN]  =3D  */ 0,
+ /* = [FIELD_TYPE_VARBINARY] =3D */ 0,
+ /* = [FIELD_TYPE_SCALAR]   =3D  */ 1U << MP_DECIMAL,
+ = /* [FIELD_TYPE_DECIMAL]  =3D  */ 1U << = MP_DECIMAL,
+ /* [FIELD_TYPE_ARRAY] =    =3D  */ 0,
+ /* = [FIELD_TYPE_MAP]      =3D  */ 0,
+};
+
const char = *field_type_strs[] =3D {
/* [FIELD_TYPE_ANY] =      =3D */ "any",
/* = [FIELD_TYPE_UNSIGNED] =3D */ "unsigned",
@@ -72,6 +87,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 +114,18 @@ = field_type_by_name_wrapper(const char *str, uint32_t len)
 * values can be stored in the j type.
 */
static const bool = field_type_compatibility[] =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,
};

bool
diff --git a/src/box/field_def.h = b/src/box/field_def.h
index c1a7ec0a9..10dc9fc13 100644
--- a/src/box/field_def.h
+++ = b/src/box/field_def.h
@@ -37,6 +37,7 @@
#include <limits.h>
#include = <msgpuck.h>
#include "opt_def.h"
+#include "lib/core/mp_user_types.h"

#if defined(__cplusplus)
extern "C" {
@@ -58,6 +59,7 @@ enum field_type {
= FIELD_TYPE_BOOLEAN,
FIELD_TYPE_VARBINARY,
= FIELD_TYPE_SCALAR,
+ FIELD_TYPE_DECIMAL,
= FIELD_TYPE_ARRAY,
FIELD_TYPE_MAP,
= field_type_MAX
@@ -109,8 +111,9 @@ = field_type_by_name(const char *name, size_t len);
/* = MsgPack type names */
extern const char = *mp_type_strs[];

-/** A helper table for = field_mp_type_is_compatible */
+/** Two helper tables for = field_mp_type_is_compatible */
extern const uint32_t = field_mp_type[];
+extern const uint32_t = field_ext_type[];

extern const struct = opt_def field_def_reg[];
extern const struct field_def = field_def_default;
@@ -142,15 +145,20 @@ struct field_def = {
struct Expr *default_value_expr;
};

-/** Checks if mp_type (MsgPack) is compatible = with field type. */
+/** Checks if mp_typeof(data) is = compatible with field type. */
static inline bool
-field_mp_type_is_compatible(enum field_type type, enum = mp_type mp_type,
+field_mp_type_is_compatible(enum = field_type type, const char *data,
=     bool is_nullable)
{
+ = enum mp_type mp_type =3D mp_typeof(*data);
+ int8_t = ext_type =3D MP_UNKNOWN;
assert(type < = field_type_MAX);
assert((size_t)mp_type < = CHAR_BIT * sizeof(*field_mp_type));
uint32_t = mask =3D field_mp_type[type] | (is_nullable * (1U << MP_NIL));
- = return (mask & (1U << mp_type)) !=3D 0;
+ = if (mp_type !=3D MP_EXT)
+ return = (mask & (1U << mp_type)) !=3D 0;
+ = mp_decode_extl(&data, &ext_type);
+ return = (field_ext_type[type] & (1U << ext_type)) !=3D 0;
}

static inline bool
diff --git a/src/box/key_def.h b/src/box/key_def.h
index f4a1a8fd1..ed3354ade 100644
--- = a/src/box/key_def.h
+++ b/src/box/key_def.h
@@= -505,7 +505,7 @@ static inline int
key_part_validate(enum = field_type key_type, const char *key,
=   uint32_t field_no, bool is_nullable)
{- = if (unlikely(!field_mp_type_is_compatible(key_type, = mp_typeof(*key),
+ if = (unlikely(!field_mp_type_is_compatible(key_type, key,
=   is_nullable))) {
= diag_set(ClientError, ER_KEY_PART_TYPE, field_no,
= = =  field_type_strs[key_type]);
diff --git = a/src/box/tuple_compare.cc b/src/box/tuple_compare.cc
index 95a0f58c9..6ab28f662 100644
--- = a/src/box/tuple_compare.cc
+++ b/src/box/tuple_compare.cc
@@ -33,6 +33,9 @@
#include "coll/coll.h"
#include "trivia/util.h" /* NOINLINE */
#include = <math.h>
+#include "lib/core/decimal.h"
+#include "lib/core/mp_decimal.h"
+#include = "lib/core/mp_user_types.h"

/* {{{ = tuple_compare */

@@ -87,7 +90,7 @@ static = enum mp_class mp_classes[] =3D {
/* = .MP_BOOL    =3D */ MP_CLASS_BOOL,
/* = .MP_FLOAT   =3D */ MP_CLASS_NUMBER,
/* = .MP_DOUBLE  =3D */ MP_CLASS_NUMBER,
- /* = .MP_BIN     =3D */ MP_CLASS_BIN
+ /* = .MP_EXT     =3D */ MP_CLASS_NUMBER /* requires = additional parsing */
};

#define COMPARE_RESULT(a, b) (a < b ? -1 : a > b)
@@ -264,6 +267,50 @@ mp_compare_double_any_number(double lhs, = const char *rhs,
return k * COMPARE_RESULT(lqbit, = rqbit);
}

+static int
+mp_compare_decimal_any_number(decimal_t *lhs, const char = *rhs,
+       enum mp_type = rhs_type, int k)
+{
+ decimal_t = rhs_dec;
+ switch (rhs_type) {
+ = case MP_FLOAT:
+ {
+ double d = =3D mp_decode_float(&rhs);
+ = decimal_from_double(&rhs_dec, d);
+ break;
+ = }
+ case MP_DOUBLE:
+ = {
+ double d =3D = mp_decode_double(&rhs);
+ decimal_from_double(&rhs_dec, = d);
+ break;
+ }
+ case = MP_INT:
+ {
+ int64_t = num =3D mp_decode_int(&rhs);
+ = decimal_from_int64(&rhs_dec, num);
+ break;
+ = }
+ case MP_UINT:
+ {
+ = = uint64_t num =3D mp_decode_uint(&rhs);
+ = decimal_from_uint64(&rhs_dec, num);
+ break;
+ = }
+ case MP_EXT:
+ {
+ = = int8_t ext_type;
+ uint32_t len =3D = mp_decode_extl(&rhs, &ext_type);
+ = assert(ext_type =3D=3D MP_DECIMAL);
+ = decimal_unpack(&rhs, len, &rhs_dec);
+ break;
+ = }
+ default:
+ = unreachable();
+ }
+ return k = * decimal_compare(lhs, &rhs_dec);
+}
+
static int
mp_compare_number_with_type(const = char *lhs, enum mp_type lhs_type,
=     const char *rhs, enum mp_type = rhs_type)
@@ -271,6 +318,21 @@ = mp_compare_number_with_type(const char *lhs, enum mp_type lhs_type,
= assert(mp_classof(lhs_type) =3D=3D MP_CLASS_NUMBER);
= assert(mp_classof(rhs_type) =3D=3D MP_CLASS_NUMBER);

+ /*
+  * = test decimals first, so that we don't have to
+  * = account for them in other comarators.
+ =  */
+ decimal_t dec;
+ if = (rhs_type =3D=3D MP_EXT) {
+ return = mp_compare_decimal_any_number(
+ = mp_decode_decimal(&rhs, &dec), lhs, lhs_type, -1
+ = = );
+ }
+ if = (lhs_type =3D=3D MP_EXT) {
+ return = mp_compare_decimal_any_number(
+ = mp_decode_decimal(&lhs, &dec), rhs, rhs_type, 1
+ = = );
+ }
if = (rhs_type =3D=3D MP_FLOAT) {
return = mp_compare_double_any_number(
mp_decode_float(&rhs), lhs, = lhs_type, -1
@@ -410,6 +472,8 @@ tuple_compare_field(const = char *field_a, const char *field_b,
return = coll !=3D NULL ?
=        mp_compare_scalar_coll(fi= eld_a, field_b, coll) :
=        mp_compare_scalar(field_a= , field_b);
+ case FIELD_TYPE_DECIMAL:
+ = = return mp_compare_number(field_a, field_b);
= default:
unreachable();
return = 0;
@@ -443,6 +507,8 @@ tuple_compare_field_with_type(const = char *field_a, enum mp_type a_type,
=        mp_compare_scalar_coll(fi= eld_a, field_b, coll) :
=        mp_compare_scalar_with_ty= pe(field_a, a_type,
   field_b, = b_type);
+ case FIELD_TYPE_DECIMAL:
+ = = return mp_compare_number(field_a, field_b);
= default:
unreachable();
return = 0;
@@ -1356,6 +1422,25 @@ static const = comparator_with_key_signature cmp_wk_arr[] =3D {
#define = HINT_VALUE_DOUBLE_MAX (exp2(HINT_VALUE_BITS - 1) - 1)
#define = HINT_VALUE_DOUBLE_MIN (-exp2(HINT_VALUE_BITS - 1))

+/**
+ * Max and min decimal numbers whose = integral parts fit
+ * in a hint value.
+ = */
+static const decimal_t HINT_VALUE_DECIMAL_MAX =3D {
+ = 18, = = = = /* decimal digits */
+ 0, /* exponent */
+ 0, /* no = special bits. */
+ {487, 423, 303, 752, 460, = 576} = /* 576,460,752,303,423,488 =3D 2^59 - 1 */
+ /* 59 =3D=3D= HINT_VALUE_BITS - 1 */
+};
+
+static const decimal_t HINT_VALUE_DECIMAL_MIN =3D {
+ = 18, = = = = /* decimal digits */
+ 0, /* exponent */
+ = 0x80, = = = = /* negative bit */
+ {488, 423, 303, 752, 460, = 576} = /* 576,460,752,303,423,488 =3D 2^59 */
+};
+
/*
 * HINT_CLASS_BITS = should be big enough to store any mp_class value.
 * = Note, ((1 << HINT_CLASS_BITS) - 1) is reserved for HINT_NONE.
@@ -1415,6 +1500,25 @@ hint_double(double d)
= return hint_create(MP_CLASS_NUMBER, val);
}

+static inline hint_t
+hint_decimal(decimal_t *dec)
+{
+ = uint64_t val =3D 0;
+ int64_t num;
+ if = (decimal_compare(dec, &HINT_VALUE_DECIMAL_MAX) >=3D 0)
+ = = val =3D HINT_VALUE_MAX;
+ else if = (decimal_compare(dec, &HINT_VALUE_DECIMAL_MIN) <=3D 0)
+ = = val =3D 0;
+ else {
+ dec =3D = decimal_to_int64(dec, &num);
+ /* We've = checked boundaries above. */
+ assert(dec !=3D NULL);
+ = = val =3D num - HINT_VALUE_INT_MIN;
+ }
+
+ return = hint_create(MP_CLASS_NUMBER, val);
+}
+
static inline uint64_t
hint_str_raw(const char = *s, uint32_t len)
{
@@ -1491,12 +1595,42 @@ = field_hint_number(const char *field)
return = hint_double(mp_decode_float(&field));
case = MP_DOUBLE:
return = hint_double(mp_decode_double(&field));
+ case = MP_EXT:
+ {
+ int8_t = ext_type;
+ uint32_t len =3D = mp_decode_extl(&field, &ext_type);
+ = switch(ext_type) {
+ case MP_DECIMAL:
+ = = {
+ decimal_t dec;
+ decimal_t = *res;
+ /*
+  * The effect of = mp_decode_extl() +
+  * decimal_unpack() is the = same that
+  * the one of = mp_decode_decimal().
+  */
+ res =3D = decimal_unpack(&field, len, &dec);
+ = assert(res !=3D NULL);
+ return = hint_decimal(res);
+ }
+ = default:
+ unreachable();
+ }
+ = }
default:
= unreachable();
}
return = HINT_NONE;
}

+static inline = hint_t
+field_hint_decimal(const char *field)
+{
+ assert(mp_typeof(*field) =3D=3D = MP_EXT);
+ decimal_t dec;
+ return = hint_decimal(mp_decode_decimal(&field, &dec));
+}+
static inline hint_t
field_hint_string(const char *field, struct coll *coll)
{
@@ -1536,6 +1670,25 @@ = field_hint_scalar(const char *field, struct coll *coll)
= case MP_BIN:
len =3D = mp_decode_binl(&field);
return hint_bin(field, len);
+ = case MP_EXT:
+ {
+ int8_t = ext_type;
+ uint32_t len =3D = mp_decode_extl(&field, &ext_type);
+ = switch(ext_type) {
+ case MP_DECIMAL:
+ = = {
+ decimal_t dec;
+ /*
+ = = =  * The effect of mp_decode_extl() +
+  * = decimal_unpack() is the same that
+  * = the one of mp_decode_decimal().
+ =  */
+ return = hint_decimal(decimal_unpack(&field, len, &dec));
+ = = }
+ default:
+ = unreachable();
+ }
+ }
= default:
unreachable();
}
@@ -1563,6 +1716,8 @@ field_hint(const char *field, struct = coll *coll)
return = field_hint_varbinary(field);
case FIELD_TYPE_SCALAR:
= = return field_hint_scalar(field, coll);
+ case = FIELD_TYPE_DECIMAL:
+ return = field_hint_decimal(field);
default:
= unreachable();
}
@@ -1668,6 = +1823,9 @@ key_def_set_hint_func(struct key_def *def)
case = FIELD_TYPE_SCALAR:
= key_def_set_hint_func<FIELD_TYPE_SCALAR>(def);
= = break;
+ case FIELD_TYPE_DECIMAL:
+ = = key_def_set_hint_func<FIELD_TYPE_DECIMAL>(def);
+ = = break;
default:
/* = Invalid key definition. */
def->key_hint =3D NULL;
diff --git a/src/box/tuple_format.c = b/src/box/tuple_format.c
index 02fadf1cf..5bb659b87 = 100644
--- a/src/box/tuple_format.c
+++ = b/src/box/tuple_format.c
@@ -1165,7 +1165,7 @@ = tuple_format_iterator_next(struct tuple_format_iterator *it,
=  * defined in format.
=  */
bool is_nullable =3D = tuple_field_is_nullable(field);
- if = (!field_mp_type_is_compatible(field->type, = mp_typeof(*entry->data),
+ if = (!field_mp_type_is_compatible(field->type, entry->data,
= = = = =  is_nullable) !=3D 0) {
= diag_set(ClientError, ER_FIELD_TYPE,
=  tuple_field_path(field),
diff --git = a/src/lib/core/decimal.h b/src/lib/core/decimal.h
index = a4e7683c7..6a152b08c 100644
--- = a/src/lib/core/decimal.h
+++ b/src/lib/core/decimal.h
@@ -37,6 +37,10 @@
#include = "third_party/decNumber/decNumber.h"
#include = <stdint.h>

+#if = defined(__cplusplus)
+extern "C" {
+#endif = /* defined(__cplusplus) */
+
typedef = decNumber decimal_t;

/**
@@ = -207,4 +211,8 @@ decimal_pack(char *data, const decimal_t *dec);
decimal_t *
decimal_unpack(const char **data, = uint32_t len, decimal_t *dec);

+#if = defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
#endif /* TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED */
diff --git a/src/lib/core/mp_decimal.h = b/src/lib/core/mp_decimal.h
index a991a5f16..778529068 = 100644
--- a/src/lib/core/mp_decimal.h
+++ = b/src/lib/core/mp_decimal.h
@@ -34,6 +34,10 @@
#include "decimal.h"
#include = <stdint.h>

+#if = defined(__cplusplus)
+extern "C" {
+#endif = /* defined(__cplusplus) */
+
/**
 * \brief Calculate exact buffer size needed to store a = decimal
 * pointed to by \a dec.
@@ = -59,4 +63,8 @@ mp_decode_decimal(const char **data, decimal_t *dec);
char *
mp_encode_decimal(char *data, const = decimal_t *dec);

+#if = defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
#endif
diff --git a/test/engine/decimal.result = b/test/engine/decimal.result
new file mode 100644
index 000000000..16cd335e0
--- /dev/null
+++ b/test/engine/decimal.result
@@ -0,0 +1,226 = @@
+-- test-run result file version 2
+env =3D= require('test_run')
+ | ---
+ | ...
+test_run =3D env.new()
+ | ---
+ = | ...
+engine =3D test_run:get_cfg('engine')
+= | ---
+ | ...
+
+decimal =3D = require('decimal')
+ | ---
+ | ...
+ffi =3D require('ffi')
+ | ---
+ = | ...
+
+_ =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()
+ = | ---
+ | ...
diff --git = a/test/engine/decimal.test.lua b/test/engine/decimal.test.lua
new file mode 100644
index = 000000000..52a300e72
--- /dev/null
+++ = b/test/engine/decimal.test.lua
@@ -0,0 +1,65 @@
+env =3D require('test_run')
+test_run =3D = env.new()
+engine =3D test_run:get_cfg('engine')
+
+decimal =3D require('decimal')
+ffi =3D require('ffi')
+
+_ =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()
-- 
2.20.1 (Apple Git-117)


--=
Konstantin Osipov, Moscow, Russia


--
Serge = Petrenko

= --Apple-Mail=_7402D318-73FE-40D4-8770-E2CD5272BA50--