Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH v1 0/2] Introduce field type MAP to SQL
@ 2021-11-11 11:37 Mergen Imeev via Tarantool-patches
  2021-11-11 11:37 ` [Tarantool-patches] [PATCH v1 1/2] sql: introduce field type MAP Mergen Imeev via Tarantool-patches
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-11-11 11:37 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch introduces field type MAP to SQL. Also, it adds function MAP() that
is used to create values of type ARRAY.

https://github.com/tarantool/tarantool/issues/4763
https://github.com/tarantool/tarantool/tree/imeevma/gh-4763-introduce-map

Mergen Imeev (2):
  sql: introduce field type MAP
  sql: introduce MAP() function

 .../gh-4763-introduce-map-to-sql.md           |    4 +
 extra/mkkeywordhash.c                         |    1 +
 src/box/sql/func.c                            |   30 +-
 src/box/sql/mem.c                             |   76 +-
 src/box/sql/mem.h                             |   21 +
 src/box/sql/parse.y                           |    3 +-
 test/box/function1.result                     |    5 -
 test/box/function1.test.lua                   |    1 -
 test/sql-tap/map.test.lua                     | 1106 +++++++++++++++++
 9 files changed, 1233 insertions(+), 14 deletions(-)
 create mode 100644 changelogs/unreleased/gh-4763-introduce-map-to-sql.md
 create mode 100755 test/sql-tap/map.test.lua

-- 
2.25.1


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [Tarantool-patches] [PATCH v1 1/2] sql: introduce field type MAP
  2021-11-11 11:37 [Tarantool-patches] [PATCH v1 0/2] Introduce field type MAP to SQL Mergen Imeev via Tarantool-patches
@ 2021-11-11 11:37 ` Mergen Imeev via Tarantool-patches
  2021-11-14 16:24   ` Vladislav Shpilevoy via Tarantool-patches
  2021-11-11 11:37 ` [Tarantool-patches] [PATCH v1 2/2] sql: introduce MAP() function Mergen Imeev via Tarantool-patches
  2021-11-18 21:21 ` [Tarantool-patches] [PATCH v1 0/2] Introduce field type MAP to SQL Vladislav Shpilevoy via Tarantool-patches
  2 siblings, 1 reply; 8+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-11-11 11:37 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch introduces MAP to SQL. After this patch, all SQL operations
and built-in functions should work correctly with MAP values. However,
there is currently no way to create MAP values using only SQL tools.

Part of #4763

@TarantoolBot document
Title: Field type MAP in SQL

Properties of type MAP in SQL:
1) a value ofttype MAP can be implicitly and explicitly cast only to
ANY;
2) only a value of type ANY with primitive type MAP can be explicitly
cast to MAP;
3) a value of any other type cannot be implicitly cast to MAP;
4) a value of type MAP cannot participate in arithmetic, bitwise,
comparison, and concationation operations.
---
 extra/mkkeywordhash.c       |   1 +
 src/box/sql/func.c          |   8 +-
 src/box/sql/mem.c           |  35 +-
 src/box/sql/mem.h           |   4 +
 src/box/sql/parse.y         |   1 +
 test/box/function1.result   |   5 -
 test/box/function1.test.lua |   1 -
 test/sql-tap/map.test.lua   | 987 ++++++++++++++++++++++++++++++++++++
 8 files changed, 1029 insertions(+), 13 deletions(-)
 create mode 100755 test/sql-tap/map.test.lua

diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
index c7e7d4e8d..30bd6f0d8 100644
--- a/extra/mkkeywordhash.c
+++ b/extra/mkkeywordhash.c
@@ -125,6 +125,7 @@ static Keyword aKeywordTable[] = {
   { "LEFT",                   "TK_JOIN_KW",     true  },
   { "LIKE",                   "TK_LIKE_KW",     true  },
   { "LIMIT",                  "TK_LIMIT",       false },
+  { "MAP",                    "TK_MAP",         true  },
   { "MATCH",                  "TK_MATCH",       true  },
   { "NATURAL",                "TK_JOIN_KW",     true  },
   { "NO",                     "TK_NO",          false },
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 63bf58473..8bf3e473d 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -359,9 +359,11 @@ typeofFunc(struct sql_context *context, int argc, struct Mem *argv)
 		z = "double";
 		break;
 	case MEM_TYPE_BIN:
-	case MEM_TYPE_MAP:
 		z = "varbinary";
 		break;
+	case MEM_TYPE_MAP:
+		z = "map";
+		break;
 	case MEM_TYPE_ARRAY:
 		z = "array";
 		break;
@@ -1294,6 +1296,7 @@ quoteFunc(struct sql_context *context, int argc, struct Mem *argv)
 			sql_result_value(context, &argv[0]);
 			break;
 		}
+	case MEM_TYPE_MAP:
 	case MEM_TYPE_ARRAY: {
 		char *buf = NULL;
 		int size = mp_snprint(buf, 0, argv[0].z) + 1;
@@ -1307,8 +1310,7 @@ quoteFunc(struct sql_context *context, int argc, struct Mem *argv)
 		mem_set_str0_allocated(context->pOut, buf);
 		break;
 	}
-	case MEM_TYPE_BIN:
-	case MEM_TYPE_MAP: {
+	case MEM_TYPE_BIN: {
 			char *zText = 0;
 			char const *zBlob = mem_as_bin(&argv[0]);
 			int nBlob = mem_len_unsafe(&argv[0]);
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 1d55a1209..f5e8c11cc 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -192,6 +192,8 @@ mem_type_class_to_str(const struct Mem *mem)
 		return "uuid";
 	case MEM_TYPE_ARRAY:
 		return "array";
+	case MEM_TYPE_MAP:
+		return "map";
 	default:
 		break;
 	}
@@ -530,6 +532,12 @@ mem_set_map_allocated(struct Mem *mem, char *value, uint32_t size)
 	set_msgpack_value(mem, value, size, 0, MEM_TYPE_MAP);
 }
 
+int
+mem_copy_map(struct Mem *mem, const char *value, uint32_t size)
+{
+	return mem_copy_bytes(mem, value, size, MEM_TYPE_MAP);
+}
+
 void
 mem_set_array_ephemeral(struct Mem *mem, char *value, uint32_t size)
 {
@@ -1385,8 +1393,6 @@ mem_to_str(struct Mem *mem)
 		return bool_to_str0(mem);
 	case MEM_TYPE_BIN:
 		return bin_to_str(mem);
-	case MEM_TYPE_MAP:
-		return map_to_str0(mem);
 	case MEM_TYPE_UUID:
 		return uuid_to_str0(mem);
 	case MEM_TYPE_DEC:
@@ -1475,6 +1481,11 @@ mem_cast_explicit(struct Mem *mem, enum field_type type)
 			return -1;
 		mem->flags &= ~MEM_Any;
 		return 0;
+	case FIELD_TYPE_MAP:
+		if (mem->type != MEM_TYPE_MAP)
+			return -1;
+		mem->flags &= ~MEM_Any;
+		return 0;
 	case FIELD_TYPE_SCALAR:
 		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
 			return -1;
@@ -3243,7 +3254,6 @@ port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
 			break;
 		case MEM_TYPE_STR:
 		case MEM_TYPE_BIN:
-		case MEM_TYPE_MAP:
 			lua_pushlstring(L, mem->z, mem->n);
 			break;
 		case MEM_TYPE_ARRAY: {
@@ -3258,6 +3268,19 @@ port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
 				luaL_setarrayhint(L, -1);
 			return;
 		}
+		case MEM_TYPE_MAP: {
+			const char *data = mem->z;
+			uint32_t size = mp_decode_map(&data);
+			lua_createtable(L, 0, size);
+			for (uint32_t i = 0; i < size; i++) {
+				luamp_decode(L, luaL_msgpack_default, &data);
+				luamp_decode(L, luaL_msgpack_default, &data);
+				lua_settable(L, -3);
+			}
+			if (luaL_msgpack_default->decode_save_metatables)
+				luaL_setmaphint(L, -1);
+			return;
+		}
 		case MEM_TYPE_NULL:
 			lua_pushnil(L);
 			break;
@@ -3388,8 +3411,10 @@ port_lua_get_vdbemem(struct port *base, uint32_t *size)
 					 field.sval.len) != 0)
 				goto error;
 			break;
+		case MP_MAP:
 		case MP_ARRAY: {
 			size_t used = region_used(region);
+			bool is_map = field.type == MP_MAP;
 			struct mpstream stream;
 			bool is_error = false;
 			mpstream_init(&stream, region, region_reserve_cb,
@@ -3412,7 +3437,9 @@ port_lua_get_vdbemem(struct port *base, uint32_t *size)
 					 "raw");
 				goto error;
 			}
-			if (mem_copy_array(&val[i], raw, size) != 0)
+			int rc = is_map ? mem_copy_map(&val[i], raw, size) :
+				 mem_copy_array(&val[i], raw, size);
+			if (rc != 0)
 				goto error;
 			region_truncate(region, used);
 			break;
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index afef6d842..7ddf81236 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -507,6 +507,10 @@ mem_set_map_dynamic(struct Mem *mem, char *value, uint32_t size);
 void
 mem_set_map_allocated(struct Mem *mem, char *value, uint32_t size);
 
+/** Copy MAP value to a newly allocated memory. The MEM type becomes MAP. */
+int
+mem_copy_map(struct Mem *mem, const char *value, uint32_t size);
+
 /**
  * Clear MEM and set it to ARRAY. The binary value belongs to another object.
  * The binary value must be msgpack of ARRAY type.
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index a3ba5da08..e27c1804b 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -1843,6 +1843,7 @@ typedef(A) ::= VARBINARY . { A.type = FIELD_TYPE_VARBINARY; }
 typedef(A) ::= UUID . { A.type = FIELD_TYPE_UUID; }
 typedef(A) ::= ANY . { A.type = FIELD_TYPE_ANY; }
 typedef(A) ::= ARRAY . { A.type = FIELD_TYPE_ARRAY; }
+typedef(A) ::= MAP . { A.type = FIELD_TYPE_MAP; }
 
 /**
  * Time-like types are temporary disabled, until they are
diff --git a/test/box/function1.result b/test/box/function1.result
index a1c89850d..334417be7 100644
--- a/test/box/function1.result
+++ b/test/box/function1.result
@@ -524,11 +524,6 @@ box.execute('SELECT lua(\'return 1 + 1\')')
   rows:
   - [2]
 ...
-box.execute('SELECT lua(\'return box.cfg\')')
----
-- null
-- 'Failed to execute SQL statement: Unsupported type passed from Lua'
-...
 box.execute('SELECT lua(\'return box.cfg()\')')
 ---
 - null
diff --git a/test/box/function1.test.lua b/test/box/function1.test.lua
index e635b6e18..5b762d08a 100644
--- a/test/box/function1.test.lua
+++ b/test/box/function1.test.lua
@@ -187,7 +187,6 @@ box.func.SUMMARIZE:drop()
 -- gh-4113: Valid method to use Lua from SQL
 --
 box.execute('SELECT lua(\'return 1 + 1\')')
-box.execute('SELECT lua(\'return box.cfg\')')
 box.execute('SELECT lua(\'return box.cfg()\')')
 box.execute('SELECT lua(\'return box.cfg.memtx_memory\')')
 
diff --git a/test/sql-tap/map.test.lua b/test/sql-tap/map.test.lua
new file mode 100755
index 000000000..2be82db61
--- /dev/null
+++ b/test/sql-tap/map.test.lua
@@ -0,0 +1,987 @@
+#!/usr/bin/env tarantool
+local test = require("sqltester")
+test:plan(110)
+
+box.schema.func.create('M1', {
+    language = 'Lua',
+    body = 'function(a, b) return {[tostring(a)] = b} end',
+    returns = 'map',
+    param_list = {'any', 'any'},
+    exports = {'LUA', 'SQL'}
+});
+
+box.schema.func.create('M2', {
+    language = 'Lua',
+    body = 'function(a, b, c, d) return '..
+           '{[tostring(a)] = b, [tostring(c)] = d} end',
+    returns = 'map',
+    param_list = {'any', 'any', 'any', 'any'},
+    exports = {'LUA', 'SQL'}
+});
+
+box.schema.func.create('M3', {
+    language = 'Lua',
+    body = 'function(a, b, c, d, e, f) return '..
+           '{[tostring(a)] = b, [tostring(c)] = d, [tostring(e)] = f} end',
+    returns = 'map',
+    param_list = {'any', 'any', 'any', 'any', 'any', 'any'},
+    exports = {'LUA', 'SQL'}
+});
+
+-- Make sure it is possible to create tables with field type MAP.
+test:do_execsql_test(
+    "map-1",
+    [[
+        CREATE TABLE t (i INT PRIMARY KEY AUTOINCREMENT, m MAP);
+    ]], {
+    })
+
+box.space.T:insert({0, {a = 1, b = 2}})
+
+-- Make sure it is possible to select from MAP field.
+test:do_execsql_test(
+    "map-2",
+    [[
+        SELECT i, m FROM t;
+    ]], {
+        0, {a = 1, b = 2},
+    })
+
+-- Make sure it is possible to insert into MAP field.
+test:do_execsql_test(
+    "map-3",
+    [[
+        INSERT INTO t(m) VALUES(NULL);
+        INSERT INTO t(m) VALUES(m1('a', 1));
+        INSERT INTO t(m) VALUES(m2('b', 2, 'c', 3));
+        INSERT INTO t(m) VALUES(m3('d', 4, 'e', 5, 'f', 6));
+        SELECT i, m FROM t;
+    ]], {
+        0, {a = 1, b = 2},
+        1, "",
+        2, {a = 1},
+        3, {b = 2, c = 3},
+        4, {d = 4, e = 5, f = 6},
+    })
+
+-- Make sure it is possible to delete from MAP field.
+test:do_execsql_test(
+    "map-4",
+    [[
+        DELETE FROM t WHERE i < 3;
+        SELECT i, m FROM t;
+    ]], {
+        3, {b = 2, c = 3},
+        4, {d = 4, e = 5, f = 6},
+    })
+
+-- Make sure it is possible to update MAP field.
+test:do_execsql_test(
+    "map-5",
+    [[
+        UPDATE t SET m = m1('abc', 123) WHERE i = 3;
+        SELECT i, m FROM t;
+    ]], {
+        3, {abc = 123},
+        4, {d = 4, e = 5, f = 6},
+    })
+
+-- Make sure MAP can only be explicitly cast to ANY.
+test:do_execsql_test(
+    "map-6.1",
+    [[
+        SELECT CAST(m AS ANY) FROM t;
+    ]], {
+        {abc = 123},
+        {d = 4, e = 5, f = 6},
+    })
+
+test:do_catchsql_test(
+    "map-6.2",
+    [[
+        SELECT CAST(m AS UNSIGNED) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to unsigned'
+    })
+
+test:do_catchsql_test(
+    "map-6.3",
+    [[
+        SELECT CAST(m AS STRING) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to string'
+    })
+
+test:do_catchsql_test(
+    "map-6.4",
+    [[
+        SELECT CAST(m AS NUMBER) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to number'
+    })
+
+test:do_catchsql_test(
+    "map-6.5",
+    [[
+        SELECT CAST(m AS DOUBLE) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to double'
+    })
+
+test:do_catchsql_test(
+    "map-6.6",
+    [[
+        SELECT CAST(m AS INTEGER) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to integer'
+    })
+
+test:do_catchsql_test(
+    "map-6.7",
+    [[
+        SELECT CAST(m AS BOOLEAN) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to boolean'
+    })
+
+test:do_catchsql_test(
+    "map-6.8",
+    [[
+        SELECT CAST(m AS VARBINARY) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to varbinary'
+    })
+
+test:do_catchsql_test(
+    "map-6.9",
+    [[
+        SELECT CAST(m AS SCALAR) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to scalar'
+    })
+
+test:do_catchsql_test(
+    "map-6.10",
+    [[
+        SELECT CAST(m AS DECIMAL) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to decimal'
+    })
+
+test:do_catchsql_test(
+    "map-6.11",
+    [[
+        SELECT CAST(m AS UUID) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to uuid'
+    })
+
+box.execute([[CREATE TABLE t1 (id INTEGER PRIMARY KEY AUTOINCREMENT, a ANY,
+                               g UNSIGNED, t STRING, n NUMBER, f DOUBLE,
+                               i INTEGER, b BOOLEAN, v VARBINARY, s SCALAR,
+                               d DECIMAL, u UUID);]])
+box.execute([[INSERT INTO t1 VALUES(1, m1('a', 1), 1, '1', 1, 1, 1, true, ]]..
+            [[x'31', 1, 1, ]]..
+            [[CAST('11111111-1111-1111-1111-111111111111' AS UUID))]])
+
+--
+-- Make sure that only ANY value can be explicitly cast to MAP if the value
+-- contains MAP.
+--
+test:do_execsql_test(
+    "map-7.1",
+    [[
+        SELECT CAST(a AS MAP) FROM t1;
+    ]], {
+        {a = 1}
+    })
+
+test:do_catchsql_test(
+    "map-7.2",
+    [[
+        SELECT CAST(g AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert integer(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.3",
+    [[
+        SELECT CAST(t AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert string('1') to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.4",
+    [[
+        SELECT CAST(n AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert number(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.5",
+    [[
+        SELECT CAST(f AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert double(1.0) to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.6",
+    [[
+        SELECT CAST(i AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert integer(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.7",
+    [[
+        SELECT CAST(b AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert boolean(TRUE) to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.8",
+    [[
+        SELECT CAST(v AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert varbinary(x'31') to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.9",
+    [[
+        SELECT CAST(s AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert scalar(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.10",
+    [[
+        SELECT CAST(d AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert decimal(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.11",
+    [[
+        SELECT CAST(u AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert "..
+           "uuid('11111111-1111-1111-1111-111111111111') to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.12",
+    [[
+        SELECT CAST(CAST(1 AS ANY) AS MAP);
+    ]], {
+        1, "Type mismatch: can not convert any(1) to map"
+    })
+
+-- Make sure that MAP can only be implicitly cast to ANY.
+test:do_execsql_test(
+    "map-8.1",
+    [[
+        INSERT INTO t1(a) VALUES(m1(1, 2));
+        SELECT a FROM t1 WHERE a IS NOT NULL;
+    ]], {
+        {a = 1},
+        {['1'] = 2}
+    })
+
+test:do_catchsql_test(
+    "map-8.2",
+    [[
+        INSERT INTO t1(g) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to unsigned'
+    })
+
+test:do_catchsql_test(
+    "map-8.3",
+    [[
+        INSERT INTO t1(t) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to string'
+    })
+
+test:do_catchsql_test(
+    "map-8.4",
+    [[
+        INSERT INTO t1(n) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to number'
+    })
+
+test:do_catchsql_test(
+    "map-8.5",
+    [[
+        INSERT INTO t1(f) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to double'
+    })
+
+test:do_catchsql_test(
+    "map-8.6",
+    [[
+        INSERT INTO t1(i) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to integer'
+    })
+
+test:do_catchsql_test(
+    "map-8.7",
+    [[
+        INSERT INTO t1(b) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to boolean'
+    })
+
+test:do_catchsql_test(
+    "map-8.8",
+    [[
+        INSERT INTO t1(v) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to varbinary'
+    })
+
+test:do_catchsql_test(
+    "map-8.9",
+    [[
+        INSERT INTO t1(s) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to scalar'
+    })
+
+test:do_catchsql_test(
+    "map-8.10",
+    [[
+        INSERT INTO t1(d) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to decimal'
+    })
+
+test:do_catchsql_test(
+    "map-8.11",
+    [[
+        INSERT INTO t1(u) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to uuid'
+    })
+
+-- Make sure nothing can be implicitly cast to MAP.
+test:do_catchsql_test(
+    "map-9.1",
+    [[
+        INSERT INTO t(m) VALUES(CAST(m1('a', 1) AS ANY));
+    ]], {
+        1, 'Type mismatch: can not convert any({"a": 1}) to map'
+    })
+
+test:do_catchsql_test(
+    "map-9.2",
+    [[
+        INSERT INTO t(m) SELECT g FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert integer(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.3",
+    [[
+        INSERT INTO t(m) SELECT t FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert string('1') to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.4",
+    [[
+        INSERT INTO t(m) SELECT n FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert number(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.5",
+    [[
+        INSERT INTO t(m) SELECT f FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert double(1.0) to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.6",
+    [[
+        INSERT INTO t(m) SELECT i FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert integer(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.7",
+    [[
+        INSERT INTO t(m) SELECT b FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert boolean(TRUE) to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.8",
+    [[
+        INSERT INTO t(m) SELECT v FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert varbinary(x'31') to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.9",
+    [[
+        INSERT INTO t(m) SELECT s FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert scalar(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.10",
+    [[
+        INSERT INTO t(m) SELECT d FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert decimal(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.11",
+    [[
+        INSERT INTO t(m) SELECT u FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert "..
+           "uuid('11111111-1111-1111-1111-111111111111') to map"
+    })
+
+--
+-- Make sure MAP cannot participate in arithmetic and bitwise operations and
+-- concatenation.
+--
+test:do_catchsql_test(
+    "map-10.1",
+    [[
+        SELECT m1(1, 2) + 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to integer, '..
+           "decimal or double"
+    })
+
+test:do_catchsql_test(
+    "map-10.2",
+    [[
+        SELECT m1(1, 2) - 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to integer, '..
+           "decimal or double"
+    })
+
+test:do_catchsql_test(
+    "map-10.3",
+    [[
+        SELECT m1(1, 2) * 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to integer, '..
+           "decimal or double"
+    })
+
+test:do_catchsql_test(
+    "map-10.4",
+    [[
+        SELECT m1(1, 2) / 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to integer, '..
+           "decimal or double"
+    })
+
+test:do_catchsql_test(
+    "map-10.5",
+    [[
+        SELECT m1(1, 2) % 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to integer'
+    })
+
+test:do_catchsql_test(
+    "map-10.6",
+    [[
+        SELECT m1(1, 2) >> 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to unsigned'
+    })
+
+test:do_catchsql_test(
+    "map-10.7",
+    [[
+        SELECT m1(1, 2) << 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to unsigned'
+    })
+
+test:do_catchsql_test(
+    "map-10.8",
+    [[
+        SELECT m1(1, 2) | 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to unsigned'
+    })
+
+test:do_catchsql_test(
+    "map-10.9",
+    [[
+        SELECT m1(1, 2) & 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to unsigned'
+    })
+
+test:do_catchsql_test(
+    "map-10.10",
+    [[
+        SELECT ~m1(1, 2);
+    ]], {
+        1, 'Type mismatch: can not convert map({"1": 2}) to unsigned'
+    })
+
+test:do_catchsql_test(
+    "map-10.11",
+    [[
+        SELECT m1(1, 2) || 'asd';
+    ]], {
+        1, 'Inconsistent types: expected string or varbinary got map({"1": 2})'
+    })
+
+-- Make sure MAP is not comparable.
+test:do_catchsql_test(
+    "map-11.1",
+    [[
+        SELECT m1('a', 1) > m1('b', 2);
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.2",
+    [[
+        SELECT m1('a', 1) < CAST(1 AS ANY);
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.3",
+    [[
+        SELECT m1('a', 1) == CAST(1 AS SCALAR);
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.4",
+    [[
+        SELECT m1('a', 1) != CAST(1 AS NUMBER);
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.5",
+    [[
+        SELECT m1('a', 1) >= CAST(1 AS DECIMAL);;
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.6",
+    [[
+        SELECT m1('a', 1) <= CAST(1 AS UNSIGNED);;
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.7",
+    [[
+        SELECT m1('a', 1) > 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.8",
+    [[
+        SELECT m1('a', 1) < 1e0;
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.9",
+    [[
+        SELECT m1('a', 1) == 'asd';
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.10",
+    [[
+        SELECT m1('a', 1) != x'323334';
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.11",
+    [[
+        SELECT m1('a', 1) >= true;
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.12",
+    [[
+        SELECT m1('a', 1) <= CAST('11111111-1111-1111-1111-111111111111' AS UUID);
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-12.1",
+    [[
+        SELECT ABS(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function ABS()"
+    })
+
+test:do_catchsql_test(
+    "map-12.2",
+    [[
+        SELECT AVG(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function AVG()"
+    })
+
+test:do_catchsql_test(
+    "map-12.3",
+    [[
+        SELECT CHAR(m) FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function CHAR()"
+    })
+
+test:do_catchsql_test(
+    "map-12.4",
+    [[
+        SELECT CHARACTER_LENGTH(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "CHARACTER_LENGTH()"
+    })
+
+test:do_catchsql_test(
+    "map-12.5",
+    [[
+        SELECT CHAR_LENGTH(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "CHAR_LENGTH()"
+    })
+
+test:do_execsql_test(
+    "map-12.6",
+    [[
+        SELECT COALESCE(NULL, m) FROM t;
+    ]], {
+        {abc = 123},
+        {d = 4, e = 5, f = 6},
+    })
+
+test:do_execsql_test(
+    "map-12.7",
+    [[
+        SELECT COUNT(m) FROM t;
+    ]], {
+        2
+    })
+
+test:do_catchsql_test(
+    "map-12.8",
+    [[
+        SELECT GREATEST(1, m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "GREATEST()"
+    })
+
+test:do_catchsql_test(
+    "map-12.9",
+    [[
+        SELECT GROUP_CONCAT(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "GROUP_CONCAT()"
+    })
+
+test:do_catchsql_test(
+    "map-12.10",
+    [[
+        SELECT HEX(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function HEX()"
+    })
+
+test:do_execsql_test(
+    "map-12.11",
+    [[
+        SELECT IFNULL(m, 1) FROM t;
+    ]], {
+        {abc = 123},
+        {d = 4, e = 5, f = 6},
+    })
+
+test:do_catchsql_test(
+    "map-12.12",
+    [[
+        SELECT LEAST(1, m) FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function LEAST()"
+    })
+
+test:do_catchsql_test(
+    "map-12.13",
+    [[
+        SELECT LENGTH(m) FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function LENGTH()"
+    })
+
+test:do_catchsql_test(
+    "map-12.14",
+    [[
+        SELECT 'asd' LIKE m FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function LIKE()"
+    })
+
+test:do_execsql_test(
+    "map-12.15",
+    [[
+        SELECT LIKELIHOOD(m, 0.5e0) FROM t;
+    ]], {
+        {abc = 123},
+        {d = 4, e = 5, f = 6},
+    })
+
+test:do_execsql_test(
+    "map-12.16",
+    [[
+        SELECT LIKELY(m) FROM t;
+    ]], {
+        {abc = 123},
+        {d = 4, e = 5, f = 6},
+    })
+
+test:do_catchsql_test(
+    "map-12.17",
+    [[
+        SELECT LOWER(m) FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function LOWER()"
+    })
+
+test:do_catchsql_test(
+    "map-12.18",
+    [[
+        SELECT MAX(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function MAX()"
+    })
+
+test:do_catchsql_test(
+    "map-12.19",
+    [[
+        SELECT MIN(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function MIN()"
+    })
+
+test:do_catchsql_test(
+    "map-12.20",
+    [[
+        SELECT NULLIF(1, m) FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function NULLIF()"
+    })
+
+test:do_catchsql_test(
+    "map-12.21",
+    [[
+        SELECT POSITION('asd', m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "POSITION()"
+    })
+
+test:do_execsql_test(
+    "map-12.22",
+    [[
+        SELECT PRINTF(m) FROM t;
+    ]], {
+        '{"abc": 123}',
+        '{"d": 4, "f": 6, "e": 5}',
+    })
+
+test:do_execsql_test(
+    "map-12.23",
+    [[
+        SELECT QUOTE(m) FROM t;
+    ]], {
+        '{"abc": 123}',
+        '{"d": 4, "f": 6, "e": 5}',
+    })
+
+test:do_catchsql_test(
+    "map-12.24",
+    [[
+        SELECT RANDOMBLOB(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "RANDOMBLOB()"
+    })
+
+test:do_catchsql_test(
+    "map-12.25",
+    [[
+        SELECT REPLACE('asd', 'a', m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "REPLACE()"
+    })
+
+test:do_catchsql_test(
+    "map-12.26",
+    [[
+        SELECT ROUND(m) FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function ROUND()"
+    })
+
+test:do_catchsql_test(
+    "map-12.27",
+    [[
+        SELECT SOUNDEX(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "SOUNDEX()"
+    })
+
+test:do_catchsql_test(
+    "map-12.28",
+    [[
+        SELECT SUBSTR(m, 1, 1) FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function SUBSTR()"
+    })
+
+test:do_catchsql_test(
+    "map-12.29",
+    [[
+        SELECT SUM(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function SUM()"
+    })
+
+test:do_catchsql_test(
+    "map-12.30",
+    [[
+        SELECT TOTAL(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "TOTAL()"
+    })
+
+test:do_catchsql_test(
+    "map-12.31",
+    [[
+        SELECT TRIM(m) FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function TRIM()"
+    })
+
+test:do_execsql_test(
+    "map-12.32",
+    [[
+        SELECT TYPEOF(m) FROM t;
+    ]], {
+        "map", "map"
+    })
+
+test:do_catchsql_test(
+    "map-12.33",
+    [[
+        SELECT UNICODE(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "UNICODE()"
+    })
+
+test:do_execsql_test(
+    "map-12.34",
+    [[
+        SELECT UNLIKELY(m) FROM t;
+    ]], {
+        {abc = 123},
+        {d = 4, e = 5, f = 6},
+    })
+
+test:do_catchsql_test(
+    "map-12.35",
+    [[
+        SELECT UPPER(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function UPPER()"
+    })
+
+test:do_catchsql_test(
+    "map-12.36",
+    [[
+        SELECT UUID(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function UUID()"
+    })
+
+test:do_catchsql_test(
+    "map-12.37",
+    [[
+        SELECT ZEROBLOB(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function ZEROBLOB()"
+    })
+
+box.execute([[DROP TABLE t1;]])
+box.execute([[DROP TABLE t;]])
+
+test:finish_test()
-- 
2.25.1


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [Tarantool-patches] [PATCH v1 2/2] sql: introduce MAP() function
  2021-11-11 11:37 [Tarantool-patches] [PATCH v1 0/2] Introduce field type MAP to SQL Mergen Imeev via Tarantool-patches
  2021-11-11 11:37 ` [Tarantool-patches] [PATCH v1 1/2] sql: introduce field type MAP Mergen Imeev via Tarantool-patches
@ 2021-11-11 11:37 ` Mergen Imeev via Tarantool-patches
  2021-11-14 16:24   ` Vladislav Shpilevoy via Tarantool-patches
  2021-11-18 21:21 ` [Tarantool-patches] [PATCH v1 0/2] Introduce field type MAP to SQL Vladislav Shpilevoy via Tarantool-patches
  2 siblings, 1 reply; 8+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-11-11 11:37 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

Closes #4763

@TarantoolBot document
Title: SQL built-in function MAP()

The SQL built-in function MAP() is now available. It takes one or more
arguments of any type and returns a value of type MAP. The return value
uses the arguments that were in odd places as keys, and the next
argument after the key is treated as the value of that key. For an odd
number of arguments, the value of the last key is NULL. Only INTEGER,
STRING and UUID values can be keys.

Example:
```
tarantool> box.execute([[SELECT map(1, array('a', true), 'asd', x'31');]])
---
- metadata:
  - name: COLUMN_1
    type: map
  rows:
  - [{1: ['a', true], 'asd': '1'}]
...
```
---
 .../gh-4763-introduce-map-to-sql.md           |   4 +
 src/box/sql/func.c                            |  22 ++++
 src/box/sql/mem.c                             |  41 ++++++
 src/box/sql/mem.h                             |  17 +++
 src/box/sql/parse.y                           |   2 +-
 test/sql-tap/map.test.lua                     | 121 +++++++++++++++++-
 6 files changed, 205 insertions(+), 2 deletions(-)
 create mode 100644 changelogs/unreleased/gh-4763-introduce-map-to-sql.md

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..08f2dc538
--- /dev/null
+++ b/changelogs/unreleased/gh-4763-introduce-map-to-sql.md
@@ -0,0 +1,4 @@
+## feature/core
+
+ * Field type MAP is now available in SQL. The MAP() function can be used to
+   create values of type MAP in SQL (gh-4763).
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 8bf3e473d..2be73b278 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -252,6 +252,26 @@ func_array(struct sql_context *ctx, int argc, struct Mem *argv)
 	region_truncate(region, svp);
 }
 
+/**
+ * Implementation of the ARRAY() function.
+ *
+ * The ARRAY() function takes one or more ANY arguments and returns ARRAY value
+ * that contains all the arguments received, in the order in which they were
+ * received.
+ */
+static void
+func_map(struct sql_context *ctx, int argc, struct Mem *argv)
+{
+	assert(argc >= 1);
+	uint32_t size;
+	struct region *region = &fiber()->gc;
+	size_t svp = region_used(region);
+	char *array = mem_encode_map(argv, argc, &size, region);
+	if (array == NULL || mem_copy_map(ctx->pOut, array, size) != 0)
+		ctx->is_aborted = true;
+	region_truncate(region, svp);
+}
+
 static const unsigned char *
 mem_as_ustr(struct Mem *mem)
 {
@@ -1909,6 +1929,7 @@ static struct sql_func_dictionary dictionaries[] = {
 	{"LIKELY", 1, 1, SQL_FUNC_UNLIKELY, true, 0, NULL},
 	{"LOWER", 1, 1, SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL, true, 0,
 	 NULL},
+	{"MAP", 1, SQL_MAX_FUNCTION_ARG, 0, true, 0, NULL},
 	{"MAX", 1, 1, SQL_FUNC_MAX | SQL_FUNC_AGG | SQL_FUNC_NEEDCOLL, false, 0,
 	 NULL},
 	{"MIN", 1, 1, SQL_FUNC_MIN | SQL_FUNC_AGG | SQL_FUNC_NEEDCOLL, false, 0,
@@ -2034,6 +2055,7 @@ static struct sql_func_definition definitions[] = {
 	 NULL},
 	{"LOWER", 1, {FIELD_TYPE_STRING}, FIELD_TYPE_STRING, LowerICUFunc,
 	 NULL},
+	{"MAP", -1, {field_type_MAX}, FIELD_TYPE_MAP, func_map, NULL},
 
 	{"MAX", 1, {FIELD_TYPE_INTEGER}, FIELD_TYPE_INTEGER, step_minmax, NULL},
 	{"MAX", 1, {FIELD_TYPE_DOUBLE}, FIELD_TYPE_DOUBLE, step_minmax, NULL},
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index f5e8c11cc..b28423086 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -3211,6 +3211,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)
+{
+	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 ((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");
+			return NULL;
+		}
+		mem_encode_to_stream(key, &stream);
+		mem_encode_to_stream(value, &stream);
+	}
+	if (count % 2 == 1) {
+		mem_encode_to_stream(&mems[count - 1], &stream);
+		mpstream_encode_nil(&stream);
+	}
+	mpstream_flush(&stream);
+	if (is_error) {
+		diag_set(OutOfMemory, stream.pos - stream.buf,
+			 "mpstream_flush", "stream");
+		return NULL;
+	}
+	*size = region_used(region) - used;
+	char *map = region_join(region, *size);
+	if (map == NULL) {
+		diag_set(OutOfMemory, *size, "region_join", "map");
+		return NULL;
+	}
+	return map;
+}
+
 /**
  * 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 7ddf81236..64f978a1f 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -1019,3 +1019,20 @@ mem_encode_to_stream(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.
+ * If the number of MEMs in the array is odd, the value of the last key will be
+ * NULL.
+ *
+ * @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 e27c1804b..1b2c1597c 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -268,7 +268,7 @@ columnlist ::= tcons.
   CONFLICT DEFERRED END ENGINE FAIL
   IGNORE INITIALLY INSTEAD NO MATCH PLAN
   QUERY KEY OFFSET RAISE RELEASE REPLACE RESTRICT
-  RENAME CTIME_KW IF ENABLE DISABLE UUID ARRAY
+  RENAME CTIME_KW IF ENABLE DISABLE UUID ARRAY MAP
   .
 %wildcard WILDCARD.
 
diff --git a/test/sql-tap/map.test.lua b/test/sql-tap/map.test.lua
index 2be82db61..f487fef55 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(123)
 
 box.schema.func.create('M1', {
     language = 'Lua',
@@ -981,6 +981,125 @@ test:do_catchsql_test(
         1, "Failed to execute SQL statement: wrong arguments for function ZEROBLOB()"
     })
 
+-- Make sure ARRAY() function works as intended.
+test:do_execsql_test(
+    "map-13.1",
+    [[
+        SELECT MAP('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 MAP('q', 1, 'w', true, 'e', 1.5e0, 'r', ARRAY('asd', x'32'),
+                   't', 1234.0);
+    ]], {
+        {w = true, e = 1.5, r = {'asd', '2'}, t = require('decimal').new(1234),
+         q = 1}
+    })
+
+test:do_execsql_test(
+    "map-13.3",
+    [[
+        SELECT typeof(MAP(1, 1));
+    ]], {
+        "map"
+    })
+
+-- Make sure MAP() accepts only INTEGER, STRING and UUID as keys.
+test:do_execsql_test(
+    "map-13.4",
+    [[
+        SELECT MAP(1, 1);
+    ]], {
+        {[1] = 1}
+    })
+
+test:do_execsql_test(
+    "map-13.5",
+    [[
+        SELECT MAP(-1, 1);
+    ]], {
+        {[-1] = 1}
+    })
+
+test:do_execsql_test(
+    "map-13.6",
+    [[
+        SELECT MAP('a', 1);
+    ]], {
+        {a = 1}
+    })
+
+test:do_execsql_test(
+    "map-13.6",
+    [[
+        SELECT typeof(MAP(UUID(), 1));
+    ]], {
+        "map"
+    })
+
+test:do_catchsql_test(
+    "map-13.7",
+    [[
+        SELECT MAP(1.5e0, 1);
+    ]], {
+        1,
+        "Type mismatch: can not convert double(1.5) to integer, string or uuid"
+    })
+
+test:do_catchsql_test(
+    "map-13.8",
+    [[
+        SELECT MAP(1.5, 1);
+    ]], {
+        1,
+        "Type mismatch: can not convert decimal(1.5) to integer, string or uuid"
+    })
+
+test:do_catchsql_test(
+    "map-13.9",
+    [[
+        SELECT MAP(x'33', 1);
+    ]], {
+        1,
+        "Type mismatch: can not convert varbinary(x'33') to integer, string "..
+        "or uuid"
+    })
+
+test:do_catchsql_test(
+    "map-13.10",
+    [[
+        SELECT MAP(ARRAY(1, 2, 3), 1);
+    ]], {
+        1,
+        "Type mismatch: can not convert array([1, 2, 3]) to integer, string or uuid"
+    })
+
+test:do_catchsql_test(
+    "map-13.11",
+    [[
+        SELECT MAP(MAP('a', 1), 1);
+    ]], {
+        1,
+        'Type mismatch: can not convert map({"a": 1}) to integer, string '..
+        'or uuid'
+    })
+
+-- Metatypes with base types INTEGER, STRING and UUID can also be keys.
+test:do_execsql_test(
+    "map-13.12",
+    [[
+        SELECT MAP(CAST(1 AS ANY), 1);
+    ]], {
+        {[1] = 1}
+    })
+
 box.execute([[DROP TABLE t1;]])
 box.execute([[DROP TABLE t;]])
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Tarantool-patches] [PATCH v1 1/2] sql: introduce field type MAP
  2021-11-11 11:37 ` [Tarantool-patches] [PATCH v1 1/2] sql: introduce field type MAP Mergen Imeev via Tarantool-patches
@ 2021-11-14 16:24   ` Vladislav Shpilevoy via Tarantool-patches
  2021-11-15 16:45     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 8+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-11-14 16:24 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for the patch!

See 5 comments below.

On 11.11.2021 12:37, imeevma@tarantool.org wrote:
> This patch introduces MAP to SQL. After this patch, all SQL operations
> and built-in functions should work correctly with MAP values. However,
> there is currently no way to create MAP values using only SQL tools.
> 
> Part of #4763
> 
> @TarantoolBot document
> Title: Field type MAP in SQL
> 
> Properties of type MAP in SQL:
> 1) a value ofttype MAP can be implicitly and explicitly cast only to
> ANY;

1. ofttype -> of type.

> 2) only a value of type ANY with primitive type MAP can be explicitly
> cast to MAP;
> 3) a value of any other type cannot be implicitly cast to MAP;
> 4) a value of type MAP cannot participate in arithmetic, bitwise,
> comparison, and concationation operations.

2. concationation -> concatenation.

3. You need to add a changelog file. For the arrays patch too.

> @@ -3258,6 +3268,19 @@ port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
>  				luaL_setarrayhint(L, -1);
>  			return;
>  		}
> +		case MEM_TYPE_MAP: {
> +			const char *data = mem->z;
> +			uint32_t size = mp_decode_map(&data);
> +			lua_createtable(L, 0, size);
> +			for (uint32_t i = 0; i < size; i++) {
> +				luamp_decode(L, luaL_msgpack_default, &data);
> +				luamp_decode(L, luaL_msgpack_default, &data);
> +				lua_settable(L, -3);
> +			}
> +			if (luaL_msgpack_default->decode_save_metatables)
> +				luaL_setmaphint(L, -1);

4. The same as for arrays. For both of them you can call luamp_decode
on the root.

> diff --git a/test/sql-tap/map.test.lua b/test/sql-tap/map.test.lua
> new file mode 100755
> index 000000000..2be82db61
> --- /dev/null
> +++ b/test/sql-tap/map.test.lua
> @@ -0,0 +1,987 @@
> +#!/usr/bin/env tarantool
> +local test = require("sqltester")
> +test:plan(110)
> +
> +box.schema.func.create('M1', {
> +    language = 'Lua',
> +    body = 'function(a, b) return {[tostring(a)] = b} end',
> +    returns = 'map',
> +    param_list = {'any', 'any'},
> +    exports = {'LUA', 'SQL'}
> +});
> +
> +box.schema.func.create('M2', {
> +    language = 'Lua',
> +    body = 'function(a, b, c, d) return '..
> +           '{[tostring(a)] = b, [tostring(c)] = d} end',
> +    returns = 'map',
> +    param_list = {'any', 'any', 'any', 'any'},
> +    exports = {'LUA', 'SQL'}
> +});
> +
> +box.schema.func.create('M3', {
> +    language = 'Lua',
> +    body = 'function(a, b, c, d, e, f) return '..
> +           '{[tostring(a)] = b, [tostring(c)] = d, [tostring(e)] = f} end',

5. Why do you need the tostrings? Keys are free to have any type in
MessagePack. In Lua too. Can you add tests for non-string keys?

> +    returns = 'map',
> +    param_list = {'any', 'any', 'any', 'any', 'any', 'any'},
> +    exports = {'LUA', 'SQL'}
> +});

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Tarantool-patches] [PATCH v1 2/2] sql: introduce MAP() function
  2021-11-11 11:37 ` [Tarantool-patches] [PATCH v1 2/2] sql: introduce MAP() function Mergen Imeev via Tarantool-patches
@ 2021-11-14 16:24   ` Vladislav Shpilevoy via Tarantool-patches
  2021-11-15 16:46     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 8+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-11-14 16:24 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for the patch!

The same as for the arrays. I am not sure this functions will be
very helpful. MAP is still not usable because can't get/set individual
values. And the function will be obsolete when proper syntax for maps
is introduced.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Tarantool-patches] [PATCH v1 1/2] sql: introduce field type MAP
  2021-11-14 16:24   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-11-15 16:45     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 8+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-11-15 16:45 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answers, diff and new patch below. There
will be missing part of diff since I applied it during resolution of
merge-conflict. Also, I dropped second patch on the branch.

On Sun, Nov 14, 2021 at 05:24:21PM +0100, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> See 5 comments below.
> 
> On 11.11.2021 12:37, imeevma@tarantool.org wrote:
> > This patch introduces MAP to SQL. After this patch, all SQL operations
> > and built-in functions should work correctly with MAP values. However,
> > there is currently no way to create MAP values using only SQL tools.
> > 
> > Part of #4763
> > 
> > @TarantoolBot document
> > Title: Field type MAP in SQL
> > 
> > Properties of type MAP in SQL:
> > 1) a value ofttype MAP can be implicitly and explicitly cast only to
> > ANY;
> 
> 1. ofttype -> of type.
> 
Fixed.

> > 2) only a value of type ANY with primitive type MAP can be explicitly
> > cast to MAP;
> > 3) a value of any other type cannot be implicitly cast to MAP;
> > 4) a value of type MAP cannot participate in arithmetic, bitwise,
> > comparison, and concationation operations.
> 
> 2. concationation -> concatenation.
> 
Fixed.

> 3. You need to add a changelog file. For the arrays patch too.
> 
Added here and in patch for ARRAY.

> > @@ -3258,6 +3268,19 @@ port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
> >  				luaL_setarrayhint(L, -1);
> >  			return;
> >  		}
> > +		case MEM_TYPE_MAP: {
> > +			const char *data = mem->z;
> > +			uint32_t size = mp_decode_map(&data);
> > +			lua_createtable(L, 0, size);
> > +			for (uint32_t i = 0; i < size; i++) {
> > +				luamp_decode(L, luaL_msgpack_default, &data);
> > +				luamp_decode(L, luaL_msgpack_default, &data);
> > +				lua_settable(L, -3);
> > +			}
> > +			if (luaL_msgpack_default->decode_save_metatables)
> > +				luaL_setmaphint(L, -1);
> 
> 4. The same as for arrays. For both of them you can call luamp_decode
> on the root.
> 
Fixed. This part will be missing from diff since I applied it during rebase to
branch with ARRAY.

> > diff --git a/test/sql-tap/map.test.lua b/test/sql-tap/map.test.lua
> > new file mode 100755
> > index 000000000..2be82db61
> > --- /dev/null
> > +++ b/test/sql-tap/map.test.lua
> > @@ -0,0 +1,987 @@
> > +#!/usr/bin/env tarantool
> > +local test = require("sqltester")
> > +test:plan(110)
> > +
> > +box.schema.func.create('M1', {
> > +    language = 'Lua',
> > +    body = 'function(a, b) return {[tostring(a)] = b} end',
> > +    returns = 'map',
> > +    param_list = {'any', 'any'},
> > +    exports = {'LUA', 'SQL'}
> > +});
> > +
> > +box.schema.func.create('M2', {
> > +    language = 'Lua',
> > +    body = 'function(a, b, c, d) return '..
> > +           '{[tostring(a)] = b, [tostring(c)] = d} end',
> > +    returns = 'map',
> > +    param_list = {'any', 'any', 'any', 'any'},
> > +    exports = {'LUA', 'SQL'}
> > +});
> > +
> > +box.schema.func.create('M3', {
> > +    language = 'Lua',
> > +    body = 'function(a, b, c, d, e, f) return '..
> > +           '{[tostring(a)] = b, [tostring(c)] = d, [tostring(e)] = f} end',
> 
> 5. Why do you need the tostrings? Keys are free to have any type in
> MessagePack. In Lua too. Can you add tests for non-string keys?
> 
I did this to avoid calling metatable(). Fixed.

> > +    returns = 'map',
> > +    param_list = {'any', 'any', 'any', 'any', 'any', 'any'},
> > +    exports = {'LUA', 'SQL'}
> > +});


Diff:

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 2ec218d1d..e25170aa8 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1169,14 +1169,6 @@ bool_to_str0(struct Mem *mem)
 	return mem_copy_str0(mem, str);
 }
 
-static inline int
-map_to_str0(struct Mem *mem)
-{
-	assert(mem->type == MEM_TYPE_MAP);
-	const char *str = mp_str(mem->z);
-	return mem_copy_str0(mem, str);
-}
-
 static inline int
 uuid_to_str0(struct Mem *mem)
 {
diff --git a/test/sql-tap/map.test.lua b/test/sql-tap/map.test.lua
index ec6d9023c..1afbb2b1d 100755
--- a/test/sql-tap/map.test.lua
+++ b/test/sql-tap/map.test.lua
@@ -4,7 +4,8 @@ test:plan(110)
 
 box.schema.func.create('M1', {
     language = 'Lua',
-    body = 'function(a, b) return {[tostring(a)] = b} end',
+    body = 'function(a, b) local m = {[a] = b} '..
+           'return setmetatable(m, { __serialize = "map" }) end',
     returns = 'map',
     param_list = {'any', 'any'},
     exports = {'LUA', 'SQL'}
@@ -12,8 +13,8 @@ box.schema.func.create('M1', {
 
 box.schema.func.create('M2', {
     language = 'Lua',
-    body = 'function(a, b, c, d) return '..
-           '{[tostring(a)] = b, [tostring(c)] = d} end',
+    body = 'function(a, b, c, d) local m = {[a] = b, [c] = d} '..
+           'return setmetatable(m, { __serialize = "map" }) end',
     returns = 'map',
     param_list = {'any', 'any', 'any', 'any'},
     exports = {'LUA', 'SQL'}
@@ -21,8 +22,8 @@ box.schema.func.create('M2', {
 
 box.schema.func.create('M3', {
     language = 'Lua',
-    body = 'function(a, b, c, d, e, f) return '..
-           '{[tostring(a)] = b, [tostring(c)] = d, [tostring(e)] = f} end',
+    body = 'function(a, b, c, d, e, f) local m = {[a] = b, [c] = d, [e] = f} '..
+           'return setmetatable(m, { __serialize = "map" }) end',
     returns = 'map',
     param_list = {'any', 'any', 'any', 'any', 'any', 'any'},
     exports = {'LUA', 'SQL'}
@@ -293,7 +294,7 @@ test:do_execsql_test(
         SELECT a FROM t1 WHERE a IS NOT NULL;
     ]], {
         {a = 1},
-        {['1'] = 2}
+        {[1] = 2}
     })
 
 test:do_catchsql_test(
@@ -301,7 +302,7 @@ test:do_catchsql_test(
     [[
         INSERT INTO t1(g) VALUES(m1(1, 2));
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to unsigned'
+        1, 'Type mismatch: can not convert map({1: 2}) to unsigned'
     })
 
 test:do_catchsql_test(
@@ -309,7 +310,7 @@ test:do_catchsql_test(
     [[
         INSERT INTO t1(t) VALUES(m1(1, 2));
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to string'
+        1, 'Type mismatch: can not convert map({1: 2}) to string'
     })
 
 test:do_catchsql_test(
@@ -317,7 +318,7 @@ test:do_catchsql_test(
     [[
         INSERT INTO t1(n) VALUES(m1(1, 2));
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to number'
+        1, 'Type mismatch: can not convert map({1: 2}) to number'
     })
 
 test:do_catchsql_test(
@@ -325,7 +326,7 @@ test:do_catchsql_test(
     [[
         INSERT INTO t1(f) VALUES(m1(1, 2));
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to double'
+        1, 'Type mismatch: can not convert map({1: 2}) to double'
     })
 
 test:do_catchsql_test(
@@ -333,7 +334,7 @@ test:do_catchsql_test(
     [[
         INSERT INTO t1(i) VALUES(m1(1, 2));
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to integer'
+        1, 'Type mismatch: can not convert map({1: 2}) to integer'
     })
 
 test:do_catchsql_test(
@@ -341,7 +342,7 @@ test:do_catchsql_test(
     [[
         INSERT INTO t1(b) VALUES(m1(1, 2));
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to boolean'
+        1, 'Type mismatch: can not convert map({1: 2}) to boolean'
     })
 
 test:do_catchsql_test(
@@ -349,7 +350,7 @@ test:do_catchsql_test(
     [[
         INSERT INTO t1(v) VALUES(m1(1, 2));
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to varbinary'
+        1, 'Type mismatch: can not convert map({1: 2}) to varbinary'
     })
 
 test:do_catchsql_test(
@@ -357,7 +358,7 @@ test:do_catchsql_test(
     [[
         INSERT INTO t1(s) VALUES(m1(1, 2));
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to scalar'
+        1, 'Type mismatch: can not convert map({1: 2}) to scalar'
     })
 
 test:do_catchsql_test(
@@ -365,7 +366,7 @@ test:do_catchsql_test(
     [[
         INSERT INTO t1(d) VALUES(m1(1, 2));
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to decimal'
+        1, 'Type mismatch: can not convert map({1: 2}) to decimal'
     })
 
 test:do_catchsql_test(
@@ -373,7 +374,7 @@ test:do_catchsql_test(
     [[
         INSERT INTO t1(u) VALUES(m1(1, 2));
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to uuid'
+        1, 'Type mismatch: can not convert map({1: 2}) to uuid'
     })
 
 -- Make sure nothing can be implicitly cast to MAP.
@@ -475,7 +476,7 @@ test:do_catchsql_test(
     [[
         SELECT m1(1, 2) + 1;
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to integer, '..
+        1, 'Type mismatch: can not convert map({1: 2}) to integer, '..
            "decimal or double"
     })
 
@@ -484,7 +485,7 @@ test:do_catchsql_test(
     [[
         SELECT m1(1, 2) - 1;
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to integer, '..
+        1, 'Type mismatch: can not convert map({1: 2}) to integer, '..
            "decimal or double"
     })
 
@@ -493,7 +494,7 @@ test:do_catchsql_test(
     [[
         SELECT m1(1, 2) * 1;
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to integer, '..
+        1, 'Type mismatch: can not convert map({1: 2}) to integer, '..
            "decimal or double"
     })
 
@@ -502,7 +503,7 @@ test:do_catchsql_test(
     [[
         SELECT m1(1, 2) / 1;
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to integer, '..
+        1, 'Type mismatch: can not convert map({1: 2}) to integer, '..
            "decimal or double"
     })
 
@@ -511,7 +512,7 @@ test:do_catchsql_test(
     [[
         SELECT m1(1, 2) % 1;
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to integer'
+        1, 'Type mismatch: can not convert map({1: 2}) to integer'
     })
 
 test:do_catchsql_test(
@@ -519,7 +520,7 @@ test:do_catchsql_test(
     [[
         SELECT m1(1, 2) >> 1;
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to unsigned'
+        1, 'Type mismatch: can not convert map({1: 2}) to unsigned'
     })
 
 test:do_catchsql_test(
@@ -527,7 +528,7 @@ test:do_catchsql_test(
     [[
         SELECT m1(1, 2) << 1;
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to unsigned'
+        1, 'Type mismatch: can not convert map({1: 2}) to unsigned'
     })
 
 test:do_catchsql_test(
@@ -535,7 +536,7 @@ test:do_catchsql_test(
     [[
         SELECT m1(1, 2) | 1;
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to unsigned'
+        1, 'Type mismatch: can not convert map({1: 2}) to unsigned'
     })
 
 test:do_catchsql_test(
@@ -543,7 +544,7 @@ test:do_catchsql_test(
     [[
         SELECT m1(1, 2) & 1;
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to unsigned'
+        1, 'Type mismatch: can not convert map({1: 2}) to unsigned'
     })
 
 test:do_catchsql_test(
@@ -551,7 +552,7 @@ test:do_catchsql_test(
     [[
         SELECT ~m1(1, 2);
     ]], {
-        1, 'Type mismatch: can not convert map({"1": 2}) to unsigned'
+        1, 'Type mismatch: can not convert map({1: 2}) to unsigned'
     })
 
 test:do_catchsql_test(
@@ -559,7 +560,7 @@ test:do_catchsql_test(
     [[
         SELECT m1(1, 2) || 'asd';
     ]], {
-        1, 'Inconsistent types: expected string or varbinary got map({"1": 2})'
+        1, 'Inconsistent types: expected string or varbinary got map({1: 2})'
     })
 
 -- Make sure MAP is not comparable.


New patch:

commit fec5aa8d4f90609bc210d557d6eb15afd4506f48
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Wed Nov 3 13:42:53 2021 +0300

    sql: introduce field type MAP
    
    This patch introduces MAP to SQL. After this patch, all SQL operations
    and built-in functions should work correctly with MAP values. However,
    there is currently no way to create MAP values using only SQL tools.
    
    Part of #4763
    
    @TarantoolBot document
    Title: Field type MAP in SQL
    
    Properties of type MAP in SQL:
    1) a value of type MAP can be implicitly and explicitly cast only to
    ANY;
    2) only a value of type ANY with primitive type MAP can be explicitly
    cast to MAP;
    3) a value of any other type cannot be implicitly cast to MAP;
    4) a value of type MAP cannot participate in arithmetic, bitwise,
    comparison, and concatination operations.

diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
index c7e7d4e8d..30bd6f0d8 100644
--- a/extra/mkkeywordhash.c
+++ b/extra/mkkeywordhash.c
@@ -125,6 +125,7 @@ static Keyword aKeywordTable[] = {
   { "LEFT",                   "TK_JOIN_KW",     true  },
   { "LIKE",                   "TK_LIKE_KW",     true  },
   { "LIMIT",                  "TK_LIMIT",       false },
+  { "MAP",                    "TK_MAP",         true  },
   { "MATCH",                  "TK_MATCH",       true  },
   { "NATURAL",                "TK_JOIN_KW",     true  },
   { "NO",                     "TK_NO",          false },
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 20d62bce7..cfc5149c9 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1370,6 +1370,7 @@ quoteFunc(struct sql_context *context, int argc, const struct Mem *argv)
 			context->is_aborted = true;
 		break;
 	}
+	case MEM_TYPE_MAP:
 	case MEM_TYPE_ARRAY: {
 		char *buf = NULL;
 		int size = mp_snprint(buf, 0, argv[0].z) + 1;
@@ -1383,8 +1384,7 @@ quoteFunc(struct sql_context *context, int argc, const struct Mem *argv)
 		mem_set_str0_allocated(context->pOut, buf);
 		break;
 	}
-	case MEM_TYPE_BIN:
-	case MEM_TYPE_MAP: {
+	case MEM_TYPE_BIN: {
 		const char *zBlob = argv[0].z;
 		int nBlob = argv[0].n;
 		uint32_t size = 2 * nBlob + 3;
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index e18bb24a2..e25170aa8 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -199,6 +199,8 @@ mem_type_class_to_str(const struct Mem *mem)
 		return "uuid";
 	case MEM_TYPE_ARRAY:
 		return "array";
+	case MEM_TYPE_MAP:
+		return "map";
 	default:
 		break;
 	}
@@ -487,6 +489,12 @@ mem_set_map_allocated(struct Mem *mem, char *value, uint32_t size)
 	set_msgpack_value(mem, value, size, 0, MEM_TYPE_MAP);
 }
 
+int
+mem_copy_map(struct Mem *mem, const char *value, uint32_t size)
+{
+	return mem_copy_bytes(mem, value, size, MEM_TYPE_MAP);
+}
+
 void
 mem_set_array_ephemeral(struct Mem *mem, char *value, uint32_t size)
 {
@@ -1161,14 +1169,6 @@ bool_to_str0(struct Mem *mem)
 	return mem_copy_str0(mem, str);
 }
 
-static inline int
-map_to_str0(struct Mem *mem)
-{
-	assert(mem->type == MEM_TYPE_MAP);
-	const char *str = mp_str(mem->z);
-	return mem_copy_str0(mem, str);
-}
-
 static inline int
 uuid_to_str0(struct Mem *mem)
 {
@@ -1270,8 +1270,6 @@ mem_to_str(struct Mem *mem)
 		return bool_to_str0(mem);
 	case MEM_TYPE_BIN:
 		return bin_to_str(mem);
-	case MEM_TYPE_MAP:
-		return map_to_str0(mem);
 	case MEM_TYPE_UUID:
 		return uuid_to_str0(mem);
 	case MEM_TYPE_DEC:
@@ -1360,6 +1358,11 @@ mem_cast_explicit(struct Mem *mem, enum field_type type)
 			return -1;
 		mem->flags &= ~MEM_Any;
 		return 0;
+	case FIELD_TYPE_MAP:
+		if (mem->type != MEM_TYPE_MAP)
+			return -1;
+		mem->flags &= ~MEM_Any;
+		return 0;
 	case FIELD_TYPE_SCALAR:
 		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
 			return -1;
@@ -3083,9 +3086,9 @@ port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
 			break;
 		case MEM_TYPE_STR:
 		case MEM_TYPE_BIN:
-		case MEM_TYPE_MAP:
 			lua_pushlstring(L, mem->z, mem->n);
 			break;
+		case MEM_TYPE_MAP:
 		case MEM_TYPE_ARRAY:
 			luamp_decode(L, luaL_msgpack_default,
 				     (const char **)&mem->z);
@@ -3220,8 +3223,10 @@ port_lua_get_vdbemem(struct port *base, uint32_t *size)
 					 field.sval.len) != 0)
 				goto error;
 			break;
+		case MP_MAP:
 		case MP_ARRAY: {
 			size_t used = region_used(region);
+			bool is_map = field.type == MP_MAP;
 			struct mpstream stream;
 			bool is_error = false;
 			mpstream_init(&stream, region, region_reserve_cb,
@@ -3244,7 +3249,9 @@ port_lua_get_vdbemem(struct port *base, uint32_t *size)
 					 "raw");
 				goto error;
 			}
-			if (mem_copy_array(&val[i], raw, size) != 0)
+			int rc = is_map ? mem_copy_map(&val[i], raw, size) :
+				 mem_copy_array(&val[i], raw, size);
+			if (rc != 0)
 				goto error;
 			region_truncate(region, used);
 			break;
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 41e8d8fa6..11834db20 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -398,6 +398,10 @@ mem_set_map_static(struct Mem *mem, char *value, uint32_t size);
 void
 mem_set_map_allocated(struct Mem *mem, char *value, uint32_t size);
 
+/** Copy MAP value to a newly allocated memory. The MEM type becomes MAP. */
+int
+mem_copy_map(struct Mem *mem, const char *value, uint32_t size);
+
 /**
  * Clear MEM and set it to ARRAY. The binary value belongs to another object.
  * The binary value must be msgpack of ARRAY type.
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index c93125a8b..548004252 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -1843,6 +1843,7 @@ typedef(A) ::= VARBINARY . { A.type = FIELD_TYPE_VARBINARY; }
 typedef(A) ::= UUID . { A.type = FIELD_TYPE_UUID; }
 typedef(A) ::= ANY . { A.type = FIELD_TYPE_ANY; }
 typedef(A) ::= ARRAY . { A.type = FIELD_TYPE_ARRAY; }
+typedef(A) ::= MAP . { A.type = FIELD_TYPE_MAP; }
 
 /**
  * Time-like types are temporary disabled, until they are
diff --git a/test/box/function1.result b/test/box/function1.result
index a1c89850d..334417be7 100644
--- a/test/box/function1.result
+++ b/test/box/function1.result
@@ -524,11 +524,6 @@ box.execute('SELECT lua(\'return 1 + 1\')')
   rows:
   - [2]
 ...
-box.execute('SELECT lua(\'return box.cfg\')')
----
-- null
-- 'Failed to execute SQL statement: Unsupported type passed from Lua'
-...
 box.execute('SELECT lua(\'return box.cfg()\')')
 ---
 - null
diff --git a/test/box/function1.test.lua b/test/box/function1.test.lua
index e635b6e18..5b762d08a 100644
--- a/test/box/function1.test.lua
+++ b/test/box/function1.test.lua
@@ -187,7 +187,6 @@ box.func.SUMMARIZE:drop()
 -- gh-4113: Valid method to use Lua from SQL
 --
 box.execute('SELECT lua(\'return 1 + 1\')')
-box.execute('SELECT lua(\'return box.cfg\')')
 box.execute('SELECT lua(\'return box.cfg()\')')
 box.execute('SELECT lua(\'return box.cfg.memtx_memory\')')
 
diff --git a/test/sql-tap/map.test.lua b/test/sql-tap/map.test.lua
new file mode 100755
index 000000000..1afbb2b1d
--- /dev/null
+++ b/test/sql-tap/map.test.lua
@@ -0,0 +1,988 @@
+#!/usr/bin/env tarantool
+local test = require("sqltester")
+test:plan(110)
+
+box.schema.func.create('M1', {
+    language = 'Lua',
+    body = 'function(a, b) local m = {[a] = b} '..
+           'return setmetatable(m, { __serialize = "map" }) end',
+    returns = 'map',
+    param_list = {'any', 'any'},
+    exports = {'LUA', 'SQL'}
+});
+
+box.schema.func.create('M2', {
+    language = 'Lua',
+    body = 'function(a, b, c, d) local m = {[a] = b, [c] = d} '..
+           'return setmetatable(m, { __serialize = "map" }) end',
+    returns = 'map',
+    param_list = {'any', 'any', 'any', 'any'},
+    exports = {'LUA', 'SQL'}
+});
+
+box.schema.func.create('M3', {
+    language = 'Lua',
+    body = 'function(a, b, c, d, e, f) local m = {[a] = b, [c] = d, [e] = f} '..
+           'return setmetatable(m, { __serialize = "map" }) end',
+    returns = 'map',
+    param_list = {'any', 'any', 'any', 'any', 'any', 'any'},
+    exports = {'LUA', 'SQL'}
+});
+
+-- Make sure it is possible to create tables with field type MAP.
+test:do_execsql_test(
+    "map-1",
+    [[
+        CREATE TABLE t (i INT PRIMARY KEY AUTOINCREMENT, m MAP);
+    ]], {
+    })
+
+box.space.T:insert({0, {a = 1, b = 2}})
+
+-- Make sure it is possible to select from MAP field.
+test:do_execsql_test(
+    "map-2",
+    [[
+        SELECT i, m FROM t;
+    ]], {
+        0, {a = 1, b = 2},
+    })
+
+-- Make sure it is possible to insert into MAP field.
+test:do_execsql_test(
+    "map-3",
+    [[
+        INSERT INTO t(m) VALUES(NULL);
+        INSERT INTO t(m) VALUES(m1('a', 1));
+        INSERT INTO t(m) VALUES(m2('b', 2, 'c', 3));
+        INSERT INTO t(m) VALUES(m3('d', 4, 'e', 5, 'f', 6));
+        SELECT i, m FROM t;
+    ]], {
+        0, {a = 1, b = 2},
+        1, "",
+        2, {a = 1},
+        3, {b = 2, c = 3},
+        4, {d = 4, e = 5, f = 6},
+    })
+
+-- Make sure it is possible to delete from MAP field.
+test:do_execsql_test(
+    "map-4",
+    [[
+        DELETE FROM t WHERE i < 3;
+        SELECT i, m FROM t;
+    ]], {
+        3, {b = 2, c = 3},
+        4, {d = 4, e = 5, f = 6},
+    })
+
+-- Make sure it is possible to update MAP field.
+test:do_execsql_test(
+    "map-5",
+    [[
+        UPDATE t SET m = m1('abc', 123) WHERE i = 3;
+        SELECT i, m FROM t;
+    ]], {
+        3, {abc = 123},
+        4, {d = 4, e = 5, f = 6},
+    })
+
+-- Make sure MAP can only be explicitly cast to ANY.
+test:do_execsql_test(
+    "map-6.1",
+    [[
+        SELECT CAST(m AS ANY) FROM t;
+    ]], {
+        {abc = 123},
+        {d = 4, e = 5, f = 6},
+    })
+
+test:do_catchsql_test(
+    "map-6.2",
+    [[
+        SELECT CAST(m AS UNSIGNED) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to unsigned'
+    })
+
+test:do_catchsql_test(
+    "map-6.3",
+    [[
+        SELECT CAST(m AS STRING) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to string'
+    })
+
+test:do_catchsql_test(
+    "map-6.4",
+    [[
+        SELECT CAST(m AS NUMBER) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to number'
+    })
+
+test:do_catchsql_test(
+    "map-6.5",
+    [[
+        SELECT CAST(m AS DOUBLE) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to double'
+    })
+
+test:do_catchsql_test(
+    "map-6.6",
+    [[
+        SELECT CAST(m AS INTEGER) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to integer'
+    })
+
+test:do_catchsql_test(
+    "map-6.7",
+    [[
+        SELECT CAST(m AS BOOLEAN) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to boolean'
+    })
+
+test:do_catchsql_test(
+    "map-6.8",
+    [[
+        SELECT CAST(m AS VARBINARY) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to varbinary'
+    })
+
+test:do_catchsql_test(
+    "map-6.9",
+    [[
+        SELECT CAST(m AS SCALAR) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to scalar'
+    })
+
+test:do_catchsql_test(
+    "map-6.10",
+    [[
+        SELECT CAST(m AS DECIMAL) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to decimal'
+    })
+
+test:do_catchsql_test(
+    "map-6.11",
+    [[
+        SELECT CAST(m AS UUID) FROM t;
+    ]], {
+        1, 'Type mismatch: can not convert map({"abc": 123}) to uuid'
+    })
+
+box.execute([[CREATE TABLE t1 (id INTEGER PRIMARY KEY AUTOINCREMENT, a ANY,
+                               g UNSIGNED, t STRING, n NUMBER, f DOUBLE,
+                               i INTEGER, b BOOLEAN, v VARBINARY, s SCALAR,
+                               d DECIMAL, u UUID);]])
+box.execute([[INSERT INTO t1 VALUES(1, m1('a', 1), 1, '1', 1, 1, 1, true, ]]..
+            [[x'31', 1, 1, ]]..
+            [[CAST('11111111-1111-1111-1111-111111111111' AS UUID))]])
+
+--
+-- Make sure that only ANY value can be explicitly cast to MAP if the value
+-- contains MAP.
+--
+test:do_execsql_test(
+    "map-7.1",
+    [[
+        SELECT CAST(a AS MAP) FROM t1;
+    ]], {
+        {a = 1}
+    })
+
+test:do_catchsql_test(
+    "map-7.2",
+    [[
+        SELECT CAST(g AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert integer(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.3",
+    [[
+        SELECT CAST(t AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert string('1') to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.4",
+    [[
+        SELECT CAST(n AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert number(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.5",
+    [[
+        SELECT CAST(f AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert double(1.0) to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.6",
+    [[
+        SELECT CAST(i AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert integer(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.7",
+    [[
+        SELECT CAST(b AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert boolean(TRUE) to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.8",
+    [[
+        SELECT CAST(v AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert varbinary(x'31') to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.9",
+    [[
+        SELECT CAST(s AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert scalar(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.10",
+    [[
+        SELECT CAST(d AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert decimal(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.11",
+    [[
+        SELECT CAST(u AS MAP) FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert "..
+           "uuid(11111111-1111-1111-1111-111111111111) to map"
+    })
+
+test:do_catchsql_test(
+    "map-7.12",
+    [[
+        SELECT CAST(CAST(1 AS ANY) AS MAP);
+    ]], {
+        1, "Type mismatch: can not convert any(1) to map"
+    })
+
+-- Make sure that MAP can only be implicitly cast to ANY.
+test:do_execsql_test(
+    "map-8.1",
+    [[
+        INSERT INTO t1(a) VALUES(m1(1, 2));
+        SELECT a FROM t1 WHERE a IS NOT NULL;
+    ]], {
+        {a = 1},
+        {[1] = 2}
+    })
+
+test:do_catchsql_test(
+    "map-8.2",
+    [[
+        INSERT INTO t1(g) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to unsigned'
+    })
+
+test:do_catchsql_test(
+    "map-8.3",
+    [[
+        INSERT INTO t1(t) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to string'
+    })
+
+test:do_catchsql_test(
+    "map-8.4",
+    [[
+        INSERT INTO t1(n) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to number'
+    })
+
+test:do_catchsql_test(
+    "map-8.5",
+    [[
+        INSERT INTO t1(f) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to double'
+    })
+
+test:do_catchsql_test(
+    "map-8.6",
+    [[
+        INSERT INTO t1(i) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to integer'
+    })
+
+test:do_catchsql_test(
+    "map-8.7",
+    [[
+        INSERT INTO t1(b) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to boolean'
+    })
+
+test:do_catchsql_test(
+    "map-8.8",
+    [[
+        INSERT INTO t1(v) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to varbinary'
+    })
+
+test:do_catchsql_test(
+    "map-8.9",
+    [[
+        INSERT INTO t1(s) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to scalar'
+    })
+
+test:do_catchsql_test(
+    "map-8.10",
+    [[
+        INSERT INTO t1(d) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to decimal'
+    })
+
+test:do_catchsql_test(
+    "map-8.11",
+    [[
+        INSERT INTO t1(u) VALUES(m1(1, 2));
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to uuid'
+    })
+
+-- Make sure nothing can be implicitly cast to MAP.
+test:do_catchsql_test(
+    "map-9.1",
+    [[
+        INSERT INTO t(m) VALUES(CAST(m1('a', 1) AS ANY));
+    ]], {
+        1, 'Type mismatch: can not convert any({"a": 1}) to map'
+    })
+
+test:do_catchsql_test(
+    "map-9.2",
+    [[
+        INSERT INTO t(m) SELECT g FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert integer(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.3",
+    [[
+        INSERT INTO t(m) SELECT t FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert string('1') to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.4",
+    [[
+        INSERT INTO t(m) SELECT n FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert number(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.5",
+    [[
+        INSERT INTO t(m) SELECT f FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert double(1.0) to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.6",
+    [[
+        INSERT INTO t(m) SELECT i FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert integer(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.7",
+    [[
+        INSERT INTO t(m) SELECT b FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert boolean(TRUE) to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.8",
+    [[
+        INSERT INTO t(m) SELECT v FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert varbinary(x'31') to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.9",
+    [[
+        INSERT INTO t(m) SELECT s FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert scalar(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.10",
+    [[
+        INSERT INTO t(m) SELECT d FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert decimal(1) to map"
+    })
+
+test:do_catchsql_test(
+    "map-9.11",
+    [[
+        INSERT INTO t(m) SELECT u FROM t1;
+    ]], {
+        1, "Type mismatch: can not convert "..
+           "uuid(11111111-1111-1111-1111-111111111111) to map"
+    })
+
+--
+-- Make sure MAP cannot participate in arithmetic and bitwise operations and
+-- concatenation.
+--
+test:do_catchsql_test(
+    "map-10.1",
+    [[
+        SELECT m1(1, 2) + 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to integer, '..
+           "decimal or double"
+    })
+
+test:do_catchsql_test(
+    "map-10.2",
+    [[
+        SELECT m1(1, 2) - 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to integer, '..
+           "decimal or double"
+    })
+
+test:do_catchsql_test(
+    "map-10.3",
+    [[
+        SELECT m1(1, 2) * 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to integer, '..
+           "decimal or double"
+    })
+
+test:do_catchsql_test(
+    "map-10.4",
+    [[
+        SELECT m1(1, 2) / 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to integer, '..
+           "decimal or double"
+    })
+
+test:do_catchsql_test(
+    "map-10.5",
+    [[
+        SELECT m1(1, 2) % 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to integer'
+    })
+
+test:do_catchsql_test(
+    "map-10.6",
+    [[
+        SELECT m1(1, 2) >> 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to unsigned'
+    })
+
+test:do_catchsql_test(
+    "map-10.7",
+    [[
+        SELECT m1(1, 2) << 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to unsigned'
+    })
+
+test:do_catchsql_test(
+    "map-10.8",
+    [[
+        SELECT m1(1, 2) | 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to unsigned'
+    })
+
+test:do_catchsql_test(
+    "map-10.9",
+    [[
+        SELECT m1(1, 2) & 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to unsigned'
+    })
+
+test:do_catchsql_test(
+    "map-10.10",
+    [[
+        SELECT ~m1(1, 2);
+    ]], {
+        1, 'Type mismatch: can not convert map({1: 2}) to unsigned'
+    })
+
+test:do_catchsql_test(
+    "map-10.11",
+    [[
+        SELECT m1(1, 2) || 'asd';
+    ]], {
+        1, 'Inconsistent types: expected string or varbinary got map({1: 2})'
+    })
+
+-- Make sure MAP is not comparable.
+test:do_catchsql_test(
+    "map-11.1",
+    [[
+        SELECT m1('a', 1) > m1('b', 2);
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.2",
+    [[
+        SELECT m1('a', 1) < CAST(1 AS ANY);
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.3",
+    [[
+        SELECT m1('a', 1) == CAST(1 AS SCALAR);
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.4",
+    [[
+        SELECT m1('a', 1) != CAST(1 AS NUMBER);
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.5",
+    [[
+        SELECT m1('a', 1) >= CAST(1 AS DECIMAL);;
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.6",
+    [[
+        SELECT m1('a', 1) <= CAST(1 AS UNSIGNED);;
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.7",
+    [[
+        SELECT m1('a', 1) > 1;
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.8",
+    [[
+        SELECT m1('a', 1) < 1e0;
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.9",
+    [[
+        SELECT m1('a', 1) == 'asd';
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.10",
+    [[
+        SELECT m1('a', 1) != x'323334';
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.11",
+    [[
+        SELECT m1('a', 1) >= true;
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-11.12",
+    [[
+        SELECT m1('a', 1) <= CAST('11111111-1111-1111-1111-111111111111' AS UUID);
+    ]], {
+        1, 'Type mismatch: can not convert map({"a": 1}) to comparable type'
+    })
+
+test:do_catchsql_test(
+    "map-12.1",
+    [[
+        SELECT ABS(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function ABS()"
+    })
+
+test:do_catchsql_test(
+    "map-12.2",
+    [[
+        SELECT AVG(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function AVG()"
+    })
+
+test:do_catchsql_test(
+    "map-12.3",
+    [[
+        SELECT CHAR(m) FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function CHAR()"
+    })
+
+test:do_catchsql_test(
+    "map-12.4",
+    [[
+        SELECT CHARACTER_LENGTH(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "CHARACTER_LENGTH()"
+    })
+
+test:do_catchsql_test(
+    "map-12.5",
+    [[
+        SELECT CHAR_LENGTH(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "CHAR_LENGTH()"
+    })
+
+test:do_execsql_test(
+    "map-12.6",
+    [[
+        SELECT COALESCE(NULL, m) FROM t;
+    ]], {
+        {abc = 123},
+        {d = 4, e = 5, f = 6},
+    })
+
+test:do_execsql_test(
+    "map-12.7",
+    [[
+        SELECT COUNT(m) FROM t;
+    ]], {
+        2
+    })
+
+test:do_catchsql_test(
+    "map-12.8",
+    [[
+        SELECT GREATEST(1, m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "GREATEST()"
+    })
+
+test:do_catchsql_test(
+    "map-12.9",
+    [[
+        SELECT GROUP_CONCAT(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "GROUP_CONCAT()"
+    })
+
+test:do_catchsql_test(
+    "map-12.10",
+    [[
+        SELECT HEX(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function HEX()"
+    })
+
+test:do_execsql_test(
+    "map-12.11",
+    [[
+        SELECT IFNULL(m, 1) FROM t;
+    ]], {
+        {abc = 123},
+        {d = 4, e = 5, f = 6},
+    })
+
+test:do_catchsql_test(
+    "map-12.12",
+    [[
+        SELECT LEAST(1, m) FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function LEAST()"
+    })
+
+test:do_catchsql_test(
+    "map-12.13",
+    [[
+        SELECT LENGTH(m) FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function LENGTH()"
+    })
+
+test:do_catchsql_test(
+    "map-12.14",
+    [[
+        SELECT 'asd' LIKE m FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function LIKE()"
+    })
+
+test:do_execsql_test(
+    "map-12.15",
+    [[
+        SELECT LIKELIHOOD(m, 0.5e0) FROM t;
+    ]], {
+        {abc = 123},
+        {d = 4, e = 5, f = 6},
+    })
+
+test:do_execsql_test(
+    "map-12.16",
+    [[
+        SELECT LIKELY(m) FROM t;
+    ]], {
+        {abc = 123},
+        {d = 4, e = 5, f = 6},
+    })
+
+test:do_catchsql_test(
+    "map-12.17",
+    [[
+        SELECT LOWER(m) FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function LOWER()"
+    })
+
+test:do_catchsql_test(
+    "map-12.18",
+    [[
+        SELECT MAX(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function MAX()"
+    })
+
+test:do_catchsql_test(
+    "map-12.19",
+    [[
+        SELECT MIN(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function MIN()"
+    })
+
+test:do_catchsql_test(
+    "map-12.20",
+    [[
+        SELECT NULLIF(1, m) FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function NULLIF()"
+    })
+
+test:do_catchsql_test(
+    "map-12.21",
+    [[
+        SELECT POSITION('asd', m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "POSITION()"
+    })
+
+test:do_execsql_test(
+    "map-12.22",
+    [[
+        SELECT PRINTF(m) FROM t;
+    ]], {
+        '{"abc": 123}',
+        '{"d": 4, "f": 6, "e": 5}',
+    })
+
+test:do_execsql_test(
+    "map-12.23",
+    [[
+        SELECT QUOTE(m) FROM t;
+    ]], {
+        '{"abc": 123}',
+        '{"d": 4, "f": 6, "e": 5}',
+    })
+
+test:do_catchsql_test(
+    "map-12.24",
+    [[
+        SELECT RANDOMBLOB(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "RANDOMBLOB()"
+    })
+
+test:do_catchsql_test(
+    "map-12.25",
+    [[
+        SELECT REPLACE('asd', 'a', m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "REPLACE()"
+    })
+
+test:do_catchsql_test(
+    "map-12.26",
+    [[
+        SELECT ROUND(m) FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function ROUND()"
+    })
+
+test:do_catchsql_test(
+    "map-12.27",
+    [[
+        SELECT SOUNDEX(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "SOUNDEX()"
+    })
+
+test:do_catchsql_test(
+    "map-12.28",
+    [[
+        SELECT SUBSTR(m, 1, 1) FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function SUBSTR()"
+    })
+
+test:do_catchsql_test(
+    "map-12.29",
+    [[
+        SELECT SUM(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function SUM()"
+    })
+
+test:do_catchsql_test(
+    "map-12.30",
+    [[
+        SELECT TOTAL(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "TOTAL()"
+    })
+
+test:do_catchsql_test(
+    "map-12.31",
+    [[
+        SELECT TRIM(m) FROM t;
+    ]], {
+        1,
+        "Failed to execute SQL statement: wrong arguments for function TRIM()"
+    })
+
+test:do_execsql_test(
+    "map-12.32",
+    [[
+        SELECT TYPEOF(m) FROM t;
+    ]], {
+        "map", "map"
+    })
+
+test:do_catchsql_test(
+    "map-12.33",
+    [[
+        SELECT UNICODE(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function "..
+           "UNICODE()"
+    })
+
+test:do_execsql_test(
+    "map-12.34",
+    [[
+        SELECT UNLIKELY(m) FROM t;
+    ]], {
+        {abc = 123},
+        {d = 4, e = 5, f = 6},
+    })
+
+test:do_catchsql_test(
+    "map-12.35",
+    [[
+        SELECT UPPER(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function UPPER()"
+    })
+
+test:do_catchsql_test(
+    "map-12.36",
+    [[
+        SELECT UUID(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function UUID()"
+    })
+
+test:do_catchsql_test(
+    "map-12.37",
+    [[
+        SELECT ZEROBLOB(m) FROM t;
+    ]], {
+        1, "Failed to execute SQL statement: wrong arguments for function ZEROBLOB()"
+    })
+
+box.execute([[DROP TABLE t1;]])
+box.execute([[DROP TABLE t;]])
+
+test:finish_test()

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Tarantool-patches] [PATCH v1 2/2] sql: introduce MAP() function
  2021-11-14 16:24   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-11-15 16:46     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 8+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-11-15 16:46 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answer below.

On Sun, Nov 14, 2021 at 05:24:37PM +0100, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> The same as for the arrays. I am not sure this functions will be
> very helpful. MAP is still not usable because can't get/set individual
> values. And the function will be obsolete when proper syntax for maps
> is introduced.
True. I dropped this patch from the branch.


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Tarantool-patches] [PATCH v1 0/2] Introduce field type MAP to SQL
  2021-11-11 11:37 [Tarantool-patches] [PATCH v1 0/2] Introduce field type MAP to SQL Mergen Imeev via Tarantool-patches
  2021-11-11 11:37 ` [Tarantool-patches] [PATCH v1 1/2] sql: introduce field type MAP Mergen Imeev via Tarantool-patches
  2021-11-11 11:37 ` [Tarantool-patches] [PATCH v1 2/2] sql: introduce MAP() function Mergen Imeev via Tarantool-patches
@ 2021-11-18 21:21 ` Vladislav Shpilevoy via Tarantool-patches
  2 siblings, 0 replies; 8+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-11-18 21:21 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for the patch!

LGTM.

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2021-11-18 21:21 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-11-11 11:37 [Tarantool-patches] [PATCH v1 0/2] Introduce field type MAP to SQL Mergen Imeev via Tarantool-patches
2021-11-11 11:37 ` [Tarantool-patches] [PATCH v1 1/2] sql: introduce field type MAP Mergen Imeev via Tarantool-patches
2021-11-14 16:24   ` Vladislav Shpilevoy via Tarantool-patches
2021-11-15 16:45     ` Mergen Imeev via Tarantool-patches
2021-11-11 11:37 ` [Tarantool-patches] [PATCH v1 2/2] sql: introduce MAP() function Mergen Imeev via Tarantool-patches
2021-11-14 16:24   ` Vladislav Shpilevoy via Tarantool-patches
2021-11-15 16:46     ` Mergen Imeev via Tarantool-patches
2021-11-18 21:21 ` [Tarantool-patches] [PATCH v1 0/2] Introduce field type MAP to SQL Vladislav Shpilevoy via Tarantool-patches

Tarantool development patches archive

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://lists.tarantool.org/tarantool-patches/0 tarantool-patches/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 tarantool-patches tarantool-patches/ https://lists.tarantool.org/tarantool-patches \
		tarantool-patches@dev.tarantool.org.
	public-inbox-index tarantool-patches

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git