Tarantool development patches archive
 help / color / mirror / Atom feed
From: Mergen Imeev via Tarantool-patches <tarantool-patches@dev.tarantool.org>
To: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Cc: tarantool-patches@dev.tarantool.org
Subject: Re: [Tarantool-patches] [PATCH v1 2/2] sql: introduce syntax for MAP values
Date: Thu, 2 Dec 2021 11:38:51 +0300	[thread overview]
Message-ID: <20211202083851.GB8207@tarantool.org> (raw)
In-Reply-To: <f6554681-1d25-f48f-b1cd-a15be581ab39@tarantool.org>

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 <imeevma@gmail.com>
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;]])
 

  reply	other threads:[~2021-12-02  8:38 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-11-18 14:08 [Tarantool-patches] [PATCH v1 0/2] Introduce syntax for MAP values is SQL Mergen Imeev via Tarantool-patches
2021-11-18 14:08 ` [Tarantool-patches] [PATCH v1 1/2] sql: properly check bind variable names Mergen Imeev via Tarantool-patches
2021-11-20  0:45   ` Vladislav Shpilevoy via Tarantool-patches
2021-11-25  8:33     ` Mergen Imeev via Tarantool-patches
2021-11-30 22:02       ` Vladislav Shpilevoy via Tarantool-patches
2021-12-02  8:32         ` Mergen Imeev via Tarantool-patches
2021-12-09  0:31           ` Vladislav Shpilevoy via Tarantool-patches
2021-12-13  7:34             ` Mergen Imeev via Tarantool-patches
2021-12-13 21:47               ` Vladislav Shpilevoy via Tarantool-patches
2021-11-18 14:08 ` [Tarantool-patches] [PATCH v1 2/2] sql: introduce syntax for MAP values Mergen Imeev via Tarantool-patches
2021-11-20  0:46   ` Vladislav Shpilevoy via Tarantool-patches
2021-11-25  8:55     ` Mergen Imeev via Tarantool-patches
2021-11-30 22:04       ` Vladislav Shpilevoy via Tarantool-patches
2021-12-02  8:38         ` Mergen Imeev via Tarantool-patches [this message]
2021-12-09  0:31           ` Vladislav Shpilevoy via Tarantool-patches
2021-12-13  7:42             ` Mergen Imeev via Tarantool-patches
2021-12-13 21:48               ` Vladislav Shpilevoy via Tarantool-patches

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20211202083851.GB8207@tarantool.org \
    --to=tarantool-patches@dev.tarantool.org \
    --cc=imeevma@tarantool.org \
    --cc=v.shpilevoy@tarantool.org \
    --subject='Re: [Tarantool-patches] [PATCH v1 2/2] sql: introduce syntax for MAP values' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox