[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