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 86ACB70152; Thu, 2 Dec 2021 11:38:57 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 86ACB70152 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1638434337; bh=Hwmpw/3xyk039dtoQIgiO0L+SJK024077MvR5Yxgs/0=; h=Date:To:Cc:References:In-Reply-To:Subject:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=HoNXJA8dUTBmOy9kz7iBUleviLYNdjfM9ufj+HqeFrdlEp/9MsyDT4d1VSmZfpu+a DFjqEFBbIxQzwrwoz5vXnGkik7WPknkiRb63qvteHyani/zM4eETYELTytoBKW1Sjr exPqqmK8ni9VN9LfQbCSsmy8AECC4cL8AJONOqlE= Received: from smtpng1.i.mail.ru (smtpng1.i.mail.ru [94.100.181.251]) (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 EDDE670152 for ; Thu, 2 Dec 2021 11:38:53 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org EDDE670152 Received: by smtpng1.m.smailru.net with esmtpa (envelope-from ) id 1mshcD-0003Ig-4V; Thu, 02 Dec 2021 11:38:53 +0300 Date: Thu, 2 Dec 2021 11:38:51 +0300 To: Vladislav Shpilevoy Cc: tarantool-patches@dev.tarantool.org Message-ID: <20211202083851.GB8207@tarantool.org> References: <4ecfb3439688bef76c96270624410dee8822176f.1637244389.git.imeevma@gmail.com> <662f6b87-f085-ab10-53b8-d087d9598b19@tarantool.org> <20211125085529.GB56448@tarantool.org> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline In-Reply-To: X-4EC0790: 10 X-7564579A: EEAE043A70213CC8 X-77F55803: 4F1203BC0FB41BD97D497884E4742A3C1997752B3D83DC12832A162FABB2E90D182A05F5380850404C228DA9ACA6FE27B7AE0EB2E3A0619CFCD5189B0D43627F1F240F6B18FFCD37693026DF2A7501CC X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE7C579B1C3ABE6C709C2099A533E45F2D0395957E7521B51C2CFCAF695D4D8E9FCEA1F7E6F0F101C6778DA827A17800CE73870E1FF9A049D91EA1F7E6F0F101C6723150C8DA25C47586E58E00D9D99D84E1BDDB23E98D2D38BBCA57AF85F7723F282D7F8D572B1250876506F63A952F82CCC7F00164DA146DAFE8445B8C89999728AA50765F7900637F6B57BC7E64490618DEB871D839B7333395957E7521B51C2DFABB839C843B9C08941B15DA834481F8AA50765F7900637F6B57BC7E6449061A352F6E88A58FB86F5D81C698A659EA7E827F84554CEF5019E625A9149C048EE9ECD01F8117BC8BEE2021AF6380DFAD18AA50765F790063735872C767BF85DA227C277FBC8AE2E8B9F5955FECEF5819E75ECD9A6C639B01B4E70A05D1297E1BBCB5012B2E24CD356 X-C1DE0DAB: 0D63561A33F958A535DCAE6850DE62D86DEC06CAAC28C9ED8364523193510F89D59269BC5F550898D99A6476B3ADF6B47008B74DF8BB9EF7333BD3B22AA88B938A852937E12ACA759F66ED85EB5F25FD410CA545F18667F91A7EA1CDA0B5A7A0 X-C8649E89: 4E36BF7865823D7055A7F0CF078B5EC49A30900B95165D342B94C1DAF75C4D2292CAF519D077D99D1C503F8972F5EB79C43E19EB1ECB0198FF10FBCEC43E2C481D7E09C32AA3244C1C2E0F625B71DA288F3E38A835161D9AF2F5F14F68F1805B729B2BEF169E0186 X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu530nj6fImhcD4MUrOEAnl0W826KZ9Q+tr5ycPtXkTV4k65bRjmOUUP8cvGozZ33TWg5HZplvhhXbhDGzqmQDTd6OAevLeAnq3Ra9uf7zvY2zzsIhlcp/Y7m53TZgf2aB4JOg4gkr2biojMMFVqzO9sR2LhQTKVBjfqA== X-Mailru-Sender: 689FA8AB762F7393C37E3C1AEC41BA5DA3D1DD0DBC0F2AFA4C9C3990259CAA3683D72C36FC87018B9F80AB2734326CD2FB559BB5D741EB96352A0ABBE4FDA4210A04DAD6CC59E33667EA787935ED9F1B X-Mras: Ok Subject: Re: [Tarantool-patches] [PATCH v1 2/2] sql: introduce syntax for MAP values 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: Mergen Imeev Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" Hi! Thank you for the review! My answers, diff and new patch below. On Tue, Nov 30, 2021 at 11:04:47PM +0100, Vladislav Shpilevoy wrote: > Thanks for the fixes! > > On 25.11.2021 09:55, Mergen Imeev wrote: > > Thank you for the review! My answers, diff and new patch below. Also, I added > > changelog and tests to show that it is possible to create an empty MAP and a > > map with more than 1000 key-value pairs. > > > > On Sat, Nov 20, 2021 at 01:46:57AM +0100, Vladislav Shpilevoy wrote: > >> Thanks for the patch! > >> > >> See 7 comments below. > >> > >>> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c > >>> index 74a98c550..789d8906c 100644 > >>> --- a/src/box/sql/expr.c > >>> +++ b/src/box/sql/expr.c > >>> @@ -3432,6 +3432,35 @@ expr_code_array(struct Parse *parser, struct Expr *expr, int reg) > >>> sqlVdbeAddOp3(vdbe, OP_Array, count, reg, values_reg); > >>> } > >>> > >>> +static void > >>> +expr_code_map(struct Parse *parser, struct Expr *expr, int reg) > >> > >> 1. I thought the policy was that we name functions, generating VDBE code, > >> using 'emit' suffix. For instance, `vdbe_emit_map()` or `sql_emit_map()`. > >> Don't know about prefix though. I see both vdbe_ and sql_ are used. > >> > > This is usually true, but this function is actually part of sqlExprCodeTarget(). > > I believe these functions were created to make sqlExprCodeTarget() more > > readable. All such functions are named sqlExprCode*(), code*() or > > expr_code _*(), for example: sqlExprCodeGetColumn(), codeReal(), > > expr_code_int(). > > > > Since all these functions are static, I think we should drop "expr_" prefix for > > them. Not in this patch, though. > > If functions take Expr as an argument like these do, they could be > considered methods of Expr. In that case dropping the expr_ prefix would > violate our naming convention. It is not about static or global here. > > As an alternative they could be considered as methods of Parse, but > then they would need to have parse_ prefix. > > For 'code' vs 'emit' - 'code' is fine by me as long as it is static. But > if it goes public, then either 'code' or 'emit' must be chosen as one > correct suffix. Not a mix. > After some thought, I think you are right. However, I would suggest removing the parser and vdbe from these functions and converting them to proper struct expr methods. This way we can make these functions return a value (most likely as an "out" argument). For example expr_code_dec() should give us DECIMAL. In this case we can make some improvements, for example we can remove "is_neg" from expr_code_int() and turn it into expr_code_uint(), since we know that this '-' sign will be specified as another expr. Also, since these will be valid expr methods, we can drop "static" from their definition. We then should name them accordingly, for example "expr_code_dec" may be named "expr_to_dec". > See 2 comments below. > > > diff --git a/changelogs/unreleased/gh-4763-introduce-map-to-sql.md b/changelogs/unreleased/gh-4763-introduce-map-to-sql.md > > new file mode 100644 > > index 000000000..013ec8f67 > > --- /dev/null > > +++ b/changelogs/unreleased/gh-4763-introduce-map-to-sql.md > > @@ -0,0 +1,4 @@ > > +## feature/core > > 1. I noticed just now - it should be feature/sql, not core. In > other patches, which are not yet submitted, too. If there are > any similar mistakes. > Thank you. Fixed here and in patch-set about ARRAY syntax. > > + > > + * Field type MAP is now available in SQL. The syntax has also been implemented > > + to allow the creation of MAP values (gh-4763).> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c > > index 32b8825bc..7411b8f67 100644 > > --- a/src/box/sql/mem.c > > +++ b/src/box/sql/mem.c > > @@ -3070,6 +3070,47 @@ mem_encode_array(const struct Mem *mems, uint32_t count, uint32_t *size, > > return array; > > } > > > > +char * > > +mem_encode_map(const struct Mem *mems, uint32_t count, uint32_t *size, > > + struct region *region) > > +{ > > + assert(count % 2 == 0); > > + size_t used = region_used(region); > > + bool is_error = false; > > + struct mpstream stream; > > + mpstream_init(&stream, region, region_reserve_cb, region_alloc_cb, > > + set_encode_error, &is_error); > > + mpstream_encode_map(&stream, (count + 1) / 2); > > + for (uint32_t i = 0; i < count / 2; ++i) { > > + const struct Mem *key = &mems[2 * i]; > > + const struct Mem *value = &mems[2 * i + 1]; > > + if (mem_is_metatype(key) || > > + (key->type & (MEM_TYPE_UINT | MEM_TYPE_INT | MEM_TYPE_UUID | > > + MEM_TYPE_STR)) == 0) { > > 2. Missed region truncate here. Looks like it would be easier to > add an 'error:' label in the end of the function to do the truncate > and return NULL. > Done. > > + diag_set(ClientError, ER_SQL_TYPE_MISMATCH, > > + mem_str(key), "integer, string or uuid"); > > + return NULL; > > + } Diff: diff --git a/changelogs/unreleased/gh-4763-introduce-map-to-sql.md b/changelogs/unreleased/gh-4763-introduce-map-to-sql.md index 013ec8f67..bc078b4c2 100644 --- a/changelogs/unreleased/gh-4763-introduce-map-to-sql.md +++ b/changelogs/unreleased/gh-4763-introduce-map-to-sql.md @@ -1,4 +1,4 @@ -## feature/core +## feature/sql * Field type MAP is now available in SQL. The syntax has also been implemented to allow the creation of MAP values (gh-4763). diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c index a275bf385..6ee5d12cc 100644 --- a/src/box/sql/mem.c +++ b/src/box/sql/mem.c @@ -3089,26 +3089,25 @@ mem_encode_map(const struct Mem *mems, uint32_t count, uint32_t *size, MEM_TYPE_STR)) == 0) { diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(key), "integer, string or uuid"); - return NULL; + goto error; } mem_to_mpstream(key, &stream); mem_to_mpstream(value, &stream); } mpstream_flush(&stream); if (is_error) { - region_truncate(region, used); diag_set(OutOfMemory, stream.pos - stream.buf, "mpstream_flush", "stream"); - return NULL; + goto error; } *size = region_used(region) - used; char *map = region_join(region, *size); - if (map == NULL) { - region_truncate(region, used); - diag_set(OutOfMemory, *size, "region_join", "map"); - return NULL; - } - return map; + if (map != NULL) + return map; + diag_set(OutOfMemory, *size, "region_join", "map"); +error: + region_truncate(region, used); + return NULL; } /** New patch: commit 91453c29ec21ad092c869012fe1a526935ce3be5 Author: Mergen Imeev Date: Thu Nov 18 11:07:59 2021 +0300 sql: introduce syntax for MAP values This patch introduces a new syntax that allows to create MAP values in an SQL query. Part of #4763 @TarantoolBot document Title: Syntax for MAP in SQL The syntax for creating document values is available in SQL. You can use `{`, ':' and `}` to create a MAP value. Only INTEGER, STRING and UUID values can be keys in MAP values. Examples: ``` tarantool> box.execute("SELECT {1 : 'a', 'asd' : 1.5, uuid() : true};") --- - metadata: - name: COLUMN_1 type: map rows: - [{1: 'a', 91ca4dbb-c6d4-4468-b4a4-ab1e409dd87e: true, 'asd': 1.5}] ... ``` ``` tarantool> box.execute("SELECT {'h' : ['abc', 321], 7 : {'b' : 1.5}};") --- - metadata: - name: COLUMN_1 type: map rows: - [{7: {'b': 1.5}, 'h': ['abc', 321]}] ... ``` diff --git a/changelogs/unreleased/gh-4763-introduce-map-to-sql.md b/changelogs/unreleased/gh-4763-introduce-map-to-sql.md new file mode 100644 index 000000000..bc078b4c2 --- /dev/null +++ b/changelogs/unreleased/gh-4763-introduce-map-to-sql.md @@ -0,0 +1,4 @@ +## feature/sql + + * Field type MAP is now available in SQL. The syntax has also been implemented + to allow the creation of MAP values (gh-4763). diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c index e832984c3..2dac9d2ef 100644 --- a/src/box/sql/expr.c +++ b/src/box/sql/expr.c @@ -3432,6 +3432,35 @@ expr_code_array(struct Parse *parser, struct Expr *expr, int reg) sqlVdbeAddOp3(vdbe, OP_Array, count, reg, values_reg); } +static void +expr_code_map(struct Parse *parser, struct Expr *expr, int reg) +{ + struct Vdbe *vdbe = parser->pVdbe; + struct ExprList *list = expr->x.pList; + if (list == NULL) { + sqlVdbeAddOp3(vdbe, OP_Map, 0, reg, 0); + return; + } + int count = list->nExpr; + assert(count % 2 == 0); + for (int i = 0; i < count / 2; ++i) { + struct Expr *expr = list->a[2 * i].pExpr; + enum field_type type = sql_expr_type(expr); + if (expr->op != TK_VARIABLE && type != FIELD_TYPE_INTEGER && + type != FIELD_TYPE_UNSIGNED && type != FIELD_TYPE_STRING && + type != FIELD_TYPE_UUID) { + diag_set(ClientError, ER_SQL_PARSER_GENERIC, "Only " + "integer, string and uuid can be keys in map"); + parser->is_aborted = true; + return; + } + } + int values_reg = parser->nMem + 1; + parser->nMem += count; + sqlExprCodeExprList(parser, list, values_reg, 0, SQL_ECEL_FACTOR); + sqlVdbeAddOp3(vdbe, OP_Map, count, reg, values_reg); +} + /* * Erase column-cache entry number i */ @@ -3887,6 +3916,10 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target) expr_code_array(pParse, pExpr, target); break; + case TK_MAP: + expr_code_map(pParse, pExpr, target); + return target; + case TK_LT: case TK_LE: case TK_GT: diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c index 510a7cce2..6ee5d12cc 100644 --- a/src/box/sql/mem.c +++ b/src/box/sql/mem.c @@ -3070,6 +3070,46 @@ mem_encode_array(const struct Mem *mems, uint32_t count, uint32_t *size, return array; } +char * +mem_encode_map(const struct Mem *mems, uint32_t count, uint32_t *size, + struct region *region) +{ + assert(count % 2 == 0); + size_t used = region_used(region); + bool is_error = false; + struct mpstream stream; + mpstream_init(&stream, region, region_reserve_cb, region_alloc_cb, + set_encode_error, &is_error); + mpstream_encode_map(&stream, (count + 1) / 2); + for (uint32_t i = 0; i < count / 2; ++i) { + const struct Mem *key = &mems[2 * i]; + const struct Mem *value = &mems[2 * i + 1]; + if (mem_is_metatype(key) || + (key->type & (MEM_TYPE_UINT | MEM_TYPE_INT | MEM_TYPE_UUID | + MEM_TYPE_STR)) == 0) { + diag_set(ClientError, ER_SQL_TYPE_MISMATCH, + mem_str(key), "integer, string or uuid"); + goto error; + } + mem_to_mpstream(key, &stream); + mem_to_mpstream(value, &stream); + } + mpstream_flush(&stream); + if (is_error) { + diag_set(OutOfMemory, stream.pos - stream.buf, + "mpstream_flush", "stream"); + goto error; + } + *size = region_used(region) - used; + char *map = region_join(region, *size); + if (map != NULL) + return map; + diag_set(OutOfMemory, *size, "region_join", "map"); +error: + region_truncate(region, used); + return NULL; +} + /** * Allocate a sequence of initialized vdbe memory registers * on region. diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h index 7f5ecf954..7e35123ca 100644 --- a/src/box/sql/mem.h +++ b/src/box/sql/mem.h @@ -874,3 +874,19 @@ mem_to_mpstream(const struct Mem *var, struct mpstream *stream); char * mem_encode_array(const struct Mem *mems, uint32_t count, uint32_t *size, struct region *region); + +/** + * Encode array of MEMs as msgpack map on region. Values in even position are + * treated as keys in MAP, values in odd position are treated as values in MAP. + * number of MEMs should be even. + * + * @param mems array of MEMs to encode. + * @param count number of elements in the array. + * @param[out] size Size of encoded msgpack map. + * @param region Region to use. + * @retval NULL on error, diag message is set. + * @retval Pointer to valid msgpack map on success. + */ +char * +mem_encode_map(const struct Mem *mems, uint32_t count, uint32_t *size, + struct region *region); diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y index ecc7f7778..a426a3ccf 100644 --- a/src/box/sql/parse.y +++ b/src/box/sql/parse.y @@ -1075,11 +1075,11 @@ expr(A) ::= VARNUM(X). { A.pExpr = expr_new_variable(pParse, &X, NULL); spanSet(&A, &X, &X); } -expr(A) ::= VARIABLE(X) id(Y). { +expr(A) ::= COLON|VARIABLE(X) id(Y). { A.pExpr = expr_new_variable(pParse, &X, &Y); spanSet(&A, &X, &Y); } -expr(A) ::= VARIABLE(X) INTEGER(Y). { +expr(A) ::= COLON|VARIABLE(X) INTEGER(Y). { A.pExpr = expr_new_variable(pParse, &X, &Y); spanSet(&A, &X, &Y); } @@ -1113,6 +1113,39 @@ expr(A) ::= LB(X) exprlist(Y) RB(E). { spanSet(&A, &X, &E); } +expr(A) ::= LCB(X) maplist(Y) RCB(E). { + struct sql *db = pParse->db; + struct Expr *expr = sql_expr_new_anon(db, TK_MAP); + if (expr == NULL) { + sql_expr_list_delete(db, Y); + pParse->is_aborted = true; + return; + } + expr->x.pList = Y; + expr->type = FIELD_TYPE_MAP; + sqlExprSetHeightAndFlags(pParse, expr); + A.pExpr = expr; + spanSet(&A, &X, &E); +} + +maplist(A) ::= nmaplist(A). +maplist(A) ::= . { + A = NULL; +} +nmaplist(A) ::= nmaplist(A) COMMA expr(X) COLON expr(Y). { + A = sql_expr_list_append(pParse->db, A, X.pExpr); + A = sql_expr_list_append(pParse->db, A, Y.pExpr); +} +nmaplist(A) ::= expr(X) COLON expr(Y). { + A = sql_expr_list_append(pParse->db, NULL, X.pExpr); + A = sql_expr_list_append(pParse->db, A, Y.pExpr); +} + +%type maplist {ExprList *} +%destructor maplist {sql_expr_list_delete(pParse->db, $$);} +%type nmaplist {ExprList *} +%destructor nmaplist {sql_expr_list_delete(pParse->db, $$);} + expr(A) ::= TRIM(X) LP trim_operands(Y) RP(E). { A.pExpr = sqlExprFunction(pParse, Y, &X); spanSet(&A, &X, &E); diff --git a/src/box/sql/tokenize.c b/src/box/sql/tokenize.c index 8bc519b9d..9e85801a3 100644 --- a/src/box/sql/tokenize.c +++ b/src/box/sql/tokenize.c @@ -58,7 +58,9 @@ #define CC_KYWD 1 /* Alphabetics or '_'. Usable in a keyword */ #define CC_ID 2 /* unicode characters usable in IDs */ #define CC_DIGIT 3 /* Digits */ -/** SQL variables: '@', '#', ':', and '$'. */ +/** Character ':'. */ +#define CC_COLON 4 +/** SQL variable special characters: '@', '#', and '$'. */ #define CC_VARALPHA 5 #define CC_VARNUM 6 /* '?'. Numeric SQL variables */ #define CC_SPACE 7 /* Space characters */ @@ -85,17 +87,21 @@ #define CC_LINEFEED 28 /* '\n' */ #define CC_LB 29 /* '[' */ #define CC_RB 30 /* ']' */ +/** Character '{'. */ +#define CC_LCB 31 +/** Character '}'. */ +#define CC_RCB 32 static const char sql_ascii_class[] = { /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */ /* 0x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 7, 28, 7, 7, 7, 27, 27, /* 1x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, /* 2x */ 7, 15, 9, 5, 5, 22, 24, 8, 17, 18, 21, 20, 23, 11, 26, 16, -/* 3x */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 19, 12, 14, 13, 6, +/* 3x */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 19, 12, 14, 13, 6, /* 4x */ 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 5x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 29, 27, 30, 27, 1, /* 6x */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -/* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 27, 10, 27, 25, 27, +/* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 31, 10, 32, 25, 27, /* 8x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 9x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* Ax */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, @@ -228,6 +234,12 @@ sql_token(const char *z, int *type, bool *is_reserved) case CC_RB: *type = TK_RB; return 1; + case CC_LCB: + *type = TK_LCB; + return 1; + case CC_RCB: + *type = TK_RCB; + return 1; case CC_SEMI: *type = TK_SEMI; return 1; @@ -371,6 +383,9 @@ sql_token(const char *z, int *type, bool *is_reserved) case CC_VARNUM: *type = TK_VARNUM; return 1; + case CC_COLON: + *type = TK_COLON; + return 1; case CC_VARALPHA: *type = TK_VARIABLE; return 1; diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index 55e494332..86de3f98a 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -1438,6 +1438,26 @@ case OP_Array: { break; } +/** + * Opcode: Map P1 P2 P3 * * + * Synopsis: r[P2] = map(P3@P1) + * + * Construct an MAP value from P1 registers starting at reg(P3). + */ +case OP_Map: { + pOut = &aMem[pOp->p2]; + + uint32_t size; + struct region *region = &fiber()->gc; + size_t svp = region_used(region); + char *val = mem_encode_map(&aMem[pOp->p3], pOp->p1, &size, region); + if (val == NULL || mem_copy_map(pOut, val, size) != 0) { + region_truncate(region, svp); + goto abort_due_to_error; + } + break; +} + /* Opcode: Eq P1 P2 P3 P4 P5 * Synopsis: IF r[P3]==r[P1] * diff --git a/test/sql-tap/map.test.lua b/test/sql-tap/map.test.lua index 1afbb2b1d..7791ca779 100755 --- a/test/sql-tap/map.test.lua +++ b/test/sql-tap/map.test.lua @@ -1,6 +1,6 @@ #!/usr/bin/env tarantool local test = require("sqltester") -test:plan(110) +test:plan(131) box.schema.func.create('M1', { language = 'Lua', @@ -982,6 +982,189 @@ test:do_catchsql_test( 1, "Failed to execute SQL statement: wrong arguments for function ZEROBLOB()" }) +-- Make sure syntax for MAP values works as intended. +test:do_execsql_test( + "map-13.1", + [[ + SELECT {'a': a, 'g': g, 't': t, 'n': n, 'f': f, 'i': i, 'b': b, 'v': v, + 's': s, 'd': d, 'u': u} FROM t1 WHERE id = 1; + ]], { + {t = "1", f = 1, n = 1, v = "1", g = 1, b = true, s = 1, + d = require('decimal').new(1), a = {a = 1}, i = 1, + u = require('uuid').fromstr('11111111-1111-1111-1111-111111111111')} + }) + +test:do_execsql_test( + "map-13.2", + [[ + SELECT {'q': 1, 'w': true, 'e': 1.5e0, 'r': ['asd', x'32'], 't': 123.0}; + ]], { + {w = true, e = 1.5, r = {'asd', '2'}, t = require('decimal').new(123), + q = 1} + }) + +test:do_execsql_test( + "map-13.3", + [[ + SELECT typeof({1: 1}); + ]], { + "map" + }) + +test:do_execsql_test( + "map-13.4", + [[ + SELECT printf({}); + ]], { + '{}' + }) + +local map = {[0] = 0} +local str = '0: 0' +for i = 1, 1000 do map[i] = i str = str .. string.format(', %d: %d', i, i) end +test:do_execsql_test( + "map-13.5", + [[ + SELECT {]]..str..[[}; + ]], { + map + }) + +-- Make sure MAP() accepts only INTEGER, STRING and UUID as keys. +test:do_execsql_test( + "map-13.4", + [[ + SELECT {1: 1}; + ]], { + {[1] = 1} + }) + +test:do_execsql_test( + "map-13.5", + [[ + SELECT {-1: 1}; + ]], { + {[-1] = 1} + }) + +test:do_execsql_test( + "map-13.6", + [[ + SELECT {'a': 1}; + ]], { + {a = 1} + }) + +test:do_execsql_test( + "map-13.6", + [[ + SELECT typeof({UUID(): 1}); + ]], { + "map" + }) + +test:do_catchsql_test( + "map-13.7", + [[ + SELECT {1.5e0: 1}; + ]], { + 1, "Only integer, string and uuid can be keys in map" + }) + +test:do_catchsql_test( + "map-13.8", + [[ + SELECT {1.5: 1}; + ]], { + 1, "Only integer, string and uuid can be keys in map" + }) + +test:do_catchsql_test( + "map-13.9", + [[ + SELECT {x'33': 1}; + ]], { + 1, "Only integer, string and uuid can be keys in map" + }) + +test:do_catchsql_test( + "map-13.10", + [[ + SELECT {[1, 2, 3]: 1}; + ]], { + 1, "Only integer, string and uuid can be keys in map" + }) + +test:do_catchsql_test( + "map-13.11", + [[ + SELECT {{'a': 1}: 1}; + ]], { + 1, + 'Only integer, string and uuid can be keys in map' + }) + +test:do_catchsql_test( + "map-13.12", + [[ + SELECT {CAST(1 AS NUMBER): 1}; + ]], { + 1, 'Only integer, string and uuid can be keys in map' + }) + +test:do_catchsql_test( + "map-13.13", + [[ + SELECT {CAST(1 AS SCALAR): 1}; + ]], { + 1, 'Only integer, string and uuid can be keys in map' + }) + +test:do_catchsql_test( + "map-13.14", + [[ + SELECT {CAST(1 AS ANY): 1}; + ]], { + 1, 'Only integer, string and uuid can be keys in map' + }) + +test:do_test( + "map-13.15", + function() + local res = {pcall(box.execute, [[SELECT {?: 1};]], {1.5})} + return {tostring(res[3])} + end, { + "Type mismatch: can not convert double(1.5) to integer, string or uuid" + }) + +-- Make sure symbol ':' is properly processed by parser. +test:do_test( + "map-14.1", + function() + local res = {pcall(box.execute, [[SELECT {:name};]], {{[':name'] = 1}})} + return {tostring(res[3])} + end, { + "Syntax error at line 1 near '}'" + }) + +test:do_test( + "map-14.2", + function() + local res = box.execute([[SELECT {:name: 5}]], {{[':name'] = 1}}) + return {tostring(res.rows[1])} + end, { + "[{1: 5}]" + }) + +test:do_test( + "map-14.3", + function() + local res = box.execute([[SELECT {5::name}]], {{[':name'] = 1}}) + return {tostring(res.rows[1])} + end, { + "[{5: 1}]" + }) + box.execute([[DROP TABLE t1;]]) box.execute([[DROP TABLE t;]])