Skip to content

Commit 7175f1c

Browse files
ImeevMAkyukhin
authored andcommitted
sql: introduce syntax for MAP values
This patch introduces a new syntax that allows to create MAP values in an SQL query. Part of tarantool#4763 @TarantoolBot document Title: Syntax for MAP in SQL The syntax for creating document values is now available in SQL. You can use `{`, `:` and `}` to create a MAP value. Only INTEGER, STRING, and UUID values can be keys in a MAP value. If there are two or more values for the same key, the last one will be used for that key. 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]}] ... ```
1 parent 899fbae commit 7175f1c

File tree

8 files changed

+534
-5
lines changed

8 files changed

+534
-5
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## feature/sql
2+
3+
* Field type MAP is now available in SQL. The syntax has also been implemented
4+
to allow the creation of MAP values (gh-4763).

src/box/sql/expr.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3411,6 +3411,51 @@ expr_code_array(struct Parse *parser, struct Expr *expr, int reg)
34113411
sqlVdbeAddOp3(vdbe, OP_Array, count, reg, values_reg);
34123412
}
34133413

3414+
/** Generate opcodes that will create a MAP from an expression. */
3415+
static void
3416+
expr_code_map(struct Parse *parser, struct Expr *expr, int reg)
3417+
{
3418+
struct Vdbe *vdbe = parser->pVdbe;
3419+
struct ExprList *list = expr->x.pList;
3420+
if (list == NULL) {
3421+
sqlVdbeAddOp3(vdbe, OP_Map, 0, reg, 0);
3422+
return;
3423+
}
3424+
assert(list->nExpr % 2 == 0);
3425+
int count = list->nExpr / 2;
3426+
for (int i = 0; i < count; ++i) {
3427+
struct Expr *expr = list->a[2 * i].pExpr;
3428+
enum field_type type = sql_expr_type(expr);
3429+
if (expr->op != TK_VARIABLE && type != FIELD_TYPE_INTEGER &&
3430+
type != FIELD_TYPE_UNSIGNED && type != FIELD_TYPE_STRING &&
3431+
type != FIELD_TYPE_UUID) {
3432+
diag_set(ClientError, ER_SQL_PARSER_GENERIC, "Only "
3433+
"integer, string and uuid can be keys in map");
3434+
parser->is_aborted = true;
3435+
return;
3436+
}
3437+
}
3438+
int len = 0;
3439+
int result_reg = parser->nMem + 1;
3440+
for (int i = 0; i < count; ++i) {
3441+
struct Expr *key = list->a[2 * i].pExpr;
3442+
int j;
3443+
for (j = i + 1; j < count; ++j) {
3444+
struct Expr *tmp_key = list->a[2 * j].pExpr;
3445+
if (sqlExprCompare(key, tmp_key, -1) == 0)
3446+
break;
3447+
}
3448+
if (j < count)
3449+
continue;
3450+
3451+
++len;
3452+
struct Expr *value = list->a[2 * i + 1].pExpr;
3453+
sqlExprCodeFactorable(parser, key, ++parser->nMem);
3454+
sqlExprCodeFactorable(parser, value, ++parser->nMem);
3455+
}
3456+
sqlVdbeAddOp3(vdbe, OP_Map, len, reg, result_reg);
3457+
}
3458+
34143459
/*
34153460
* Erase column-cache entry number i
34163461
*/
@@ -3866,6 +3911,10 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
38663911
expr_code_array(pParse, pExpr, target);
38673912
break;
38683913

3914+
case TK_MAP:
3915+
expr_code_map(pParse, pExpr, target);
3916+
return target;
3917+
38693918
case TK_LT:
38703919
case TK_LE:
38713920
case TK_GT:

src/box/sql/mem.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3245,6 +3245,45 @@ mem_encode_array(const struct Mem *mems, uint32_t count, uint32_t *size,
32453245
return array;
32463246
}
32473247

3248+
char *
3249+
mem_encode_map(const struct Mem *mems, uint32_t count, uint32_t *size,
3250+
struct region *region)
3251+
{
3252+
size_t used = region_used(region);
3253+
bool is_error = false;
3254+
struct mpstream stream;
3255+
mpstream_init(&stream, region, region_reserve_cb, region_alloc_cb,
3256+
set_encode_error, &is_error);
3257+
mpstream_encode_map(&stream, count);
3258+
for (uint32_t i = 0; i < count; ++i) {
3259+
const struct Mem *key = &mems[2 * i];
3260+
const struct Mem *value = &mems[2 * i + 1];
3261+
if (mem_is_metatype(key) ||
3262+
(key->type & (MEM_TYPE_UINT | MEM_TYPE_INT | MEM_TYPE_UUID |
3263+
MEM_TYPE_STR)) == 0) {
3264+
diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
3265+
mem_str(key), "integer, string or uuid");
3266+
goto error;
3267+
}
3268+
mem_to_mpstream(key, &stream);
3269+
mem_to_mpstream(value, &stream);
3270+
}
3271+
mpstream_flush(&stream);
3272+
if (is_error) {
3273+
diag_set(OutOfMemory, stream.pos - stream.buf,
3274+
"mpstream_flush", "stream");
3275+
goto error;
3276+
}
3277+
*size = region_used(region) - used;
3278+
char *map = region_join(region, *size);
3279+
if (map != NULL)
3280+
return map;
3281+
diag_set(OutOfMemory, *size, "region_join", "map");
3282+
error:
3283+
region_truncate(region, used);
3284+
return NULL;
3285+
}
3286+
32483287
/**
32493288
* Allocate a sequence of initialized vdbe memory registers
32503289
* on region.

src/box/sql/mem.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,3 +919,19 @@ mem_to_mpstream(const struct Mem *var, struct mpstream *stream);
919919
char *
920920
mem_encode_array(const struct Mem *mems, uint32_t count, uint32_t *size,
921921
struct region *region);
922+
923+
/**
924+
* Encode array of MEMs as msgpack map on region. Values in even position are
925+
* treated as keys in MAP, values in odd position are treated as values in MAP.
926+
* number of MEMs should be even.
927+
*
928+
* @param mems array of MEMs to encode.
929+
* @param count number of elements in the array.
930+
* @param[out] size Size of encoded msgpack map.
931+
* @param region Region to use.
932+
* @retval NULL on error, diag message is set.
933+
* @retval Pointer to valid msgpack map on success.
934+
*/
935+
char *
936+
mem_encode_map(const struct Mem *mems, uint32_t count, uint32_t *size,
937+
struct region *region);

src/box/sql/parse.y

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,11 +1073,11 @@ expr(A) ::= VARNUM(X). {
10731073
A.pExpr = expr_new_variable(pParse, &X, NULL);
10741074
spanSet(&A, &X, &X);
10751075
}
1076-
expr(A) ::= VARIABLE(X) id(Y). {
1076+
expr(A) ::= COLON|VARIABLE(X) id(Y). {
10771077
A.pExpr = expr_new_variable(pParse, &X, &Y);
10781078
spanSet(&A, &X, &Y);
10791079
}
1080-
expr(A) ::= VARIABLE(X) INTEGER(Y). {
1080+
expr(A) ::= COLON|VARIABLE(X) INTEGER(Y). {
10811081
A.pExpr = expr_new_variable(pParse, &X, &Y);
10821082
spanSet(&A, &X, &Y);
10831083
}
@@ -1111,6 +1111,39 @@ expr(A) ::= LB(X) exprlist(Y) RB(E). {
11111111
spanSet(&A, &X, &E);
11121112
}
11131113

1114+
expr(A) ::= LCB(X) maplist(Y) RCB(E). {
1115+
struct sql *db = pParse->db;
1116+
struct Expr *expr = sql_expr_new_anon(db, TK_MAP);
1117+
if (expr == NULL) {
1118+
sql_expr_list_delete(db, Y);
1119+
pParse->is_aborted = true;
1120+
return;
1121+
}
1122+
expr->x.pList = Y;
1123+
expr->type = FIELD_TYPE_MAP;
1124+
sqlExprSetHeightAndFlags(pParse, expr);
1125+
A.pExpr = expr;
1126+
spanSet(&A, &X, &E);
1127+
}
1128+
1129+
maplist(A) ::= nmaplist(A).
1130+
maplist(A) ::= . {
1131+
A = NULL;
1132+
}
1133+
nmaplist(A) ::= nmaplist(A) COMMA expr(X) COLON expr(Y). {
1134+
A = sql_expr_list_append(pParse->db, A, X.pExpr);
1135+
A = sql_expr_list_append(pParse->db, A, Y.pExpr);
1136+
}
1137+
nmaplist(A) ::= expr(X) COLON expr(Y). {
1138+
A = sql_expr_list_append(pParse->db, NULL, X.pExpr);
1139+
A = sql_expr_list_append(pParse->db, A, Y.pExpr);
1140+
}
1141+
1142+
%type maplist {ExprList *}
1143+
%destructor maplist {sql_expr_list_delete(pParse->db, $$);}
1144+
%type nmaplist {ExprList *}
1145+
%destructor nmaplist {sql_expr_list_delete(pParse->db, $$);}
1146+
11141147
expr(A) ::= TRIM(X) LP trim_operands(Y) RP(E). {
11151148
A.pExpr = sqlExprFunction(pParse, Y, &X);
11161149
spanSet(&A, &X, &E);

src/box/sql/tokenize.c

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@
5858
#define CC_KYWD 1 /* Alphabetics or '_'. Usable in a keyword */
5959
#define CC_ID 2 /* unicode characters usable in IDs */
6060
#define CC_DIGIT 3 /* Digits */
61-
/** SQL variables: '@', '#', ':', and '$'. */
61+
/** Character ':'. */
62+
#define CC_COLON 4
63+
/** SQL variable special characters: '@', '#', and '$'. */
6264
#define CC_VARALPHA 5
6365
#define CC_VARNUM 6 /* '?'. Numeric SQL variables */
6466
#define CC_SPACE 7 /* Space characters */
@@ -85,17 +87,21 @@
8587
#define CC_LINEFEED 28 /* '\n' */
8688
#define CC_LB 29 /* '[' */
8789
#define CC_RB 30 /* ']' */
90+
/** Character '{'. */
91+
#define CC_LCB 31
92+
/** Character '}'. */
93+
#define CC_RCB 32
8894

8995
static const char sql_ascii_class[] = {
9096
/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */
9197
/* 0x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 7, 28, 7, 7, 7, 27, 27,
9298
/* 1x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
9399
/* 2x */ 7, 15, 9, 5, 5, 22, 24, 8, 17, 18, 21, 20, 23, 11, 26, 16,
94-
/* 3x */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 19, 12, 14, 13, 6,
100+
/* 3x */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 19, 12, 14, 13, 6,
95101
/* 4x */ 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
96102
/* 5x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 29, 27, 30, 27, 1,
97103
/* 6x */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
98-
/* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 27, 10, 27, 25, 27,
104+
/* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 31, 10, 32, 25, 27,
99105
/* 8x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
100106
/* 9x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
101107
/* 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)
228234
case CC_RB:
229235
*type = TK_RB;
230236
return 1;
237+
case CC_LCB:
238+
*type = TK_LCB;
239+
return 1;
240+
case CC_RCB:
241+
*type = TK_RCB;
242+
return 1;
231243
case CC_SEMI:
232244
*type = TK_SEMI;
233245
return 1;
@@ -371,6 +383,9 @@ sql_token(const char *z, int *type, bool *is_reserved)
371383
case CC_VARNUM:
372384
*type = TK_VARNUM;
373385
return 1;
386+
case CC_COLON:
387+
*type = TK_COLON;
388+
return 1;
374389
case CC_VARALPHA:
375390
*type = TK_VARIABLE;
376391
return 1;

src/box/sql/vdbe.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1442,6 +1442,27 @@ case OP_Array: {
14421442
break;
14431443
}
14441444

1445+
/**
1446+
* Opcode: Map P1 P2 P3 * *
1447+
* Synopsis: r[P2] = map(P3@P1)
1448+
*
1449+
* Construct an MAP value from P1 registers starting at reg(P3).
1450+
*/
1451+
case OP_Map: {
1452+
pOut = &aMem[pOp->p2];
1453+
1454+
uint32_t size;
1455+
struct region *region = &fiber()->gc;
1456+
size_t svp = region_used(region);
1457+
char *val = mem_encode_map(&aMem[pOp->p3], pOp->p1, &size, region);
1458+
if (val == NULL || mem_copy_map(pOut, val, size) != 0) {
1459+
region_truncate(region, svp);
1460+
goto abort_due_to_error;
1461+
}
1462+
region_truncate(region, svp);
1463+
break;
1464+
}
1465+
14451466
/* Opcode: Eq P1 P2 P3 P4 P5
14461467
* Synopsis: IF r[P3]==r[P1]
14471468
*

0 commit comments

Comments
 (0)