[PATCH v3 4/6] decimal: allow to encode/decode decimals as MsgPack

Serge Petrenko sergepetrenko at tarantool.org
Tue Aug 20 20:10:02 MSK 2019


This patch adds the methods necessary to encode and decode decimals to
MsgPack. MsgPack EXT type (MP_EXT) together with a new extension type
MP_DECIMAL is used as a record header.

The decimal MsgPack representation looks like this:
+--------+-------------------+------------+===============+
| MP_EXT | length (optional) | MP_DECIMAL | PackedDecimal |
+--------+-------------------+------------+===============+
The whole record may be encoded and decoded with
mp_encode_decimal() and mp_decode_decimal(). This is equivalent to
performing mp_encode_extl()/mp_decode_extl() on the first 3 fields and
decimal_pack/unpack() on the PackedDecimal field.

It is also possible to decode and encode decimals to msgpack from lua,
which means you can insert decimals into spaces, but only into unindexed
fields for now.

Follow up #692
Part of #4333
---
 extra/exports                        |   4 +
 src/lib/core/CMakeLists.txt          |   1 +
 src/lib/core/mp_decimal.c            |  72 ++++++++++
 src/lib/core/mp_decimal.h            |  62 ++++++++
 src/lib/core/mp_extension_types.h    |  47 +++++++
 src/lib/core/mpstream.c              |  11 ++
 src/lib/core/mpstream.h              |   4 +
 src/lua/decimal.c                    |   4 +-
 src/lua/decimal.h                    |   7 +
 src/lua/msgpack.c                    |  53 +++++--
 src/lua/msgpackffi.lua               |  38 +++++
 src/lua/utils.c                      |  16 ++-
 src/lua/utils.h                      |   8 +-
 test/app-tap/lua/serializer_test.lua |  14 ++
 test/app-tap/msgpackffi.test.lua     |   3 +-
 test/app/decimal.result              | 146 +++++++++----------
 test/app/msgpack.result              |  41 ++++++
 test/app/msgpack.test.lua            |  15 ++
 test/box/tuple.result                |  85 +++++++++++
 test/box/tuple.test.lua              |  23 +++
 test/engine/decimal.result           |  81 +++++++++++
 test/engine/decimal.test.lua         |  21 +++
 test/unit/decimal.c                  |  60 +++++++-
 test/unit/decimal.result             | 202 ++++++++++++++++++++++++++-
 third_party/lua-cjson/lua_cjson.c    |  12 +-
 third_party/lua-yaml/lyaml.cc        |  10 +-
 26 files changed, 942 insertions(+), 98 deletions(-)
 create mode 100644 src/lib/core/mp_decimal.c
 create mode 100644 src/lib/core/mp_decimal.h
 create mode 100644 src/lib/core/mp_extension_types.h
 create mode 100644 test/engine/decimal.result
 create mode 100644 test/engine/decimal.test.lua

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/CMakeLists.txt b/src/lib/core/CMakeLists.txt
index 4b96d1105..e60b5199e 100644
--- a/src/lib/core/CMakeLists.txt
+++ b/src/lib/core/CMakeLists.txt
@@ -27,6 +27,7 @@ set(core_sources
     mpstream.c
     port.c
     decimal.c
+    mp_decimal.c
 )
 
 if (TARGET_OS_NETBSD)
diff --git a/src/lib/core/mp_decimal.c b/src/lib/core/mp_decimal.c
new file mode 100644
index 000000000..985e75291
--- /dev/null
+++ b/src/lib/core/mp_decimal.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2019, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "mp_decimal.h"
+#include "mp_extension_types.h"
+#include "msgpuck.h"
+#include "decimal.h"
+
+uint32_t
+mp_sizeof_decimal(const decimal_t *dec)
+{
+	return mp_sizeof_ext(decimal_len(dec));
+}
+
+decimal_t *
+mp_decode_decimal(const char **data, decimal_t *dec)
+{
+	if (mp_typeof(**data) != MP_EXT)
+		return NULL;
+
+	int8_t type;
+	uint32_t len;
+	const char *const svp = *data;
+
+	len = mp_decode_extl(data, &type);
+
+	if (type != MP_DECIMAL || len == 0) {
+		*data = svp;
+		return NULL;
+	}
+	decimal_t *res = decimal_unpack(data,  len, dec);
+	if (!res)
+		*data = svp;
+	return res;
+}
+
+char *
+mp_encode_decimal(char *data, const decimal_t *dec)
+{
+	uint32_t len = decimal_len(dec);
+	data = mp_encode_extl(data, MP_DECIMAL, len);
+	data = decimal_pack(data, dec);
+	return data;
+}
diff --git a/src/lib/core/mp_decimal.h b/src/lib/core/mp_decimal.h
new file mode 100644
index 000000000..a991a5f16
--- /dev/null
+++ b/src/lib/core/mp_decimal.h
@@ -0,0 +1,62 @@
+#ifndef TARANTOOL_LIB_CORE_MP_DECIMAL_INCLUDED
+#define TARANTOOL_LIB_CORE_MP_DECIMAL_INCLUDED
+/*
+ * Copyright 2019, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "decimal.h"
+#include <stdint.h>
+
+/**
+ * \brief Calculate exact buffer size needed to store a decimal
+ * pointed to by \a dec.
+ */
+uint32_t
+mp_sizeof_decimal(const decimal_t *dec);
+
+/**
+ * \brief Decode a decimal from MsgPack \a data
+ * \param data - buffer pointer
+ * \return the decoded decimal
+ * \post *data = *data + mp_sizeof_decimal(retval)
+ */
+decimal_t *
+mp_decode_decimal(const char **data, decimal_t *dec);
+
+/**
+ * \brief Encode a decimal pointed to by \a dec.
+ * \parad dec - decimal pointer
+ * \param data - a buffer
+ * \return \a data + mp_sizeof_decimal(\a dec)
+ */
+char *
+mp_encode_decimal(char *data, const decimal_t *dec);
+
+#endif
diff --git a/src/lib/core/mp_extension_types.h b/src/lib/core/mp_extension_types.h
new file mode 100644
index 000000000..bc9873f68
--- /dev/null
+++ b/src/lib/core/mp_extension_types.h
@@ -0,0 +1,47 @@
+#ifndef TARANTOOL_LIB_CORE_MP_USER_TYPES_H_INCLUDED
+#define TARANTOOL_LIB_CORE_MP_USER_TYPES_H_INCLUDED
+/*
+ * Copyright 2019, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "msgpuck.h"
+
+/**
+ * MessagePack extension type. Used as a subtype after MP_EXT
+ * format specifier.
+ * Values in range [-128, -1] are reserved.
+ * You may assign values in range [0, 127]
+ */
+enum mp_extension_type {
+    MP_UNKNOWN_EXTENSION = 0,
+    MP_DECIMAL = 1,
+};
+
+#endif
diff --git a/src/lib/core/mpstream.c b/src/lib/core/mpstream.c
index 8b7276ab1..2be1797d0 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, const 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..3a022daa0 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, const 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 7f9358787..23e50ba68 100644
--- a/src/lua/decimal.c
+++ b/src/lua/decimal.c
@@ -79,10 +79,8 @@ ldecimal_##name(struct lua_State *L) {						\
 	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..b13e59247 100644
--- a/src/lua/decimal.h
+++ b/src/lua/decimal.h
@@ -31,12 +31,19 @@
 #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) */
 
+extern uint32_t CTID_DECIMAL;
+
 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..c2be0b3e8 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" /* lua_pushdecimal() */
+#include "lib/core/decimal.h" /* decimal_unpack() */
+#include "lib/core/mp_extension_types.h"
+
 #include <fiber.h>
 
 void
@@ -175,16 +179,22 @@ 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;
+		default:
+			/* 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;
+		}
 	}
 	return MP_EXT;
 }
@@ -283,9 +293,30 @@ luamp_decode(struct lua_State *L, struct luaL_serializer *cfg,
 		return;
 	}
 	case MP_EXT:
-		luamp_decode_extension(L, data);
+	{
+		int8_t ext_type;
+		const char *svp = *data;
+		uint32_t len = mp_decode_extl(data, &ext_type);
+		switch (ext_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:
+			/* reset data to the extension header */
+			*data = svp;
+			luamp_decode_extension(L, data);
+			break;
+		}
 		break;
 	}
+	}
 }
 
 
diff --git a/src/lua/msgpackffi.lua b/src/lua/msgpackffi.lua
index bfeedbc4b..f7ee44291 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,28 @@ local function decode_map(data, size)
     return setmetatable(map, msgpack.map_mt)
 end
 
+local ext_decoder = {
+    -- MP_UNKNOWN_EXTENSION
+    [0] = function(data, len) error("unsupported extension type") end,
+    -- MP_DECIMAL
+    [1] = function(data, len) local num = ffi.new("decimal_t") builtin.decimal_unpack(data, len, num) return num 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)
+    local fun = ext_decoder[t[0]]
+    if type(fun) == 'function' then
+        return fun(data, len)
+    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 +564,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..75efe0ed2 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_EXTENSION;
+			}
 		}
 		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_EXTENSION;
 	}
 #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_EXTENSION); /* 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_EXTENSION &&
+	    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_EXTENSION)
 		return;
 
 	if (cfg->encode_invalid_as_nil) {
diff --git a/src/lua/utils.h b/src/lua/utils.h
index 7e7cdc0c6..d42cc3992 100644
--- a/src/lua/utils.h
+++ b/src/lua/utils.h
@@ -55,6 +55,9 @@ extern "C" {
 
 #include "lua/error.h"
 
+#include "lib/core/mp_extension_types.h"
+#include "lib/core/decimal.h" /* decimal_t */
+
 struct lua_State;
 struct ibuf;
 
@@ -286,8 +289,11 @@ struct luaL_field {
 		bool bval;
 		/* Array or map. */
 		uint32_t size;
+		decimal_t *decval;
 	};
 	enum mp_type type;
+	/* subtypes of MP_EXT */
+	enum mp_extension_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_EXTENSION)
 		return;
 	luaL_convertfield(L, cfg, idx, field);
 }
diff --git a/test/app-tap/lua/serializer_test.lua b/test/app-tap/lua/serializer_test.lua
index a48e557a3..e7048da4d 100644
--- a/test/app-tap/lua/serializer_test.lua
+++ b/test/app-tap/lua/serializer_test.lua
@@ -189,6 +189,17 @@ local function test_double(test, s)
     ss = nil
 end
 
+local function test_decimal(test, s)
+    local decimal = require('decimal')
+    test:plan(10)
+
+    rt(test, s, decimal.new(1), 'cdata')
+    rt(test, s, decimal.new('1e37'), 'cdata')
+    rt(test, s, decimal.new('1e-38'), 'cdata')
+    rt(test, s, decimal.new('1234567891234567890.0987654321987654321'), 'cdata')
+    rt(test, s, decimal.new('-1234567891234567890.0987654321987654321'), 'cdata')
+end
+
 local function test_boolean(test, s)
     test:plan(4)
 
@@ -223,6 +234,8 @@ local function test_nil(test, s)
     test:is(t[4], s.NULL, "sparse array with NULL")
 end
 
+
+
 local function test_table(test, s, is_array, is_map)
     test:plan(s.cfg and 31 or 13)
 
@@ -398,4 +411,5 @@ return {
     test_nil = test_nil;
     test_table = test_table;
     test_ucdata = test_ucdata;
+    test_decimal = test_decimal;
 }
diff --git a/test/app-tap/msgpackffi.test.lua b/test/app-tap/msgpackffi.test.lua
index f9646335b..f2a8f254b 100755
--- a/test/app-tap/msgpackffi.test.lua
+++ b/test/app-tap/msgpackffi.test.lua
@@ -72,10 +72,11 @@ end
 
 tap.test("msgpackffi", function(test)
     local serializer = require('msgpackffi')
-    test:plan(9)
+    test:plan(10)
     test:test("unsigned", common.test_unsigned, serializer)
     test:test("signed", common.test_signed, serializer)
     test:test("double", common.test_double, serializer)
+    test:test("decimal", common.test_decimal, serializer)
     test:test("boolean", common.test_boolean, serializer)
     test:test("string", common.test_string, serializer)
     test:test("nil", common.test_nil, serializer)
diff --git a/test/app/decimal.result b/test/app/decimal.result
index 2e44928bb..8251e13d8 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,11 +216,11 @@ a ~= b
  | ...
 a
  | ---
- | - '10'
+ | - 10
  | ...
 b
  | ---
- | - '0.1'
+ | - 0.1
  | ...
 
 -- check comparsion with nil
@@ -251,19 +251,19 @@ a <= nil
 
 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))
  | ---
@@ -287,7 +287,7 @@ a + -a == 0
  | ...
 a
  | ---
- | - '10'
+ | - 10
  | ...
 
 a = decimal.new('1.1234567891234567891234567891234567891')
@@ -295,7 +295,7 @@ a = decimal.new('1.1234567891234567891234567891234567891')
  | ...
 a
  | ---
- | - '1.1234567891234567891234567891234567891'
+ | - 1.1234567891234567891234567891234567891
  | ...
 decimal.precision(a)
  | ---
@@ -311,7 +311,7 @@ decimal.round(a, 37) == a
  | ...
 a
  | ---
- | - '1.1234567891234567891234567891234567891'
+ | - 1.1234567891234567891234567891234567891
  | ...
 a = decimal.round(a, 36)
  | ---
@@ -335,19 +335,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)
@@ -360,7 +360,7 @@ decimal.ln(-1)
  | ...
 decimal.ln(1)
  | ---
- | - '0'
+ | - 0
  | ...
 decimal.log10(0)
  | ---
@@ -372,7 +372,7 @@ decimal.log10(-1)
  | ...
 decimal.log10(1)
  | ---
- | - '0'
+ | - 0
  | ...
 decimal.exp(88)
  | ---
@@ -380,7 +380,7 @@ decimal.exp(88)
  | ...
 decimal.exp(87)
  | ---
- | - '60760302250568721495223289381302760753'
+ | - 60760302250568721495223289381302760753
  | ...
 decimal.sqrt(-5)
  | ---
@@ -388,7 +388,7 @@ decimal.sqrt(-5)
  | ...
 decimal.sqrt(5)
  | ---
- | - '2.2360679774997896964091736687312762354'
+ | - 2.2360679774997896964091736687312762354
  | ...
 
 -- various incorrect operands
@@ -434,11 +434,11 @@ a ^ 2
  | ...
 a ^ 1.9
  | ---
- | - '1258925411794167210423954106395800606.1'
+ | - 1258925411794167210423954106395800606.1
  | ...
 a * '1e18'
  | ---
- | - '10000000000000000000000000000000000000'
+ | - 10000000000000000000000000000000000000
  | ...
 a = decimal.new(string.rep('9', 38))
  | ---
@@ -461,7 +461,7 @@ a + '0.5'
  | ...
 a + '0.4'
  | ---
- | - '99999999999999999999999999999999999999'
+ | - 99999999999999999999999999999999999999
  | ...
 a / 0.5
  | ---
@@ -477,7 +477,7 @@ a = decimal.new('-13')
  | ...
 a ^ 2
  | ---
- | - '169'
+ | - 169
  | ...
 -- fractional powers are allowed only for positive numbers
 a ^ 2.5
@@ -490,69 +490,69 @@ a = decimal.new('1e5')
  | ...
 a
  | ---
- | - '100000'
+ | - 100000
  | ...
 decimal.trim(a)
  | ---
- | - '100000'
+ | - 100000
  | ...
 decimal.trim(decimal.rescale(a, 10))
  | ---
- | - '100000'
+ | - 100000
  | ...
 decimal.rescale(a, 10)
  | ---
- | - '100000.0000000000'
+ | - 100000.0000000000
  | ...
 decimal.rescale(a, -5)
  | ---
- | - '100000'
+ | - 100000
  | ...
 decimal.rescale(a, 0)
  | ---
- | - '100000'
+ | - 100000
  | ...
 decimal.rescale(a, 32)
  | ---
- | - '100000.00000000000000000000000000000000'
+ | - 100000.00000000000000000000000000000000
  | ...
 -- scale too big
 decimal.rescale(a, 33)
  | ---
- | - '100000'
+ | - 100000
  | ...
 decimal.trim(decimal.rescale(a, 10))
  | ---
- | - '100000'
+ | - 100000
  | ...
 a = decimal.new('123.456789000000000')
  | ---
  | ...
 a
  | ---
- | - '123.456789000000000'
+ | - 123.456789000000000
  | ...
 decimal.trim(a)
  | ---
- | - '123.456789'
+ | - 123.456789
  | ...
 
 -- 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
  | ...
 
 -- check remainder operation
@@ -561,37 +561,37 @@ a = decimal.new(172.51)
  | ...
 a % 1
  | ---
- | - '0.51'
+ | - 0.51
  | ...
 a % 2
  | ---
- | - '0.51'
+ | - 0.51
  | ...
 a % 0.3
  | ---
- | - '0.01'
+ | - 0.01
  | ...
 a % 0.13
  | ---
- | - '0.00'
+ | - 0.00
  | ...
 a % 13.27
  | ---
- | - '0.00'
+ | - 0.00
  | ...
 a % 100
  | ---
- | - '72.51'
+ | - 72.51
  | ...
 a % 173
  | ---
- | - '172.51'
+ | - 172.51
  | ...
 a % 72
  | ---
- | - '28.51'
+ | - 28.51
  | ...
 720 % a
  | ---
- | - '29.96'
+ | - 29.96
  | ...
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/test/box/tuple.result b/test/box/tuple.result
index 2561ebc35..a5010538d 100644
--- a/test/box/tuple.result
+++ b/test/box/tuple.result
@@ -1252,3 +1252,88 @@ s2:frommap({a="1", k="11"}):tomap({names_only = true})
 s2:drop()
 ---
 ...
+-- test decimals in tuple
+dec = require('decimal')
+---
+...
+a = dec.new('1')
+---
+...
+b = dec.new('1e10')
+---
+...
+c = dec.new('1e-10')
+---
+...
+d = box.tuple.new{5, a, 6, b, 7, c, "string"}
+---
+...
+d
+---
+- [5, 1, 6, 10000000000, 7, 0.0000000001, 'string']
+...
+state, val = d:next()
+---
+...
+state
+---
+- 1
+...
+val
+---
+- 5
+...
+state, val = d:next(state)
+---
+...
+state
+---
+- 2
+...
+val
+---
+- 1
+...
+state, val = d:next(state)
+---
+...
+state, val
+---
+- 3
+- 6
+...
+d:slice(1)
+---
+- 1
+- 6
+- 10000000000
+- 7
+- 0.0000000001
+- string
+...
+d:slice(-1)
+---
+- string
+...
+d:slice(-2)
+---
+- 0.0000000001
+- string
+...
+msgpack.decode(msgpackffi.encode(d))
+---
+- [5, 1, 6, 10000000000, 7, 0.0000000001, 'string']
+- 24
+...
+d:bsize()
+---
+- 23
+...
+d:update{{'!', 3, dec.new('1234.5678')}}
+---
+- [5, 1, 1234.5678, 6, 10000000000, 7, 0.0000000001, 'string']
+...
+d:update{{'=', -1, dec.new('0.12345678910111213')}}
+---
+- [5, 1, 6, 10000000000, 7, 0.0000000001, 0.12345678910111213]
+...
diff --git a/test/box/tuple.test.lua b/test/box/tuple.test.lua
index b0a4ab173..8d4431bc6 100644
--- a/test/box/tuple.test.lua
+++ b/test/box/tuple.test.lua
@@ -426,3 +426,26 @@ s2:frommap({a="1", k="11"})
 s2:frommap({a="1", k="11"}):tomap({names_only = true})
 
 s2:drop()
+
+-- test decimals in tuple
+dec = require('decimal')
+a = dec.new('1')
+b = dec.new('1e10')
+c = dec.new('1e-10')
+d = box.tuple.new{5, a, 6, b, 7, c, "string"}
+d
+state, val = d:next()
+state
+val
+state, val = d:next(state)
+state
+val
+state, val = d:next(state)
+state, val
+d:slice(1)
+d:slice(-1)
+d:slice(-2)
+msgpack.decode(msgpackffi.encode(d))
+d:bsize()
+d:update{{'!', 3, dec.new('1234.5678')}}
+d:update{{'=', -1, dec.new('0.12345678910111213')}}
diff --git a/test/engine/decimal.result b/test/engine/decimal.result
new file mode 100644
index 000000000..f8888d7c9
--- /dev/null
+++ b/test/engine/decimal.result
@@ -0,0 +1,81 @@
+-- test-run result file version 2
+env = require('test_run')
+ | ---
+ | ...
+test_run = env.new()
+ | ---
+ | ...
+engine = test_run:get_cfg('engine')
+ | ---
+ | ...
+
+decimal = require('decimal')
+ | ---
+ | ...
+
+_ = box.schema.space.create('test', {engine=engine})
+ | ---
+ | ...
+_ = box.space.test:create_index('pk')
+ | ---
+ | ...
+box.space.test:insert{1, decimal.new(1.1)}
+ | ---
+ | - [1, 1.1]
+ | ...
+box.space.test:insert{2, decimal.new(2.2)}
+ | ---
+ | - [2, 2.2]
+ | ...
+box.space.test:insert{3, decimal.new(1.1)}
+ | ---
+ | - [3, 1.1]
+ | ...
+box.space.test:insert{4, decimal.new('1234567890123456789.9876543210987654321'), decimal.new(1.2345)}
+ | ---
+ | - [4, 1234567890123456789.9876543210987654321, 1.2345]
+ | ...
+box.space.test:select{}
+ | ---
+ | - - [1, 1.1]
+ |   - [2, 2.2]
+ |   - [3, 1.1]
+ |   - [4, 1234567890123456789.9876543210987654321, 1.2345]
+ | ...
+a = box.space.test:get{4}
+ | ---
+ | ...
+a:next()
+ | ---
+ | - 1
+ | - 4
+ | ...
+a:next(1)
+ | ---
+ | - 2
+ | - 1234567890123456789.9876543210987654321
+ | ...
+a:next(2)
+ | ---
+ | - 3
+ | - 1.2345
+ | ...
+a:slice(-2)
+ | ---
+ | - 1234567890123456789.9876543210987654321
+ | - 1.2345
+ | ...
+box.space.test:replace{3, decimal.new(3.3)}
+ | ---
+ | - [3, 3.3]
+ | ...
+box.space.test:select{}
+ | ---
+ | - - [1, 1.1]
+ |   - [2, 2.2]
+ |   - [3, 3.3]
+ |   - [4, 1234567890123456789.9876543210987654321, 1.2345]
+ | ...
+box.space.test:drop()
+ | ---
+ | ...
diff --git a/test/engine/decimal.test.lua b/test/engine/decimal.test.lua
new file mode 100644
index 000000000..1b14871b0
--- /dev/null
+++ b/test/engine/decimal.test.lua
@@ -0,0 +1,21 @@
+env = require('test_run')
+test_run = env.new()
+engine = test_run:get_cfg('engine')
+
+decimal = require('decimal')
+
+_ = box.schema.space.create('test', {engine=engine})
+_ = box.space.test:create_index('pk')
+box.space.test:insert{1, decimal.new(1.1)}
+box.space.test:insert{2, decimal.new(2.2)}
+box.space.test:insert{3, decimal.new(1.1)}
+box.space.test:insert{4, decimal.new('1234567890123456789.9876543210987654321'), decimal.new(1.2345)}
+box.space.test:select{}
+a = box.space.test:get{4}
+a:next()
+a:next(1)
+a:next(2)
+a:slice(-2)
+box.space.test:replace{3, decimal.new(3.3)}
+box.space.test:select{}
+box.space.test:drop()
diff --git a/test/unit/decimal.c b/test/unit/decimal.c
index b55333ed7..625913efe 100644
--- a/test/unit/decimal.c
+++ b/test/unit/decimal.c
@@ -1,5 +1,8 @@
 #include "unit.h"
 #include "decimal.h"
+#include "mp_decimal.h"
+#include "mp_extension_types.h"
+#include "msgpuck.h"
 #include <limits.h>
 #include <string.h>
 #include <float.h> /* DBL_DIG */
@@ -71,6 +74,32 @@
 
 char buf[32];
 
+#define test_mpdec(str) ({\
+	decimal_t dec;\
+	decimal_from_string(&dec, str);\
+	uint32_t l1 = mp_sizeof_decimal(&dec);\
+	ok(l1 <= 25 && l1 >= 4, "mp_sizeof_decimal("str")");\
+	char *b1 = mp_encode_decimal(buf, &dec);\
+	is(b1, buf + l1, "mp_sizeof_decimal("str") == len(mp_encode_decimal("str"))");\
+	const char *b2 = buf;\
+	const char *b3 = buf;\
+	decimal_t d2;\
+	mp_next(&b3);\
+	is(b3, b1, "mp_next(mp_encode("str"))");\
+	mp_decode_decimal(&b2, &d2);\
+	is(b1, b2, "mp_decode(mp_encode("str") len");\
+	is(decimal_compare(&dec, &d2), 0, "mp_decode(mp_encode("str")) value");\
+	is(decimal_scale(&dec), decimal_scale(&d2), "mp_decode(mp_encode("str")) scale");\
+	is(strcmp(decimal_to_string(&d2), str), 0, "str(mp_decode(mp_encode("str"))) == "str);\
+	b2 = buf;\
+	int8_t type;\
+	uint32_t l2 = mp_decode_extl(&b2, &type);\
+	is(type, MP_DECIMAL, "mp_ext_type is MP_DECIMAL");\
+	is(&d2, decimal_unpack(&b2, l2, &d2), "decimal_unpack() after mp_decode_extl()");\
+	is(decimal_compare(&dec, &d2), 0, "decimal_unpack() after mp_decode_extl() value");\
+	is(b2, buf + l1, "decimal_unpack() after mp_decode_extl() len");\
+})
+
 #define test_decpack(str) ({\
 	decimal_t dec;\
 	decimal_from_string(&dec, str);\
@@ -136,10 +165,37 @@ test_pack_unpack(void)
 	return check_plan();
 }
 
+static int
+test_mp_decimal(void)
+{
+	plan(198);
+
+	test_mpdec("0");
+	test_mpdec("-0");
+	test_mpdec("1");
+	test_mpdec("-1");
+	test_mpdec("0.1");
+	test_mpdec("-0.1");
+	test_mpdec("2.718281828459045");
+	test_mpdec("-2.718281828459045");
+	test_mpdec("3.141592653589793");
+	test_mpdec("-3.141592653589793");
+	test_mpdec("1234567891234567890.0987654321987654321");
+	test_mpdec("-1234567891234567890.0987654321987654321");
+	test_mpdec("0.0000000000000000000000000000000000001");
+	test_mpdec("-0.0000000000000000000000000000000000001");
+	test_mpdec("0.00000000000000000000000000000000000001");
+	test_mpdec("-0.00000000000000000000000000000000000001");
+	test_mpdec("99999999999999999999999999999999999999");
+	test_mpdec("-99999999999999999999999999999999999999");
+
+	return check_plan();
+}
+
 int
 main(void)
 {
-	plan(279);
+	plan(280);
 
 	dectest(314, 271, uint64, uint64_t);
 	dectest(65535, 23456, uint64, uint64_t);
@@ -201,5 +257,7 @@ main(void)
 
 	test_pack_unpack();
 
+	test_mp_decimal();
+
 	return check_plan();
 }
diff --git a/test/unit/decimal.result b/test/unit/decimal.result
index 2dd91af49..e6812a8dd 100644
--- a/test/unit/decimal.result
+++ b/test/unit/decimal.result
@@ -1,4 +1,4 @@
-1..279
+1..280
 ok 1 - decimal(314)
 ok 2 - decimal(271)
 ok 3 - decimal(314) + decimal(271)
@@ -430,3 +430,203 @@ ok 278 - decimal_sqrt(-10) - error on wrong operands.
     ok 150 - unpack malformed decimal fails
     ok 151 - decode malformed decimal preserves buffer position
 ok 279 - subtests
+    1..198
+    ok 1 - mp_sizeof_decimal(0)
+    ok 2 - mp_sizeof_decimal(0) == len(mp_encode_decimal(0))
+    ok 3 - mp_next(mp_encode(0))
+    ok 4 - mp_decode(mp_encode(0) len
+    ok 5 - mp_decode(mp_encode(0)) value
+    ok 6 - mp_decode(mp_encode(0)) scale
+    ok 7 - str(mp_decode(mp_encode(0))) == 0
+    ok 8 - mp_ext_type is MP_DECIMAL
+    ok 9 - decimal_unpack() after mp_decode_extl()
+    ok 10 - decimal_unpack() after mp_decode_extl() value
+    ok 11 - decimal_unpack() after mp_decode_extl() len
+    ok 12 - mp_sizeof_decimal(-0)
+    ok 13 - mp_sizeof_decimal(-0) == len(mp_encode_decimal(-0))
+    ok 14 - mp_next(mp_encode(-0))
+    ok 15 - mp_decode(mp_encode(-0) len
+    ok 16 - mp_decode(mp_encode(-0)) value
+    ok 17 - mp_decode(mp_encode(-0)) scale
+    ok 18 - str(mp_decode(mp_encode(-0))) == -0
+    ok 19 - mp_ext_type is MP_DECIMAL
+    ok 20 - decimal_unpack() after mp_decode_extl()
+    ok 21 - decimal_unpack() after mp_decode_extl() value
+    ok 22 - decimal_unpack() after mp_decode_extl() len
+    ok 23 - mp_sizeof_decimal(1)
+    ok 24 - mp_sizeof_decimal(1) == len(mp_encode_decimal(1))
+    ok 25 - mp_next(mp_encode(1))
+    ok 26 - mp_decode(mp_encode(1) len
+    ok 27 - mp_decode(mp_encode(1)) value
+    ok 28 - mp_decode(mp_encode(1)) scale
+    ok 29 - str(mp_decode(mp_encode(1))) == 1
+    ok 30 - mp_ext_type is MP_DECIMAL
+    ok 31 - decimal_unpack() after mp_decode_extl()
+    ok 32 - decimal_unpack() after mp_decode_extl() value
+    ok 33 - decimal_unpack() after mp_decode_extl() len
+    ok 34 - mp_sizeof_decimal(-1)
+    ok 35 - mp_sizeof_decimal(-1) == len(mp_encode_decimal(-1))
+    ok 36 - mp_next(mp_encode(-1))
+    ok 37 - mp_decode(mp_encode(-1) len
+    ok 38 - mp_decode(mp_encode(-1)) value
+    ok 39 - mp_decode(mp_encode(-1)) scale
+    ok 40 - str(mp_decode(mp_encode(-1))) == -1
+    ok 41 - mp_ext_type is MP_DECIMAL
+    ok 42 - decimal_unpack() after mp_decode_extl()
+    ok 43 - decimal_unpack() after mp_decode_extl() value
+    ok 44 - decimal_unpack() after mp_decode_extl() len
+    ok 45 - mp_sizeof_decimal(0.1)
+    ok 46 - mp_sizeof_decimal(0.1) == len(mp_encode_decimal(0.1))
+    ok 47 - mp_next(mp_encode(0.1))
+    ok 48 - mp_decode(mp_encode(0.1) len
+    ok 49 - mp_decode(mp_encode(0.1)) value
+    ok 50 - mp_decode(mp_encode(0.1)) scale
+    ok 51 - str(mp_decode(mp_encode(0.1))) == 0.1
+    ok 52 - mp_ext_type is MP_DECIMAL
+    ok 53 - decimal_unpack() after mp_decode_extl()
+    ok 54 - decimal_unpack() after mp_decode_extl() value
+    ok 55 - decimal_unpack() after mp_decode_extl() len
+    ok 56 - mp_sizeof_decimal(-0.1)
+    ok 57 - mp_sizeof_decimal(-0.1) == len(mp_encode_decimal(-0.1))
+    ok 58 - mp_next(mp_encode(-0.1))
+    ok 59 - mp_decode(mp_encode(-0.1) len
+    ok 60 - mp_decode(mp_encode(-0.1)) value
+    ok 61 - mp_decode(mp_encode(-0.1)) scale
+    ok 62 - str(mp_decode(mp_encode(-0.1))) == -0.1
+    ok 63 - mp_ext_type is MP_DECIMAL
+    ok 64 - decimal_unpack() after mp_decode_extl()
+    ok 65 - decimal_unpack() after mp_decode_extl() value
+    ok 66 - decimal_unpack() after mp_decode_extl() len
+    ok 67 - mp_sizeof_decimal(2.718281828459045)
+    ok 68 - mp_sizeof_decimal(2.718281828459045) == len(mp_encode_decimal(2.718281828459045))
+    ok 69 - mp_next(mp_encode(2.718281828459045))
+    ok 70 - mp_decode(mp_encode(2.718281828459045) len
+    ok 71 - mp_decode(mp_encode(2.718281828459045)) value
+    ok 72 - mp_decode(mp_encode(2.718281828459045)) scale
+    ok 73 - str(mp_decode(mp_encode(2.718281828459045))) == 2.718281828459045
+    ok 74 - mp_ext_type is MP_DECIMAL
+    ok 75 - decimal_unpack() after mp_decode_extl()
+    ok 76 - decimal_unpack() after mp_decode_extl() value
+    ok 77 - decimal_unpack() after mp_decode_extl() len
+    ok 78 - mp_sizeof_decimal(-2.718281828459045)
+    ok 79 - mp_sizeof_decimal(-2.718281828459045) == len(mp_encode_decimal(-2.718281828459045))
+    ok 80 - mp_next(mp_encode(-2.718281828459045))
+    ok 81 - mp_decode(mp_encode(-2.718281828459045) len
+    ok 82 - mp_decode(mp_encode(-2.718281828459045)) value
+    ok 83 - mp_decode(mp_encode(-2.718281828459045)) scale
+    ok 84 - str(mp_decode(mp_encode(-2.718281828459045))) == -2.718281828459045
+    ok 85 - mp_ext_type is MP_DECIMAL
+    ok 86 - decimal_unpack() after mp_decode_extl()
+    ok 87 - decimal_unpack() after mp_decode_extl() value
+    ok 88 - decimal_unpack() after mp_decode_extl() len
+    ok 89 - mp_sizeof_decimal(3.141592653589793)
+    ok 90 - mp_sizeof_decimal(3.141592653589793) == len(mp_encode_decimal(3.141592653589793))
+    ok 91 - mp_next(mp_encode(3.141592653589793))
+    ok 92 - mp_decode(mp_encode(3.141592653589793) len
+    ok 93 - mp_decode(mp_encode(3.141592653589793)) value
+    ok 94 - mp_decode(mp_encode(3.141592653589793)) scale
+    ok 95 - str(mp_decode(mp_encode(3.141592653589793))) == 3.141592653589793
+    ok 96 - mp_ext_type is MP_DECIMAL
+    ok 97 - decimal_unpack() after mp_decode_extl()
+    ok 98 - decimal_unpack() after mp_decode_extl() value
+    ok 99 - decimal_unpack() after mp_decode_extl() len
+    ok 100 - mp_sizeof_decimal(-3.141592653589793)
+    ok 101 - mp_sizeof_decimal(-3.141592653589793) == len(mp_encode_decimal(-3.141592653589793))
+    ok 102 - mp_next(mp_encode(-3.141592653589793))
+    ok 103 - mp_decode(mp_encode(-3.141592653589793) len
+    ok 104 - mp_decode(mp_encode(-3.141592653589793)) value
+    ok 105 - mp_decode(mp_encode(-3.141592653589793)) scale
+    ok 106 - str(mp_decode(mp_encode(-3.141592653589793))) == -3.141592653589793
+    ok 107 - mp_ext_type is MP_DECIMAL
+    ok 108 - decimal_unpack() after mp_decode_extl()
+    ok 109 - decimal_unpack() after mp_decode_extl() value
+    ok 110 - decimal_unpack() after mp_decode_extl() len
+    ok 111 - mp_sizeof_decimal(1234567891234567890.0987654321987654321)
+    ok 112 - mp_sizeof_decimal(1234567891234567890.0987654321987654321) == len(mp_encode_decimal(1234567891234567890.0987654321987654321))
+    ok 113 - mp_next(mp_encode(1234567891234567890.0987654321987654321))
+    ok 114 - mp_decode(mp_encode(1234567891234567890.0987654321987654321) len
+    ok 115 - mp_decode(mp_encode(1234567891234567890.0987654321987654321)) value
+    ok 116 - mp_decode(mp_encode(1234567891234567890.0987654321987654321)) scale
+    ok 117 - str(mp_decode(mp_encode(1234567891234567890.0987654321987654321))) == 1234567891234567890.0987654321987654321
+    ok 118 - mp_ext_type is MP_DECIMAL
+    ok 119 - decimal_unpack() after mp_decode_extl()
+    ok 120 - decimal_unpack() after mp_decode_extl() value
+    ok 121 - decimal_unpack() after mp_decode_extl() len
+    ok 122 - mp_sizeof_decimal(-1234567891234567890.0987654321987654321)
+    ok 123 - mp_sizeof_decimal(-1234567891234567890.0987654321987654321) == len(mp_encode_decimal(-1234567891234567890.0987654321987654321))
+    ok 124 - mp_next(mp_encode(-1234567891234567890.0987654321987654321))
+    ok 125 - mp_decode(mp_encode(-1234567891234567890.0987654321987654321) len
+    ok 126 - mp_decode(mp_encode(-1234567891234567890.0987654321987654321)) value
+    ok 127 - mp_decode(mp_encode(-1234567891234567890.0987654321987654321)) scale
+    ok 128 - str(mp_decode(mp_encode(-1234567891234567890.0987654321987654321))) == -1234567891234567890.0987654321987654321
+    ok 129 - mp_ext_type is MP_DECIMAL
+    ok 130 - decimal_unpack() after mp_decode_extl()
+    ok 131 - decimal_unpack() after mp_decode_extl() value
+    ok 132 - decimal_unpack() after mp_decode_extl() len
+    ok 133 - mp_sizeof_decimal(0.0000000000000000000000000000000000001)
+    ok 134 - mp_sizeof_decimal(0.0000000000000000000000000000000000001) == len(mp_encode_decimal(0.0000000000000000000000000000000000001))
+    ok 135 - mp_next(mp_encode(0.0000000000000000000000000000000000001))
+    ok 136 - mp_decode(mp_encode(0.0000000000000000000000000000000000001) len
+    ok 137 - mp_decode(mp_encode(0.0000000000000000000000000000000000001)) value
+    ok 138 - mp_decode(mp_encode(0.0000000000000000000000000000000000001)) scale
+    ok 139 - str(mp_decode(mp_encode(0.0000000000000000000000000000000000001))) == 0.0000000000000000000000000000000000001
+    ok 140 - mp_ext_type is MP_DECIMAL
+    ok 141 - decimal_unpack() after mp_decode_extl()
+    ok 142 - decimal_unpack() after mp_decode_extl() value
+    ok 143 - decimal_unpack() after mp_decode_extl() len
+    ok 144 - mp_sizeof_decimal(-0.0000000000000000000000000000000000001)
+    ok 145 - mp_sizeof_decimal(-0.0000000000000000000000000000000000001) == len(mp_encode_decimal(-0.0000000000000000000000000000000000001))
+    ok 146 - mp_next(mp_encode(-0.0000000000000000000000000000000000001))
+    ok 147 - mp_decode(mp_encode(-0.0000000000000000000000000000000000001) len
+    ok 148 - mp_decode(mp_encode(-0.0000000000000000000000000000000000001)) value
+    ok 149 - mp_decode(mp_encode(-0.0000000000000000000000000000000000001)) scale
+    ok 150 - str(mp_decode(mp_encode(-0.0000000000000000000000000000000000001))) == -0.0000000000000000000000000000000000001
+    ok 151 - mp_ext_type is MP_DECIMAL
+    ok 152 - decimal_unpack() after mp_decode_extl()
+    ok 153 - decimal_unpack() after mp_decode_extl() value
+    ok 154 - decimal_unpack() after mp_decode_extl() len
+    ok 155 - mp_sizeof_decimal(0.00000000000000000000000000000000000001)
+    ok 156 - mp_sizeof_decimal(0.00000000000000000000000000000000000001) == len(mp_encode_decimal(0.00000000000000000000000000000000000001))
+    ok 157 - mp_next(mp_encode(0.00000000000000000000000000000000000001))
+    ok 158 - mp_decode(mp_encode(0.00000000000000000000000000000000000001) len
+    ok 159 - mp_decode(mp_encode(0.00000000000000000000000000000000000001)) value
+    ok 160 - mp_decode(mp_encode(0.00000000000000000000000000000000000001)) scale
+    ok 161 - str(mp_decode(mp_encode(0.00000000000000000000000000000000000001))) == 0.00000000000000000000000000000000000001
+    ok 162 - mp_ext_type is MP_DECIMAL
+    ok 163 - decimal_unpack() after mp_decode_extl()
+    ok 164 - decimal_unpack() after mp_decode_extl() value
+    ok 165 - decimal_unpack() after mp_decode_extl() len
+    ok 166 - mp_sizeof_decimal(-0.00000000000000000000000000000000000001)
+    ok 167 - mp_sizeof_decimal(-0.00000000000000000000000000000000000001) == len(mp_encode_decimal(-0.00000000000000000000000000000000000001))
+    ok 168 - mp_next(mp_encode(-0.00000000000000000000000000000000000001))
+    ok 169 - mp_decode(mp_encode(-0.00000000000000000000000000000000000001) len
+    ok 170 - mp_decode(mp_encode(-0.00000000000000000000000000000000000001)) value
+    ok 171 - mp_decode(mp_encode(-0.00000000000000000000000000000000000001)) scale
+    ok 172 - str(mp_decode(mp_encode(-0.00000000000000000000000000000000000001))) == -0.00000000000000000000000000000000000001
+    ok 173 - mp_ext_type is MP_DECIMAL
+    ok 174 - decimal_unpack() after mp_decode_extl()
+    ok 175 - decimal_unpack() after mp_decode_extl() value
+    ok 176 - decimal_unpack() after mp_decode_extl() len
+    ok 177 - mp_sizeof_decimal(99999999999999999999999999999999999999)
+    ok 178 - mp_sizeof_decimal(99999999999999999999999999999999999999) == len(mp_encode_decimal(99999999999999999999999999999999999999))
+    ok 179 - mp_next(mp_encode(99999999999999999999999999999999999999))
+    ok 180 - mp_decode(mp_encode(99999999999999999999999999999999999999) len
+    ok 181 - mp_decode(mp_encode(99999999999999999999999999999999999999)) value
+    ok 182 - mp_decode(mp_encode(99999999999999999999999999999999999999)) scale
+    ok 183 - str(mp_decode(mp_encode(99999999999999999999999999999999999999))) == 99999999999999999999999999999999999999
+    ok 184 - mp_ext_type is MP_DECIMAL
+    ok 185 - decimal_unpack() after mp_decode_extl()
+    ok 186 - decimal_unpack() after mp_decode_extl() value
+    ok 187 - decimal_unpack() after mp_decode_extl() len
+    ok 188 - mp_sizeof_decimal(-99999999999999999999999999999999999999)
+    ok 189 - mp_sizeof_decimal(-99999999999999999999999999999999999999) == len(mp_encode_decimal(-99999999999999999999999999999999999999))
+    ok 190 - mp_next(mp_encode(-99999999999999999999999999999999999999))
+    ok 191 - mp_decode(mp_encode(-99999999999999999999999999999999999999) len
+    ok 192 - mp_decode(mp_encode(-99999999999999999999999999999999999999)) value
+    ok 193 - mp_decode(mp_encode(-99999999999999999999999999999999999999)) scale
+    ok 194 - str(mp_decode(mp_encode(-99999999999999999999999999999999999999))) == -99999999999999999999999999999999999999
+    ok 195 - mp_ext_type is MP_DECIMAL
+    ok 196 - decimal_unpack() after mp_decode_extl()
+    ok 197 - decimal_unpack() after mp_decode_extl() value
+    ok 198 - decimal_unpack() after mp_decode_extl() len
+ok 280 - subtests
diff --git a/third_party/lua-cjson/lua_cjson.c b/third_party/lua-cjson/lua_cjson.c
index 631c53e3f..f9f43f765 100644
--- a/third_party/lua-cjson/lua_cjson.c
+++ b/third_party/lua-cjson/lua_cjson.c
@@ -411,9 +411,15 @@ static void json_append_data(lua_State *l, struct luaL_serializer *cfg,
     json_append_array(l, cfg, current_depth, json, field.size);
     return;
     case MP_EXT:
-    /* handled by luaL_convertfield */
-    assert(false);
-    return;
+	switch (field.ext_type) {
+	case MP_DECIMAL:
+	{
+	    const char *str = decimal_to_string(field.decval);
+	    return json_append_string(cfg, json, str, strlen(str));
+	}
+	default:
+	    assert(false);
+	}
     }
 }
 
diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
index 46c98bde1..7485341fa 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,7 +694,14 @@ static int dump_node(struct lua_yaml_dumper *dumper)
       len = 4;
       break;
    case MP_EXT:
-      assert(0); /* checked by luaL_checkfield() */
+      switch (field.ext_type) {
+      case MP_DECIMAL:
+         str = decimal_to_string(field.decval);
+	 len = strlen(str);
+	 break;
+      default:
+	 assert(0); /* checked by luaL_checkfield() */
+      }
       break;
     }
 
-- 
2.20.1 (Apple Git-117)




More information about the Tarantool-patches mailing list