From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id 191EB6EC41; Mon, 16 Aug 2021 18:58:03 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 191EB6EC41 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1629129483; bh=q6+5Hxz5i8zLxbFgdAN1pZVx/rBWEtQvKMUNbuNVn+E=; h=To:Cc:Date:In-Reply-To:References:Subject:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=lHFT3AYm0Spp14UK10TFX8rGZAFFRT8T54wdY1OK9oSQtxERf+mbPBAV1XTX+1KI5 AnS5FKFNyG0CuC7fXC+Ne3LLGHjrE6rU70mZ1u13Bj+5sZOYoCi6TQt8Ft0A/TXaKW 1g2KSCpSrYCpzUI3PAPMPnqquAFlgapr/kNcDycA= Received: from smtpng2.i.mail.ru (smtpng2.i.mail.ru [94.100.179.3]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 5B0806EC44 for ; Mon, 16 Aug 2021 18:57:03 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 5B0806EC44 Received: by smtpng2.m.smailru.net with esmtpa (envelope-from ) id 1mFez0-0003E4-BS; Mon, 16 Aug 2021 18:57:02 +0300 To: korablev@tarantool.org Cc: tarantool-patches@dev.tarantool.org Date: Mon, 16 Aug 2021 18:57:02 +0300 Message-Id: X-Mailer: git-send-email 2.25.1 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-7564579A: 78E4E2B564C1792B X-77F55803: 4F1203BC0FB41BD92087353F0EC44DD9ECFD080E047A606F56291F8739A291D6182A05F5380850407772849F2CF54437CC793CAE54333601A35FCB311FC8B2C9AE39BCA405E287B9 X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE77F3E0A4828C8B68EEA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F7900637963FCF4A2EF6E1A88638F802B75D45FF36EB9D2243A4F8B5A6FCA7DBDB1FC311F39EFFDF887939037866D6147AF826D8235CB5FCE0DBB07622DC4F7ED2C02B1F117882F4460429724CE54428C33FAD305F5C1EE8F4F765FCAA867293B0326636D2E47CDBA5A96583BD4B6F7A4D31EC0BC014FD901B82EE079FA2833FD35BB23D27C277FBC8AE2E8BF1175FABE1C0F9B6A471835C12D1D977C4224003CC8364762BB6847A3DEAEFB0F43C7A68FF6260569E8FC8737B5C2249EC8D19AE6D49635B68655334FD4449CB9ECD01F8117BC8BEAAAE862A0553A39223F8577A6DFFEA7CE31A2885C41F97C443847C11F186F3C59DAA53EE0834AAEE X-B7AD71C0: AC4F5C86D027EB782CDD5689AFBDA7A213B5FB47DCBC3458834459D11680B50566263B10DCBA0F9A1D21EDF8D01CF82D X-C1DE0DAB: C20DE7B7AB408E4181F030C43753B8186998911F362727C414F749A5E30D975C5B73F950BC6E7FFBD794532A70087EDA7255C0D34E867ABF9C2B6934AE262D3EE7EAB7254005DCED7532B743992DF240BDC6A1CF3F042BAD6DF99611D93F60EFE37876E7723AB534DC48ACC2A39D04F89CDFB48F4795C241BDAD6C7F3747799A X-C8649E89: 4E36BF7865823D7055A7F0CF078B5EC49A30900B95165D3455049D7B43D89D648A98D0C78E27264F9829F113F31FDA5D926A8D120FE26E3914ABB900E8C88A6F1D7E09C32AA3244C77C815A02EA4012E5CF0D176056B6481FE8DA44ABE2443F7729B2BEF169E0186 X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu530nj6fImhcD4MUrOEAnl0W826KZ9Q+tr5ycPtXkTV4k65bRjmOUUP8cvGozZ33TWg5HZplvhhXbhDGzqmQDTd6OAevLeAnq3Ra9uf7zvY2zzsIhlcp/Y7m53TZgf2aB4JOg4gkr2biojIrFL/N5KnVGjnXuJPkoQgA== X-Mailru-Sender: 689FA8AB762F7393C37E3C1AEC41BA5DFF6AE67DD30482B08F6CF0C2DA8321D783D72C36FC87018B9F80AB2734326CD2FB559BB5D741EB96352A0ABBE4FDA4210A04DAD6CC59E33667EA787935ED9F1B X-Mras: Ok Subject: [Tarantool-patches] [PATCH v1 2/4] sql: introduce field type decimal X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Mergen Imeev via Tarantool-patches Reply-To: imeevma@tarantool.org Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" 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