[tarantool-patches] [PATCH v2 4/8] lua: rework luaL_field types to support msgpack extensions

Serge Petrenko sergepetrenko at tarantool.org
Thu Aug 15 11:27:07 MSK 2019


> 15 авг. 2019 г., в 1:23, Konstantin Osipov <kostja at tarantool.org> написал(а):
> 
> * Serge Petrenko <sergepetrenko at 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’s what I’ve done in previous revision, but Vladimir didn’t 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’s 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’re not squashed yet in this revision.

=================================================
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 <http://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 = decimal_len(dec);
-	*data++ = decimal_scale(dec);
+	/* reserve space for resulting scale */
+	char *svp = data++;
	len--;
	int32_t scale;
	char *tmp = (char *)decPackedFromNumber((uint8_t *)data, len, &scale, dec);
	assert(tmp == data);
-	assert(scale == (int32_t)decimal_scale(dec));
+	/* scale may be negative, when exponent is > 0 */
+	assert(scale == (int32_t)decimal_scale(dec) || scale < 0);
+	mp_store_u8(svp, (int8_t)scale);
	(void)tmp;
	data += 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 = *((*data)++);
+	int32_t scale = (int8_t)mp_load_u8(data);
	len--;
	decimal_t *res = 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 = 0
+    MP_UNKNOWN = 0,
+    MP_DECIMAL = 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 = mpstream_reserve(stream, mp_sizeof_decimal(val));
+	if (data == NULL)
+		return;
+	char *pos = 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) == 2);						\
+	if (lua_isnil(L, 1) || lua_isnil(L, 2)) {				\
+		lua_pushboolean(L, false);					\
+		return 1;							\
+	}									\
	decimal_t *lhs = lua_todecimal(L, 1);					\
	decimal_t *rhs = 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 = 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) == top);
		return MP_ARRAY;
	case MP_EXT:
-		/* Run trigger if type can't be encoded */
-		type = luamp_encode_extension(L, top, stream);
-		if (type != 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 != MP_EXT);
-		assert(lua_gettop(L) == 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 = luamp_encode_extension(L, top, stream);
+			if (type != 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 != MP_EXT || field->ext_type != MP_UNKNOWN);
+			assert(lua_gettop(L) == 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 = mp_decode_extl(data, &type);
+		switch (type) {
+		case MP_DECIMAL:
+		{
+			decimal_t *dec = lua_pushdecimal(L);
+			dec = decimal_unpack(data, len, dec);
+			if (dec == 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 = (jit.arch == '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 = buf:alloc(builtin.mp_sizeof_decimal(num))
+    builtin.mp_encode_decimal(p, num)
+end
+
local function encode_int(buf, num)
    if num >= 0 then
        if num <= 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 = ffi.new("int8_t[1]")
+    -- mp_decode_extl and mp_decode_decimal
+    -- need type code
+    data[0] = data[0] - 1
+    local old_data = data[0]
+    local len = builtin.mp_decode_extl(data, t)
+    --MP_DECIMAL
+    if t[0] == 1 then
+        local num = ffi.new("decimal_t")
+        builtin.decimal_unpack(data, len, num)
+        return num
+    else
+        error("Unsupported extension type")
+    end
+end
+
local decoder_hint = {
    --[[{{{ MP_BIN]]
    [0xc4] = function(data) return decode_str(data, decode_u8(data)) end;
@@ -528,6 +559,8 @@ decode_r = function(data)
        return false
    elseif c == 0xc3 then
        return true
+    elseif c >= 0xd4 and c <= 0xd8 or c >= 0xc7 and c <= 0xc9 then
+        return decode_ext(data)
    else
        local fun = decoder_hint[c];
        assert (type(fun) == "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 = MP_EXT;
+			if (cd->ctypeid == CTID_DECIMAL) {
+				field->ext_type = MP_DECIMAL;
+				field->decval = (decimal_t *) cdata;
+			} else {
+				field->ext_type = 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 = MP_EXT;
+		field->ext_type = 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 = lua_gettop(L) + idx + 1;
-	assert(field->type == MP_EXT); /* must be called after tofield() */
+	assert(field->type == MP_EXT && field->ext_type == MP_UNKNOWN); /* must be called after tofield() */

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

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

-	if (field->type != MP_EXT)
+	if (field->type != MP_EXT || field->ext_type != 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;                /* 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 != MP_EXT)
+	if (field->type != MP_EXT || field->ext_type != 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 = 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 = decimal.new('10')
 | ...
a
 | ---
- | - '10'
+ | - 10
 | ...
b = 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 == -(a + b)
 | ---
@@ -167,11 +167,11 @@ b ^ a
 | ...
a
 | ---
- | - '10'
+ | - 10
 | ...
b
 | ---
- | - '0.1'
+ | - 0.1
 | ...

a < b
@@ -216,28 +216,28 @@ a ~= 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 == decimal.ln(decimal.exp(a))
 | ---
@@ -261,7 +261,7 @@ a + -a == 0
 | ...
a
 | ---
- | - '10'
+ | - 10
 | ...

a = decimal.new('1.1234567891234567891234567891234567891')
@@ -269,7 +269,7 @@ a = decimal.new('1.1234567891234567891234567891234567891')
 | ...
a
 | ---
- | - '1.1234567891234567891234567891234567891'
+ | - 1.1234567891234567891234567891234567891
 | ...
decimal.precision(a)
 | ---
@@ -285,7 +285,7 @@ decimal.round(a, 37) == a
 | ...
a
 | ---
- | - '1.1234567891234567891234567891234567891'
+ | - 1.1234567891234567891234567891234567891
 | ...
a = decimal.round(a, 36)
 | ---
@@ -309,19 +309,19 @@ decimal.round(a, -5) == 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 = 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 = 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 = 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 = require('decimal')
+---
+...
+a = decimal.new('1e37')
+---
+...
+b = decimal.new('1e-38')
+---
+...
+c = decimal.new('1')
+---
+...
+d = decimal.new('0.1234567')
+---
+...
+e = decimal.new('123.4567')
+---
+...
+msgpack.decode(msgpack.encode(a)) == a
+---
+- true
+...
+msgpack.decode(msgpack.encode(b)) == b
+---
+- true
+...
+msgpack.decode(msgpack.encode(c)) == c
+---
+- true
+...
+msgpack.decode(msgpack.encode(d)) == d
+---
+- true
+...
+msgpack.decode(msgpack.encode(e)) == 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 = msgpack.encode(100, buf)
-- is not negative.
--
msgpack.decode(ffi.cast('char *', '\x04\x05\x06'), -1)
+
+--
+-- gh-4333: msgpack encode/decode decimals.
+--
+decimal = require('decimal')
+a = decimal.new('1e37')
+b = decimal.new('1e-38')
+c = decimal.new('1')
+d = decimal.new('0.1234567')
+e = decimal.new('123.4567')
+msgpack.decode(msgpack.encode(a)) == a
+msgpack.decode(msgpack.encode(b)) == b
+msgpack.decode(msgpack.encode(c)) == c
+msgpack.decode(msgpack.encode(d)) == d
+msgpack.decode(msgpack.encode(e)) == e
diff --git a/third_party/lua-yaml/lyaml.cc <http://lyaml.cc/> b/third_party/lua-yaml/lyaml.cc <http://lyaml.cc/>
index 46c98bde1..8ff2867e3 100644
--- a/third_party/lua-yaml/lyaml.cc <http://lyaml.cc/>
+++ b/third_party/lua-yaml/lyaml.cc <http://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 <http://yaml.org/>,2002:"

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




=======================================

Here’s the old version of the patch adding decimal indices

=======================================
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 <http://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[] = {
	/* [FIELD_TYPE_UNSIGNED] =  */ 1U << MP_UINT,
	/* [FIELD_TYPE_STRING]   =  */ 1U << MP_STR,
	/* [FIELD_TYPE_NUMBER]   =  */ (1U << MP_UINT) | (1U << MP_INT) |
-		(1U << MP_FLOAT) | (1U << MP_DOUBLE),
+		(1U << MP_FLOAT) | (1U << MP_DOUBLE) | (1U << MP_EXT),
	/* [FIELD_TYPE_INTEGER]  =  */ (1U << MP_UINT) | (1U << MP_INT),
	/* [FIELD_TYPE_BOOLEAN]  =  */ 1U << MP_BOOL,
	/* [FIELD_TYPE_VARBINARY] =  */ 1U << MP_BIN,
	/* [FIELD_TYPE_SCALAR]   =  */ (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]  =  */ 1U << MP_EXT,
	/* [FIELD_TYPE_ARRAY]    =  */ 1U << MP_ARRAY,
	/* [FIELD_TYPE_MAP]      =  */ (1U << MP_MAP),
};

+const uint32_t field_ext_type[] = {
+	/* [FIELD_TYPE_ANY]      =  */ UINT32_MAX & ~(1U << MP_UNKNOWN),
+	/* [FIELD_TYPE_UNSIGNED] =  */ 0,
+	/* [FIELD_TYPE_STRING]   =  */ 0,
+	/* [FIELD_TYPE_NUMBER]   =  */ 1U << MP_DECIMAL,
+	/* [FIELD_TYPE_INTEGER]  =  */ 0,
+	/* [FIELD_TYPE_BOOLEAN]  =  */ 0,
+	/* [FIELD_TYPE_VARBINARY] = */ 0,
+	/* [FIELD_TYPE_SCALAR]   =  */ 1U << MP_DECIMAL,
+	/* [FIELD_TYPE_DECIMAL]  =  */ 1U << MP_DECIMAL,
+	/* [FIELD_TYPE_ARRAY]    =  */ 0,
+	/* [FIELD_TYPE_MAP]      =  */ 0,
+};
+
const char *field_type_strs[] = {
	/* [FIELD_TYPE_ANY]      = */ "any",
	/* [FIELD_TYPE_UNSIGNED] = */ "unsigned",
@@ -72,6 +87,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 +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[] = {
-	   /*   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 = mp_typeof(*data);
+	int8_t ext_type = MP_UNKNOWN;
	assert(type < field_type_MAX);
	assert((size_t)mp_type < CHAR_BIT * sizeof(*field_mp_type));
	uint32_t mask = field_mp_type[type] | (is_nullable * (1U << MP_NIL));
-	return (mask & (1U << mp_type)) != 0;
+	if (mp_type != MP_EXT)
+		return (mask & (1U << mp_type)) != 0;
+	mp_decode_extl(&data, &ext_type);
+	return (field_ext_type[type] & (1U << ext_type)) != 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 <http://tuple_compare.cc/> b/src/box/tuple_compare.cc <http://tuple_compare.cc/>
index 95a0f58c9..6ab28f662 100644
--- a/src/box/tuple_compare.cc <http://tuple_compare.cc/>
+++ b/src/box/tuple_compare.cc <http://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[] = {
	/* .MP_BOOL    = */ MP_CLASS_BOOL,
	/* .MP_FLOAT   = */ MP_CLASS_NUMBER,
	/* .MP_DOUBLE  = */ MP_CLASS_NUMBER,
-	/* .MP_BIN     = */ MP_CLASS_BIN
+	/* .MP_EXT     = */ 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 = mp_decode_float(&rhs);
+		decimal_from_double(&rhs_dec, d);
+		break;
+	}
+	case MP_DOUBLE:
+	{
+		double d = mp_decode_double(&rhs);
+		decimal_from_double(&rhs_dec, d);
+		break;
+	}
+	case MP_INT:
+	{
+		int64_t num = mp_decode_int(&rhs);
+		decimal_from_int64(&rhs_dec, num);
+		break;
+	}
+	case MP_UINT:
+	{
+		uint64_t num = mp_decode_uint(&rhs);
+		decimal_from_uint64(&rhs_dec, num);
+		break;
+	}
+	case MP_EXT:
+	{
+		int8_t ext_type;
+		uint32_t len = mp_decode_extl(&rhs, &ext_type);
+		assert(ext_type == 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) == 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 comarators.
+	 */
+	decimal_t dec;
+	if (rhs_type == MP_EXT) {
+		return mp_compare_decimal_any_number(
+			mp_decode_decimal(&rhs, &dec), lhs, lhs_type, -1
+		);
+	}
+	if (lhs_type == MP_EXT) {
+		return mp_compare_decimal_any_number(
+			mp_decode_decimal(&lhs, &dec), rhs, rhs_type, 1
+		);
+	}
	if (rhs_type == 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 != 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[] = {
#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 = {
+	18,				/* decimal digits */
+	0,				/* exponent */
+	0,				/* no special bits. */
+	{487, 423, 303, 752, 460, 576}	/* 576,460,752,303,423,488 = 2^59 - 1 */
+					/* 59 == HINT_VALUE_BITS - 1 */
+};
+
+static const decimal_t HINT_VALUE_DECIMAL_MIN = {
+	18,				/* decimal digits */
+	0,				/* exponent */
+	0x80,				/* negative bit */
+	{488, 423, 303, 752, 460, 576}	/* 576,460,752,303,423,488 = 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 = 0;
+	int64_t num;
+	if (decimal_compare(dec, &HINT_VALUE_DECIMAL_MAX) >= 0)
+		val = HINT_VALUE_MAX;
+	else if (decimal_compare(dec, &HINT_VALUE_DECIMAL_MIN) <= 0)
+		val = 0;
+	else {
+		dec = decimal_to_int64(dec, &num);
+		/* We've checked boundaries above. */
+		assert(dec != NULL);
+		val = 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 = 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 = decimal_unpack(&field, len, &dec);
+			assert(res != 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) == 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 = mp_decode_binl(&field);
		return hint_bin(field, len);
+	case MP_EXT:
+	{
+		int8_t ext_type;
+		uint32_t len = 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 = 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 = 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) != 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 = require('test_run')
+ | ---
+ | ...
+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', {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 <http://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 = require('test_run')
+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', {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 <http://box.space.test.index.sk/>:select{}
+
+box.space.test:drop()
-- 
2.20.1 (Apple Git-117)

> 
> -- 
> Konstantin Osipov, Moscow, Russia


--
Serge Petrenko
sergepetrenko at tarantool.org <mailto:sergepetrenko at tarantool.org>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.tarantool.org/pipermail/tarantool-patches/attachments/20190815/563364f9/attachment.html>


More information about the Tarantool-patches mailing list