[Tarantool-patches] [PATCH v1 2/4] sql: introduce field type decimal

imeevma at tarantool.org imeevma at tarantool.org
Mon Aug 16 18:57:02 MSK 2021


This patch introduces a decimal field type. However, implicit and
explicit casts and arithmetic operations for this type will be presented
in next few patches. Literals also will be introduced later.

Part of #4415
---
 extra/mkkeywordhash.c                         |   2 +-
 src/box/sql/expr.c                            |   3 +
 src/box/sql/func.c                            |   4 +
 src/box/sql/mem.c                             | 173 +++++--
 src/box/sql/mem.h                             |  18 +-
 src/box/sql/parse.y                           |   1 +
 src/box/sql/sqlInt.h                          |   1 +
 test/sql-tap/CMakeLists.txt                   |   1 +
 test/sql-tap/decimal.c                        |  48 ++
 test/sql-tap/decimal.test.lua                 | 441 ++++++++++++++++++
 test/sql-tap/engine.cfg                       |   3 +
 .../gh-5913-segfault-on-select-uuid.test.lua  |  83 ----
 .../sql-tap/gh-6024-funcs-return-bin.test.lua |   8 +-
 13 files changed, 661 insertions(+), 125 deletions(-)
 create mode 100644 test/sql-tap/decimal.c
 create mode 100755 test/sql-tap/decimal.test.lua
 delete mode 100755 test/sql-tap/gh-5913-segfault-on-select-uuid.test.lua

diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
index 0d998506c..1c9d12295 100644
--- a/extra/mkkeywordhash.c
+++ b/extra/mkkeywordhash.c
@@ -196,7 +196,7 @@ static Keyword aKeywordTable[] = {
   { "CURRENT_TIMESTAMP",      "TK_STANDARD",    true  },
   { "DATE",                   "TK_STANDARD",    true  },
   { "DATETIME",               "TK_STANDARD",    true  },
-  { "DECIMAL",                "TK_STANDARD",    true  },
+  { "DECIMAL",                "TK_DECIMAL",     true  },
   { "DECLARE",                "TK_STANDARD",    true  },
   { "DENSE_RANK",             "TK_STANDARD",    true  },
   { "DESCRIBE",               "TK_STANDARD",    true  },
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index c67a7091c..275dbc5ba 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -387,6 +387,8 @@ sql_type_result(enum field_type lhs, enum field_type rhs)
 			return FIELD_TYPE_NUMBER;
 		if (lhs == FIELD_TYPE_DOUBLE || rhs == FIELD_TYPE_DOUBLE)
 			return FIELD_TYPE_DOUBLE;
+		if (lhs == FIELD_TYPE_DECIMAL || rhs == FIELD_TYPE_DECIMAL)
+			return FIELD_TYPE_DECIMAL;
 		if (lhs == FIELD_TYPE_INTEGER || rhs == FIELD_TYPE_INTEGER)
 			return FIELD_TYPE_INTEGER;
 		assert(lhs == FIELD_TYPE_UNSIGNED ||
@@ -2229,6 +2231,7 @@ sqlExprCanBeNull(const Expr * p)
 		op = p->op2;
 	switch (op) {
 	case TK_INTEGER:
+	case TK_DECIMAL:
 	case TK_STRING:
 	case TK_FLOAT:
 	case TK_BLOB:
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 1551d3ef2..e9572c56c 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -171,6 +171,9 @@ typeofFunc(sql_context * context, int NotUsed, sql_value ** argv)
 	case MEM_TYPE_UINT:
 		z = "integer";
 		break;
+	case MEM_TYPE_DEC:
+		z = "decimal";
+		break;
 	case MEM_TYPE_STR:
 		z = "string";
 		break;
@@ -1111,6 +1114,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 		sql_result_text(context, buf, UUID_STR_LEN, SQL_TRANSIENT);
 		break;
 	}
+	case MEM_TYPE_DEC:
 	case MEM_TYPE_UINT:
 	case MEM_TYPE_INT: {
 			sql_result_value(context, argv[0]);
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 066940fac..016f0e80b 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -42,6 +42,7 @@
 #include "lua/utils.h"
 #include "lua/serializer.h"
 #include "lua/msgpack.h"
+#include "lua/decimal.h"
 #include "uuid/mp_uuid.h"
 #include "mp_decimal.h"
 
@@ -86,6 +87,7 @@ mem_type_class(enum mem_type type)
 		return MEM_CLASS_NULL;
 	case MEM_TYPE_UINT:
 	case MEM_TYPE_INT:
+	case MEM_TYPE_DEC:
 	case MEM_TYPE_DOUBLE:
 		return MEM_CLASS_NUMBER;
 	case MEM_TYPE_STR:
@@ -107,6 +109,8 @@ mem_is_field_compatible(const struct Mem *mem, enum field_type type)
 {
 	if (mem->type == MEM_TYPE_UUID)
 		return (field_ext_type[type] & (1U << MP_UUID)) != 0;
+	if (mem->type == MEM_TYPE_DEC)
+		return (field_ext_type[type] & (1U << MP_DECIMAL)) != 0;
 	enum mp_type mp_type = mem_mp_type(mem);
 	assert(mp_type != MP_EXT);
 	return field_mp_plain_type_is_compatible(type, mp_type, true);
@@ -132,6 +136,9 @@ mem_str(const struct Mem *mem)
 	case MEM_TYPE_DOUBLE:
 		sql_snprintf(STR_VALUE_MAX_LEN, buf, "%!.15g", mem->u.r);
 		return tt_sprintf("%s(%s)", type, buf);
+	case MEM_TYPE_DEC:
+		decimal_to_string(&mem->u.d, buf);
+		return tt_sprintf("%s(%s)", type, buf);
 	case MEM_TYPE_BIN: {
 		int len = MIN(mem->n, STR_VALUE_MAX_LEN / 2);
 		for (int i = 0; i < len; ++i) {
@@ -172,6 +179,7 @@ mem_type_class_to_str(const struct Mem *mem)
 		return "NULL";
 	case MEM_TYPE_UINT:
 	case MEM_TYPE_INT:
+	case MEM_TYPE_DEC:
 	case MEM_TYPE_DOUBLE:
 		return "number";
 	case MEM_TYPE_STR:
@@ -284,6 +292,15 @@ mem_set_double(struct Mem *mem, double value)
 	mem->type = MEM_TYPE_DOUBLE;
 }
 
+void
+mem_set_dec(struct Mem *mem, decimal_t *d)
+{
+	mem_clear(mem);
+	mem->u.d = *d;
+	mem->type = MEM_TYPE_DEC;
+	assert(mem->flags == 0);
+}
+
 void
 mem_set_uuid(struct Mem *mem, const struct tt_uuid *uuid)
 {
@@ -1191,6 +1208,10 @@ mem_cast_explicit(struct Mem *mem, enum field_type type)
 		return -1;
 	case FIELD_TYPE_NUMBER:
 		return mem_to_number(mem);
+	case FIELD_TYPE_DECIMAL:
+		if (mem->type == MEM_TYPE_DEC)
+			return 0;
+		return -1;
 	case FIELD_TYPE_UUID:
 		if (mem->type == MEM_TYPE_UUID) {
 			mem->flags = 0;
@@ -1274,6 +1295,10 @@ mem_cast_implicit(struct Mem *mem, enum field_type type)
 			return -1;
 		mem->flags = MEM_Number;
 		return 0;
+	case FIELD_TYPE_DECIMAL:
+		if (mem->type == MEM_TYPE_DEC)
+			return 0;
+		return -1;
 	case FIELD_TYPE_MAP:
 		if (mem->type == MEM_TYPE_MAP)
 			return 0;
@@ -1595,12 +1620,12 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 static inline int
 check_types_numeric_arithmetic(const struct Mem *a, const struct Mem *b)
 {
-	if (!mem_is_num(a) || mem_is_metatype(a)) {
+	if (!mem_is_num(a) || mem_is_metatype(a) || a->type == MEM_TYPE_DEC) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(a),
 			 "integer, unsigned or double");
 		return -1;
 	}
-	if (!mem_is_num(b) || mem_is_metatype(b)) {
+	if (!mem_is_num(b) || mem_is_metatype(b) || b->type == MEM_TYPE_DEC) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(b),
 			 "integer, unsigned or double");
 		return -1;
@@ -1916,19 +1941,84 @@ mem_cmp_num(const struct Mem *a, const struct Mem *b)
 			return -1;
 		return 0;
 	}
-	if (a->type == MEM_TYPE_DOUBLE) {
-		if (b->type == MEM_TYPE_INT)
-			return double_compare_nint64(a->u.r, b->u.i, 1);
-		return double_compare_uint64(a->u.r, b->u.u, 1);
-	}
-	if (b->type == MEM_TYPE_DOUBLE) {
-		if (a->type == MEM_TYPE_INT)
+	if ((a->type & b->type & MEM_TYPE_DEC) != 0)
+		return decimal_compare(&a->u.d, &b->u.d);
+	switch (a->type) {
+	case MEM_TYPE_INT:
+		switch (b->type) {
+		case MEM_TYPE_UINT:
+			return -1;
+		case MEM_TYPE_DOUBLE:
 			return double_compare_nint64(b->u.r, a->u.i, -1);
-		return double_compare_uint64(b->u.r, a->u.u, -1);
+		case MEM_TYPE_DEC: {
+			decimal_t dec;
+			decimal_from_int64(&dec, a->u.i);
+			return decimal_compare(&dec, &b->u.d);
+		}
+		default:
+			unreachable();
+		}
+	case MEM_TYPE_UINT:
+		switch (b->type) {
+		case MEM_TYPE_INT:
+			return 1;
+		case MEM_TYPE_DOUBLE:
+			return double_compare_uint64(b->u.r, a->u.u, -1);
+		case MEM_TYPE_DEC: {
+			decimal_t dec;
+			decimal_from_uint64(&dec, a->u.u);
+			return decimal_compare(&dec, &b->u.d);
+		}
+		default:
+			unreachable();
+		}
+	case MEM_TYPE_DOUBLE:
+		switch (b->type) {
+		case MEM_TYPE_INT:
+			return double_compare_nint64(a->u.r, b->u.i, 1);
+		case MEM_TYPE_UINT:
+			return double_compare_uint64(a->u.r, b->u.u, 1);
+		case MEM_TYPE_DEC: {
+			if (a->u.r >= 1e38)
+				return 1;
+			if (a->u.r <= -1e38)
+				return -1;
+			decimal_t dec;
+			decimal_t *d = decimal_from_double(&dec, a->u.r);
+			assert(d != NULL && d == &dec);
+			return decimal_compare(d, &b->u.d);
+		}
+		default:
+			unreachable();
+		}
+	case MEM_TYPE_DEC:
+		switch (b->type) {
+		case MEM_TYPE_INT: {
+			decimal_t dec;
+			decimal_from_int64(&dec, b->u.i);
+			return decimal_compare(&a->u.d, &dec);
+		}
+		case MEM_TYPE_UINT: {
+			decimal_t dec;
+			decimal_from_uint64(&dec, b->u.u);
+			return decimal_compare(&a->u.d, &dec);
+		}
+		case MEM_TYPE_DOUBLE: {
+			if (b->u.r >= 1e38)
+				return 1;
+			if (b->u.r <= -1e38)
+				return -1;
+			decimal_t dec;
+			decimal_t *d = decimal_from_double(&dec, b->u.r);
+			assert(d != NULL && d == &dec);
+			return decimal_compare(&a->u.d, d);
+		}
+		default:
+			unreachable();
+		}
+	default:
+		unreachable();
 	}
-	if (a->type == MEM_TYPE_INT)
-		return -1;
-	assert(a->type == MEM_TYPE_UINT && b->type == MEM_TYPE_INT);
 	return 1;
 }
 
@@ -2035,6 +2125,11 @@ mem_cmp_msgpack(const struct Mem *a, const char **b, int *result,
 			if (uuid_unpack(b, len, &mem.u.uuid) == NULL)
 				return -1;
 			break;
+		} else if (type == MP_DECIMAL) {
+			mem.type = MEM_TYPE_DEC;
+			if (decimal_unpack(b, len, &mem.u.d) == 0)
+				return -1;
+			break;
 		}
 		*b += len;
 		mem.type = MEM_TYPE_BIN;
@@ -2121,6 +2216,8 @@ mem_type_to_str(const struct Mem *p)
 		return "boolean";
 	case MEM_TYPE_UUID:
 		return "uuid";
+	case MEM_TYPE_DEC:
+		return "decimal";
 	default:
 		unreachable();
 	}
@@ -2149,6 +2246,7 @@ mem_mp_type(const struct Mem *mem)
 		return MP_BOOL;
 	case MEM_TYPE_DOUBLE:
 		return MP_DOUBLE;
+	case MEM_TYPE_DEC:
 	case MEM_TYPE_UUID:
 		return MP_EXT;
 	default:
@@ -2319,6 +2417,9 @@ memTracePrint(Mem *p)
 	case MEM_TYPE_UUID:
 		printf(" uuid:%s", tt_uuid_str(&p->u.uuid));
 		return;
+	case MEM_TYPE_DEC:
+		printf(" decimal:%s", decimal_str(&p->u.d));
+		return;
 	default: {
 		char zBuf[200];
 		sqlVdbeMemPrettyPrint(p, zBuf);
@@ -2583,6 +2684,13 @@ mem_from_mp_ephemeral(struct Mem *mem, const char *buf, uint32_t *len)
 			mem->type = MEM_TYPE_UUID;
 			mem->flags = 0;
 			break;
+		} else if (type == MP_DECIMAL) {
+			buf = svp;
+			if (mp_decode_decimal(&buf, &mem->u.d) == NULL)
+				return -1;
+			mem->type = MEM_TYPE_DEC;
+			mem->flags = 0;
+			break;
 		}
 		buf += size;
 		mem->z = (char *)svp;
@@ -2715,6 +2823,9 @@ mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var)
 	case MEM_TYPE_UUID:
 		mpstream_encode_uuid(stream, &var->u.uuid);
 		return;
+	case MEM_TYPE_DEC:
+		mpstream_encode_decimal(stream, &var->u.d);
+		return;
 	default:
 		unreachable();
 	}
@@ -2804,6 +2915,9 @@ port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
 		case MEM_TYPE_UUID:
 			*luaL_pushuuid(L) = mem->u.uuid;
 			break;
+		case MEM_TYPE_DEC:
+			*lua_pushdecimal(L) = mem->u.d;
+			break;
 		default:
 			unreachable();
 		}
@@ -2926,26 +3040,10 @@ port_lua_get_vdbemem(struct port *base, uint32_t *size)
 		case MP_EXT: {
 			assert(field.ext_type == MP_UUID ||
 			       field.ext_type == MP_DECIMAL);
-			char *buf;
-			uint32_t size;
-			uint32_t svp = region_used(&fiber()->gc);
-			if (field.ext_type == MP_UUID) {
+			if (field.ext_type == MP_UUID)
 				mem_set_uuid(&val[i], field.uuidval);
-				break;
-			} else {
-				size = mp_sizeof_decimal(field.decval);
-				buf = region_alloc(&fiber()->gc, size);
-				if (buf == NULL) {
-					diag_set(OutOfMemory, size,
-						 "region_alloc", "buf");
-					goto error;
-				}
-				mp_encode_decimal(buf, field.decval);
-			}
-			int rc = mem_copy_bin(&val[i], buf, size);
-			region_truncate(&fiber()->gc, svp);
-			if (rc != 0)
-				goto error;
+			else
+				mem_set_dec(&val[i], field.decval);
 			break;
 		}
 		case MP_NIL:
@@ -3049,6 +3147,17 @@ port_c_get_vdbemem(struct port *base, uint32_t *size)
 				}
 				val[i].type = MEM_TYPE_UUID;
 				break;
+			} else if (type == MP_DECIMAL) {
+				decimal_t *d = &val[i].u.d;
+				data = str;
+				if (mp_decode_decimal(&data, d) == NULL) {
+					diag_set(ClientError,
+						 ER_INVALID_MSGPACK, "Invalid "
+						 "MP_DECIMAL MsgPack format");
+					goto error;
+				}
+				val[i].type = MEM_TYPE_DEC;
+				break;
 			}
 			data += len;
 			if (mem_copy_bin(&val[i], str, data - str) != 0)
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 5f004646e..543944b80 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -31,6 +31,7 @@
  */
 #include "box/field_def.h"
 #include "uuid/tt_uuid.h"
+#include "decimal.h"
 
 struct sql;
 struct Vdbe;
@@ -49,10 +50,11 @@ enum mem_type {
 	MEM_TYPE_BOOL		= 1 << 7,
 	MEM_TYPE_DOUBLE		= 1 << 8,
 	MEM_TYPE_UUID		= 1 << 9,
-	MEM_TYPE_INVALID	= 1 << 10,
-	MEM_TYPE_FRAME		= 1 << 11,
-	MEM_TYPE_PTR		= 1 << 12,
-	MEM_TYPE_AGG		= 1 << 13,
+	MEM_TYPE_DEC		= 1 << 10,
+	MEM_TYPE_INVALID	= 1 << 11,
+	MEM_TYPE_FRAME		= 1 << 12,
+	MEM_TYPE_PTR		= 1 << 13,
+	MEM_TYPE_AGG		= 1 << 14,
 };
 
 /*
@@ -75,6 +77,7 @@ struct Mem {
 		struct func *func;
 		struct VdbeFrame *pFrame;	/* Used when flags==MEM_Frame */
 		struct tt_uuid uuid;
+		decimal_t d;
 	} u;
 	/** Type of the value this MEM contains. */
 	enum mem_type type;
@@ -138,7 +141,8 @@ static inline bool
 mem_is_num(const struct Mem *mem)
 {
 	enum mem_type type = mem->type;
-	return (type & (MEM_TYPE_UINT | MEM_TYPE_INT | MEM_TYPE_DOUBLE)) != 0;
+	return (type & (MEM_TYPE_UINT | MEM_TYPE_INT | MEM_TYPE_DOUBLE |
+			MEM_TYPE_DEC)) != 0;
 }
 
 static inline bool
@@ -306,6 +310,10 @@ mem_set_double(struct Mem *mem, double value);
 void
 mem_set_uuid(struct Mem *mem, const struct tt_uuid *uuid);
 
+/** Clear MEM and set it to DECIMAL. */
+void
+mem_set_dec(struct Mem *mem, decimal_t *dec);
+
 /** Clear MEM and set it to STRING. The string belongs to another object. */
 void
 mem_set_str_ephemeral(struct Mem *mem, char *value, uint32_t len);
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index bd041e862..436c98cd9 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -1863,6 +1863,7 @@ number_typedef(A) ::= NUMBER . { A.type = FIELD_TYPE_NUMBER; }
 number_typedef(A) ::= DOUBLE . { A.type = FIELD_TYPE_DOUBLE; }
 number_typedef(A) ::= INT|INTEGER_KW . { A.type = FIELD_TYPE_INTEGER; }
 number_typedef(A) ::= UNSIGNED . { A.type = FIELD_TYPE_UNSIGNED; }
+number_typedef(A) ::= DECIMAL . { A.type = FIELD_TYPE_DECIMAL; }
 
 /**
  * NUMERIC type is temporary disabled. To be enabled when
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 60fa1678d..e893cccc0 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -1236,6 +1236,7 @@ enum trim_side_mask {
 #define sql_type_is_numeric(X)  ((X) == FIELD_TYPE_INTEGER || \
 				 (X) == FIELD_TYPE_NUMBER || \
 				 (X) == FIELD_TYPE_UNSIGNED || \
+				 (X) == FIELD_TYPE_DECIMAL || \
 				 (X) == FIELD_TYPE_DOUBLE)
 
 /*
diff --git a/test/sql-tap/CMakeLists.txt b/test/sql-tap/CMakeLists.txt
index bd2b9f33f..87f23b2f7 100644
--- a/test/sql-tap/CMakeLists.txt
+++ b/test/sql-tap/CMakeLists.txt
@@ -2,3 +2,4 @@ include_directories(${MSGPUCK_INCLUDE_DIRS})
 build_module(gh-5938-wrong-string-length gh-5938-wrong-string-length.c)
 build_module(gh-6024-funcs-return-bin gh-6024-funcs-return-bin.c)
 build_module(sql_uuid sql_uuid.c)
+build_module(decimal decimal.c)
diff --git a/test/sql-tap/decimal.c b/test/sql-tap/decimal.c
new file mode 100644
index 000000000..4d9d1ce19
--- /dev/null
+++ b/test/sql-tap/decimal.c
@@ -0,0 +1,48 @@
+#include "msgpuck.h"
+#include "module.h"
+#include "mp_decimal.h"
+#include "mp_extension_types.h"
+
+enum {
+	BUF_SIZE = 512,
+};
+
+int
+is_dec(box_function_ctx_t *ctx, const char *args, const char *args_end)
+{
+	(void)args_end;
+	uint32_t arg_count = mp_decode_array(&args);
+	if (arg_count != 1) {
+		return box_error_set(__FILE__, __LINE__, ER_PROC_C,
+				     "invalid argument count");
+	}
+	bool is_uuid;
+	if (mp_typeof(*args) == MP_EXT) {
+		const char *str = args;
+		int8_t type;
+		mp_decode_extl(&str, &type);
+		is_uuid = type == MP_DECIMAL;
+	} else {
+		is_uuid = false;
+	}
+
+	char res[BUF_SIZE];
+	memset(res, '\0', BUF_SIZE);
+	char *end = mp_encode_bool(res, is_uuid);
+	box_return_mp(ctx, res, end);
+	return 0;
+}
+
+int
+ret_dec(box_function_ctx_t *ctx, const char *args, const char *args_end)
+{
+	(void)args;
+	(void)args_end;
+	decimal_t dec;
+	decimal_from_string(&dec, "111");
+	char res[BUF_SIZE];
+	memset(res, '\0', BUF_SIZE);
+	char *end = mp_encode_decimal(res, &dec);
+	box_return_mp(ctx, res, end);
+	return 0;
+}
diff --git a/test/sql-tap/decimal.test.lua b/test/sql-tap/decimal.test.lua
new file mode 100755
index 000000000..dd69ca370
--- /dev/null
+++ b/test/sql-tap/decimal.test.lua
@@ -0,0 +1,441 @@
+#!/usr/bin/env tarantool
+local build_path = os.getenv("BUILDDIR")
+package.cpath = build_path..'/test/sql-tap/?.so;'..build_path..'/test/sql-tap/?.dylib;'..package.cpath
+
+local test = require("sqltester")
+test:plan(43)
+
+local dec = require("decimal")
+local dec1 = dec.new("111")
+local dec2 = dec.new("55555")
+local dec3 = dec.new("3333")
+
+-- Check that it is possible to create spaces with DECIMAL field.
+test:do_execsql_test(
+    "dec-1",
+    [[
+        CREATE TABLE t1 (i INT PRIMARY KEY, u DECIMAL);
+        CREATE TABLE t2 (u DECIMAL PRIMARY KEY);
+    ]], {
+    })
+
+box.space.T1:insert({1, dec1})
+box.space.T1:insert({2, dec2})
+box.space.T1:insert({3, dec3})
+box.space.T1:insert({4, dec1})
+box.space.T1:insert({5, dec1})
+box.space.T1:insert({6, dec2})
+box.space.T2:insert({dec1})
+box.space.T2:insert({dec2})
+box.space.T2:insert({dec3})
+
+-- Check that SELECT can work with DECIMAL.
+test:do_execsql_test(
+    "dec-2.1.1",
+    [[
+        SELECT * FROM t1;
+    ]], {
+        1, dec1, 2, dec2, 3, dec3, 4, dec1, 5, dec1, 6, dec2
+    })
+
+test:do_execsql_test(
+    "dec-2.1.2",
+    [[
+        SELECT * FROM t2;
+    ]], {
+        dec1, dec3, dec2
+    })
+
+-- Check that ORDER BY can work with DECIMAL.
+test:do_execsql_test(
+    "dec-2.2.1",
+    [[
+        SELECT * FROM t1 ORDER BY u;
+    ]], {
+        1, dec1, 4, dec1, 5, dec1, 3, dec3, 2, dec2, 6, dec2
+    })
+
+test:do_execsql_test(
+    "dec-2.2.2",
+    [[
+        SELECT * FROM t1 ORDER BY u DESC;
+    ]], {
+        2, dec2, 6, dec2, 3, dec3, 1, dec1, 4, dec1, 5, dec1
+    })
+
+test:do_execsql_test(
+    "dec-2.2.3",
+    [[
+        SELECT * FROM t2 ORDER BY u;
+    ]], {
+        dec1, dec3, dec2
+    })
+
+test:do_execsql_test(
+    "dec-2.2.4",
+    [[
+        SELECT * FROM t2 ORDER BY u DESC;
+    ]], {
+        dec2, dec3, dec1
+    })
+
+-- Check that GROUP BY can work with DECIMAL.
+test:do_execsql_test(
+    "dec-2.3.1",
+    [[
+        SELECT count(*), u FROM t1 GROUP BY u;
+    ]], {
+        3, dec1, 1, dec3, 2, dec2
+    })
+
+test:do_execsql_test(
+    "dec-2.3.2",
+    [[
+        SELECT count(*), u FROM t2 GROUP BY u;
+    ]], {
+        1, dec1, 1, dec3, 1, dec2
+    })
+
+-- Check that subselects can work with DECIMAL.
+test:do_execsql_test(
+    "dec-2.4",
+    [[
+        SELECT * FROM (SELECT * FROM (SELECT * FROM t2 LIMIT 2) LIMIT 2 OFFSET 1);
+    ]], {
+        dec3
+    })
+
+-- Check that DISTINCT can work with DECIMAL.
+test:do_execsql_test(
+    "dec-2.5",
+    [[
+        SELECT DISTINCT u FROM t1;
+    ]], {
+        dec1, dec2, dec3
+    })
+
+-- Check that VIEW can work with DECIMAL.
+test:do_execsql_test(
+    "dec-2.6",
+    [[
+        CREATE VIEW v AS SELECT u FROM t1;
+        SELECT * FROM v;
+    ]], {
+        dec1, dec2, dec3, dec1, dec1, dec2
+    })
+
+test:execsql([[
+    CREATE TABLE t3 (s SCALAR PRIMARY KEY);
+]])
+
+-- Check that SCALAR field can contain DECIMAL and use it in index.
+test:do_execsql_test(
+    "dec-3",
+    [[
+        INSERT INTO t3 SELECT u FROM t2;
+        SELECT s, typeof(s) FROM t3;
+    ]], {
+        dec1, "scalar", dec3, "scalar", dec2, "scalar"
+    })
+
+-- Check that ephemeral space can work with DECIMAL.
+test:do_execsql_test(
+    "dec-4",
+    [[
+        EXPLAIN SELECT * from (VALUES(1)), t2;
+    ]], {
+        "/OpenTEphemeral/"
+    })
+
+test:execsql([[
+    CREATE TABLE t5f (u DECIMAL PRIMARY KEY, f DECIMAL REFERENCES t5f(u));
+    CREATE TABLE t5c (i INT PRIMARY KEY, f DECIMAL,
+                      CONSTRAINT ck CHECK(f != 111));
+    CREATE TABLE t5u (i INT PRIMARY KEY, f DECIMAL UNIQUE);
+]])
+
+-- Check that FOREIGN KEY constraint can work with DECIMAL.
+test:do_catchsql_test(
+    "dec-5.1.1",
+    [[
+        INSERT INTO t5f SELECT (SELECT u from t2 LIMIT 1 OFFSET 1), (SELECT u from t2 LIMIT 1);
+    ]], {
+        1, "Failed to execute SQL statement: FOREIGN KEY constraint failed"
+    })
+
+test:do_execsql_test(
+    "dec-5.1.2",
+    [[
+        INSERT INTO t5f SELECT u, u from t2 LIMIT 1;
+        SELECT * from t5f;
+    ]], {
+        dec1, dec1
+    })
+
+test:do_execsql_test(
+    "dec-5.1.3",
+    [[
+        INSERT INTO t5f SELECT (SELECT u from t2 LIMIT 1 OFFSET 1), (SELECT u from t2 LIMIT 1);
+        SELECT * from t5f;
+    ]], {
+        dec1, dec1, dec3, dec1
+    })
+
+-- Check that CHECK constraint can work with DECIMAL.
+test:do_catchsql_test(
+    "dec-5.2.1",
+    [[
+        INSERT INTO t5c SELECT 1, u FROM t2 LIMIT 1;
+    ]], {
+        1, "Check constraint failed 'CK': f != 111"
+    })
+
+test:do_execsql_test(
+    "dec-5.2.2",
+    [[
+        INSERT INTO t5c SELECT 2, u FROM t2 LIMIT 1 OFFSET 1;
+        SELECT * from t5c;
+    ]], {
+        2, dec3
+    })
+
+-- Check that UNIQUE constraint can work with DECIMAL.
+test:do_execsql_test(
+    "dec-5.3.1",
+    [[
+        INSERT INTO t5u SELECT 1, u FROM t2 LIMIT 1;
+        SELECT * from t5u;
+    ]], {
+        1, dec1
+    })
+
+test:do_catchsql_test(
+    "dec-5.3.2",
+    [[
+        INSERT INTO t5u SELECT 2, u FROM t2 LIMIT 1;
+    ]], {
+        1, 'Duplicate key exists in unique index "unique_unnamed_T5U_2" in '..
+           'space "T5U" with old tuple - [1, 111] and new tuple - [2, 111]'
+    })
+
+local func = {language = 'Lua', body = 'function(x) return type(x) end',
+              returns = 'string', param_list = {'any'}, exports = {'SQL'}}
+box.schema.func.create('RETURN_TYPE', func);
+
+-- Check that Lua user-defined functions can accept DECIMAL.
+test:do_execsql_test(
+    "dec-6.1",
+    [[
+        SELECT RETURN_TYPE(u) FROM t2;
+    ]], {
+        "cdata", "cdata", "cdata"
+    })
+
+func = {language = 'Lua', returns = 'decimal', param_list = {}, exports = {'SQL'},
+        body = 'function(x) return require("decimal").new("111") end'}
+box.schema.func.create('GET_DEC', func);
+
+-- Check that Lua user-defined functions can return DECIMAL.
+test:do_execsql_test(
+    "dec-6.2",
+    [[
+        SELECT GET_DEC();
+    ]], {
+        dec1
+    })
+
+func = {language = 'C', returns = 'boolean', param_list = {'any'}, exports = {'SQL'}}
+box.schema.func.create("decimal.is_dec", func)
+
+-- Check that C user-defined functions can accept DECIMAL.
+test:do_execsql_test(
+    "dec-6.3",
+    [[
+        SELECT "decimal.is_dec"(i), "decimal.is_dec"(u) FROM t1 LIMIT 1;
+    ]], {
+        false, true
+    })
+
+func = {language = 'C', returns = 'decimal', param_list = {}, exports = {'SQL'}}
+box.schema.func.create("decimal.ret_dec", func)
+
+-- Check that C user-defined functions can return DECIMAL.
+test:do_execsql_test(
+    "dec-6.4",
+    [[
+        SELECT "decimal.ret_dec"();
+    ]], {
+        dec1
+    })
+
+test:execsql([[
+    CREATE TABLE t7 (i INT PRIMARY KEY AUTOINCREMENT, u DECIMAL);
+    CREATE TABLE t7t (u DECIMAL PRIMARY KEY);
+    CREATE TRIGGER t AFTER INSERT ON t7 FOR EACH ROW BEGIN INSERT INTO t7t SELECT new.u; END;
+]])
+
+-- Check that trigger can work with DECIMAL.
+test:do_execsql_test(
+    "dec-7",
+    [[
+        INSERT INTO t7(u) SELECT * FROM t2;
+        SELECT * FROM t7t;
+    ]], {
+        dec1, dec3, dec2
+    })
+
+-- Check that JOIN by DECIMAL field works.
+test:do_execsql_test(
+    "dec-8.1",
+    [[
+        SELECT * FROM t1 JOIN t2 on t1.u = t2.u;
+    ]], {
+        1, dec1, dec1, 2, dec2, dec2, 3, dec3, dec3,
+        4, dec1, dec1, 5, dec1, dec1, 6, dec2, dec2
+    })
+
+test:do_execsql_test(
+    "dec-8.2",
+    [[
+        SELECT * FROM t1 LEFT JOIN t2 on t1.u = t2.u;
+    ]], {
+        1, dec1, dec1, 2, dec2, dec2, 3, dec3, dec3,
+        4, dec1, dec1, 5, dec1, dec1, 6, dec2, dec2
+    })
+
+test:do_execsql_test(
+    "dec-8.3",
+    [[
+        SELECT * FROM t1 INNER JOIN t2 on t1.u = t2.u;
+    ]], {
+        1, dec1, dec1, 2, dec2, dec2, 3, dec3, dec3,
+        4, dec1, dec1, 5, dec1, dec1, 6, dec2, dec2
+    })
+
+-- Check that comparison with DECIMAL works as intended.
+test:do_execsql_test(
+    "dec-9.1.1",
+    [[
+        SELECT u > 1 FROM t2;
+    ]], {
+        true, true, true
+    })
+
+test:do_catchsql_test(
+    "dec-9.1.2",
+    [[
+        SELECT u > CAST('11111111-1111-1111-1111-111111111111' AS UUID) FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to number"
+    })
+
+test:do_catchsql_test(
+    "dec-9.1.3",
+    [[
+        SELECT u > '1' FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert string('1') to number"
+    })
+
+test:do_execsql_test(
+    "dec-9.1.4",
+    [[
+        SELECT u > 1.5 FROM t2;
+    ]], {
+        true, true, true
+    })
+
+test:do_execsql_test(
+    "dec-9.1.5",
+    [[
+        SELECT u > -1 FROM t2;
+    ]], {
+        true, true, true
+    })
+
+test:do_catchsql_test(
+    "dec-9.1.6",
+    [[
+        SELECT u > true FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert boolean(TRUE) to number"
+    })
+
+test:do_catchsql_test(
+    "dec-9.1.7",
+    [[
+        SELECT u > x'31' FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert varbinary(x'31') to number"
+    })
+
+test:do_execsql_test(
+    "dec-9.2.1",
+    [[
+        SELECT u = 1 FROM t2;
+    ]], {
+        false, false, false
+    })
+
+test:do_catchsql_test(
+    "dec-9.2.2",
+    [[
+        SELECT u = CAST('11111111-1111-1111-1111-111111111111' AS UUID) FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to number"
+    })
+
+test:do_catchsql_test(
+    "dec-9.2.3",
+    [[
+        SELECT u = '1' FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert string('1') to number"
+    })
+
+test:do_execsql_test(
+    "dec-9.2.4",
+    [[
+        SELECT u = 1.5 FROM t2;
+    ]], {
+        false, false, false
+    })
+
+test:do_execsql_test(
+    "dec-9.2.5",
+    [[
+        SELECT u = -1 FROM t2;
+    ]], {
+        false, false, false
+    })
+
+test:do_catchsql_test(
+    "dec-9.2.6",
+    [[
+        SELECT u = true FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert boolean(TRUE) to number"
+    })
+
+test:do_catchsql_test(
+    "dec-9.2.7",
+    [[
+        SELECT u = x'31' FROM t2;
+    ]], {
+        1, "Type mismatch: can not convert varbinary(x'31') to number"
+    })
+
+test:execsql([[
+    DROP TRIGGER t;
+    DROP VIEW v;
+    DROP TABLE t7t;
+    DROP TABLE t7;
+    DROP TABLE t5u;
+    DROP TABLE t5c;
+    DROP TABLE t5f;
+    DROP TABLE t3;
+    DROP TABLE t2;
+    DROP TABLE t1;
+]])
+
+test:finish_test()
diff --git a/test/sql-tap/engine.cfg b/test/sql-tap/engine.cfg
index 820c72b00..511d0a716 100644
--- a/test/sql-tap/engine.cfg
+++ b/test/sql-tap/engine.cfg
@@ -26,6 +26,9 @@
     "metatypes.test.lua": {
         "memtx": {"engine": "memtx"}
     },
+    "decimal.test.lua": {
+        "memtx": {"engine": "memtx"}
+    },
     "gh-4077-iproto-execute-no-bind.test.lua": {},
     "*": {
         "memtx": {"engine": "memtx"},
diff --git a/test/sql-tap/gh-5913-segfault-on-select-uuid.test.lua b/test/sql-tap/gh-5913-segfault-on-select-uuid.test.lua
deleted file mode 100755
index 8847fede4..000000000
--- a/test/sql-tap/gh-5913-segfault-on-select-uuid.test.lua
+++ /dev/null
@@ -1,83 +0,0 @@
-#!/usr/bin/env tarantool
-local test = require("sqltester")
-test:plan(4)
-
-local uuid = require("uuid").fromstr("11111111-1111-1111-1111-111111111111")
-local decimal = require("decimal").new(111.111)
-
-box.schema.create_space('T')
-box.space.T:format({{name = "I", type = "integer"}, {name = "U", type = "uuid"},
-                    {name = "D", type = "decimal"}})
-box.space.T:create_index("primary")
-box.space.T:insert({1, uuid, decimal})
-
---
--- Make sure that there is no segmentation fault on select from field that
--- contains UUID or DECIMAL. Currently SQL does not support DECIMAL, so it is
--- treated as VARBINARY.
---
-test:do_execsql_test(
-    "gh-5913-1",
-    [[
-        SELECT i, u, d FROM t;
-        SELECT i, u from t;
-    ]], {
-        1, uuid
-    })
-
-box.schema.create_space('T1')
-box.space.T1:format({{name = "I", type = "integer"},
-                     {name = "U", type = "uuid", is_nullable = true},
-                     {name = "D", type = "decimal", is_nullable = true}})
-box.space.T1:create_index("primary")
-
---
--- Since SQL does not support DECIMAL and it is treated as VARBINARY, it cannot
--- be inserted from SQL.
---
-test:do_catchsql_test(
-    "gh-5913-2",
-    [[
-        INSERT INTO t1 SELECT i, NULL, d FROM t;
-    ]], {
-        1, "Type mismatch: can not convert varbinary(x'C70501030111111C') to decimal"
-    })
-
---
--- Still, if DECIMAL fields does not selected directly, insert is working
--- properly in case the space which receives these values is empty.
---
-test:do_execsql_test(
-    "gh-5913-3",
-    [[
-        INSERT INTO t1 SELECT * FROM t;
-        SELECT count() FROM t1;
-    ]], {
-        1
-    })
-
-box.schema.create_space('TU')
-box.space.TU:format({{name = "I", type = "integer"},
-                     {name = "U", type = "uuid"}})
-box.space.TU:create_index("primary")
-box.space.TU:insert({1, uuid})
-
-box.schema.create_space('TD')
-box.space.TD:format({{name = "I", type = "integer"},
-                     {name = "D", type = "decimal"}})
-box.space.TD:create_index("primary")
-box.space.TD:insert({1, decimal})
-
---
--- Update of DECIMAL also does not lead to segfault, however throws an error
--- since after changing value cannot be inserted into the field from SQL.
---
-test:do_catchsql_test(
-    "gh-5913-4",
-    [[
-        UPDATE td SET d = d;
-    ]], {
-        1, "Type mismatch: can not convert varbinary(x'C70501030111111C') to decimal"
-    })
-
-test:finish_test()
diff --git a/test/sql-tap/gh-6024-funcs-return-bin.test.lua b/test/sql-tap/gh-6024-funcs-return-bin.test.lua
index 79464afd1..6fd009261 100755
--- a/test/sql-tap/gh-6024-funcs-return-bin.test.lua
+++ b/test/sql-tap/gh-6024-funcs-return-bin.test.lua
@@ -38,7 +38,7 @@ test:do_execsql_test(
 box.schema.func.create("gh-6024-funcs-return-bin.ret_decimal", {
     language = "C",
     param_list = {},
-    returns = "varbinary",
+    returns = "decimal",
     exports = {"SQL"},
 })
 
@@ -47,7 +47,7 @@ test:do_execsql_test(
     [[
         SELECT typeof("gh-6024-funcs-return-bin.ret_decimal"());
     ]], {
-        "varbinary"
+        "decimal"
     })
 
 box.schema.func.create("get_uuid", {
@@ -69,7 +69,7 @@ test:do_execsql_test(
 box.schema.func.create("get_decimal", {
     language = "LUA",
     param_list = {},
-    returns = "varbinary",
+    returns = "decimal",
     body = "function(x) return require('decimal').new('9999999999999999999.9999999999999999999') end",
     exports = {"SQL"},
 })
@@ -79,7 +79,7 @@ test:do_execsql_test(
     [[
         SELECT typeof("get_decimal"()), "get_decimal"() == "gh-6024-funcs-return-bin.ret_decimal"();
     ]], {
-        "varbinary", true
+        "decimal", true
     })
 
 box.schema.func.drop("gh-6024-funcs-return-bin.ret_bin")
-- 
2.25.1



More information about the Tarantool-patches mailing list