Skip to content

Commit 899fbae

Browse files
ImeevMAkyukhin
authored andcommitted
sql: properly check bind variable names
After this patch, variable names will have to follow the rules defined for identifiers in SQL. Essentially, this means that a digit can no longer be used as the first character of a bind variable name. Part of tarantool#4763 NO_DOC=Will be added later. NO_CHANGELOG=Will be added later.
1 parent b08e23f commit 899fbae

File tree

7 files changed

+116
-71
lines changed

7 files changed

+116
-71
lines changed

src/box/sql/expr.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,6 +1314,51 @@ sqlExprAssignVarNumber(Parse * pParse, Expr * pExpr, u32 n)
13141314
}
13151315
}
13161316

1317+
struct Expr *
1318+
expr_new_variable(struct Parse *parse, const struct Token *spec,
1319+
const struct Token *id)
1320+
{
1321+
assert(spec != NULL && spec->n == 1);
1322+
uint32_t len = 1;
1323+
if (parse->parse_only) {
1324+
diag_set(ClientError, ER_SQL_PARSER_GENERIC_WITH_POS,
1325+
parse->line_count, parse->line_pos,
1326+
"bindings are not allowed in DDL");
1327+
parse->is_aborted = true;
1328+
return NULL;
1329+
}
1330+
if (id != NULL) {
1331+
assert(spec->z[0] != '?');
1332+
if (id->z - spec->z != 1) {
1333+
diag_set(ClientError, ER_SQL_UNKNOWN_TOKEN,
1334+
parse->line_count, spec->z - parse->zTail + 1,
1335+
spec->n, spec->z);
1336+
parse->is_aborted = true;
1337+
return NULL;
1338+
}
1339+
if (spec->z[0] == '#' && sqlIsdigit(id->z[0])) {
1340+
diag_set(ClientError, ER_SQL_SYNTAX_NEAR_TOKEN,
1341+
parse->line_count, spec->n, spec->z);
1342+
parse->is_aborted = true;
1343+
return NULL;
1344+
}
1345+
len += id->n;
1346+
}
1347+
struct Expr *expr = sql_expr_new_empty(parse->db, TK_VARIABLE, len + 1);
1348+
if (expr == NULL)
1349+
return NULL;
1350+
expr->type = FIELD_TYPE_BOOLEAN;
1351+
expr->flags = EP_Leaf;
1352+
expr->u.zToken = (char *)(expr + 1);
1353+
expr->u.zToken[0] = spec->z[0];
1354+
if (id != NULL)
1355+
memcpy(expr->u.zToken + 1, id->z, id->n);
1356+
expr->u.zToken[len] = '\0';
1357+
1358+
sqlExprAssignVarNumber(parse, expr, len);
1359+
return expr;
1360+
}
1361+
13171362
/*
13181363
* Recursively delete an expression tree.
13191364
*/

src/box/sql/parse.y

Lines changed: 20 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,15 +1000,6 @@ idlist(A) ::= nm(Y). {
10001000
case TK_UNKNOWN:
10011001
p->type = FIELD_TYPE_BOOLEAN;
10021002
break;
1003-
case TK_VARIABLE:
1004-
/*
1005-
* For variables we set BOOLEAN type since
1006-
* unassigned bindings will be replaced
1007-
* with NULL automatically, i.e. without
1008-
* explicit call of sql_bind_*().
1009-
*/
1010-
p->type = FIELD_TYPE_BOOLEAN;
1011-
break;
10121003
default:
10131004
p->type = FIELD_TYPE_SCALAR;
10141005
break;
@@ -1017,20 +1008,16 @@ idlist(A) ::= nm(Y). {
10171008
p->flags = EP_Leaf;
10181009
p->iAgg = -1;
10191010
p->u.zToken = (char*)&p[1];
1020-
if (op != TK_VARIABLE) {
1021-
int rc = sql_normalize_name(p->u.zToken, name_sz, t.z, t.n);
1022-
if (rc > name_sz) {
1023-
name_sz = rc;
1024-
p = sqlDbReallocOrFree(pParse->db, p, sizeof(*p) + name_sz);
1025-
if (p == NULL)
1026-
goto tarantool_error;
1027-
p->u.zToken = (char *) &p[1];
1028-
if (sql_normalize_name(p->u.zToken, name_sz, t.z, t.n) > name_sz)
1029-
unreachable();
1011+
int rc = sql_normalize_name(p->u.zToken, name_sz, t.z, t.n);
1012+
if (rc > name_sz) {
1013+
name_sz = rc;
1014+
p = sqlDbReallocOrFree(pParse->db, p, sizeof(*p) + name_sz);
1015+
if (p == NULL) {
1016+
pParse->is_aborted = true;
1017+
return;
10301018
}
1031-
} else {
1032-
memcpy(p->u.zToken, t.z, t.n);
1033-
p->u.zToken[t.n] = 0;
1019+
p->u.zToken = (char *)&p[1];
1020+
sql_normalize_name(p->u.zToken, name_sz, t.z, t.n);
10341021
}
10351022
#if SQL_MAX_EXPR_DEPTH>0
10361023
p->nHeight = 1;
@@ -1040,9 +1027,6 @@ idlist(A) ::= nm(Y). {
10401027
pOut->zStart = t.z;
10411028
pOut->zEnd = &t.z[t.n];
10421029
return;
1043-
tarantool_error:
1044-
sqlDbFree(pParse->db, p);
1045-
pParse->is_aborted = true;
10461030
}
10471031
}
10481032

@@ -1085,30 +1069,17 @@ term(A) ::= INTEGER(X). {
10851069
A.zEnd = X.z + X.n;
10861070
if( A.pExpr ) A.pExpr->flags |= EP_Leaf;
10871071
}
1088-
expr(A) ::= VARIABLE(X). {
1089-
Token t = X;
1090-
if (pParse->parse_only) {
1091-
spanSet(&A, &t, &t);
1092-
diag_set(ClientError, ER_SQL_PARSER_GENERIC_WITH_POS, pParse->line_count,
1093-
pParse->line_pos, "bindings are not allowed in DDL");
1094-
pParse->is_aborted = true;
1095-
A.pExpr = NULL;
1096-
} else if (!(X.z[0]=='#' && sqlIsdigit(X.z[1]))) {
1097-
u32 n = X.n;
1098-
spanExpr(&A, pParse, TK_VARIABLE, X);
1099-
if (A.pExpr->u.zToken[0] == '?' && n > 1) {
1100-
diag_set(ClientError, ER_SQL_SYNTAX_NEAR_TOKEN, pParse->line_count, t.n, t.z);
1101-
pParse->is_aborted = true;
1102-
} else {
1103-
sqlExprAssignVarNumber(pParse, A.pExpr, n);
1104-
}
1105-
}else{
1106-
assert( t.n>=2 );
1107-
spanSet(&A, &t, &t);
1108-
diag_set(ClientError, ER_SQL_SYNTAX_NEAR_TOKEN, pParse->line_count, t.n, t.z);
1109-
pParse->is_aborted = true;
1110-
A.pExpr = NULL;
1111-
}
1072+
expr(A) ::= VARNUM(X). {
1073+
A.pExpr = expr_new_variable(pParse, &X, NULL);
1074+
spanSet(&A, &X, &X);
1075+
}
1076+
expr(A) ::= VARIABLE(X) id(Y). {
1077+
A.pExpr = expr_new_variable(pParse, &X, &Y);
1078+
spanSet(&A, &X, &Y);
1079+
}
1080+
expr(A) ::= VARIABLE(X) INTEGER(Y). {
1081+
A.pExpr = expr_new_variable(pParse, &X, &Y);
1082+
spanSet(&A, &X, &Y);
11121083
}
11131084
expr(A) ::= expr(A) COLLATE id(C). {
11141085
A.pExpr = sqlExprAddCollateToken(pParse, A.pExpr, &C, 1);

src/box/sql/sqlInt.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2622,6 +2622,17 @@ Expr *sqlExprFunction(Parse *, ExprList *, Token *);
26222622
void sqlExprAssignVarNumber(Parse *, Expr *, u32);
26232623
ExprList *sqlExprListAppendVector(Parse *, ExprList *, IdList *, Expr *);
26242624

2625+
/**
2626+
* Parse tokens as a name or a position of bound variable.
2627+
*
2628+
* @param parse Parse context.
2629+
* @param spec Special symbol for bound variable.
2630+
* @param id Name or position number of bound variable.
2631+
*/
2632+
struct Expr *
2633+
expr_new_variable(struct Parse *parse, const struct Token *spec,
2634+
const struct Token *id);
2635+
26252636
/**
26262637
* Set the sort order for the last element on the given ExprList.
26272638
*

src/box/sql/tokenize.c

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@
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-
#define CC_DOLLAR 4 /* '$' */
62-
#define CC_VARALPHA 5 /* '@', '#', ':'. Alphabetic SQL variables */
61+
/** SQL variables: '@', '#', ':', and '$'. */
62+
#define CC_VARALPHA 5
6363
#define CC_VARNUM 6 /* '?'. Numeric SQL variables */
6464
#define CC_SPACE 7 /* Space characters */
6565
#define CC_QUOTE 8 /* '\''. String literals */
@@ -90,7 +90,7 @@ static const char sql_ascii_class[] = {
9090
/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */
9191
/* 0x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 7, 28, 7, 7, 7, 27, 27,
9292
/* 1x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
93-
/* 2x */ 7, 15, 9, 5, 4, 22, 24, 8, 17, 18, 21, 20, 23, 11, 26, 16,
93+
/* 2x */ 7, 15, 9, 5, 5, 22, 24, 8, 17, 18, 21, 20, 23, 11, 26, 16,
9494
/* 3x */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 19, 12, 14, 13, 6,
9595
/* 4x */ 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
9696
/* 5x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 29, 27, 30, 27, 1,
@@ -184,7 +184,7 @@ int
184184
sql_token(const char *z, int *type, bool *is_reserved)
185185
{
186186
*is_reserved = false;
187-
int i, n;
187+
int i;
188188
char c, delim;
189189
/* Switch on the character-class of the first byte
190190
* of the token. See the comment on the CC_ defines
@@ -369,23 +369,11 @@ sql_token(const char *z, int *type, bool *is_reserved)
369369
}
370370
return i;
371371
case CC_VARNUM:
372-
*type = TK_VARIABLE;
373-
for (i = 1; sqlIsdigit(z[i]); i++) {
374-
}
375-
return i;
376-
case CC_DOLLAR:
372+
*type = TK_VARNUM;
373+
return 1;
377374
case CC_VARALPHA:
378-
n = 0;
379375
*type = TK_VARIABLE;
380-
for (i = 1; (c = z[i]) != 0; i++) {
381-
if (IdChar(c))
382-
n++;
383-
else
384-
break;
385-
}
386-
if (n == 0)
387-
*type = TK_ILLEGAL;
388-
return i;
376+
return 1;
389377
case CC_KYWD:
390378
for (i = 1; sql_ascii_class[*(unsigned char*)(z+i)] <= CC_KYWD;
391379
i++) {

test/sql-luatest/bind_test.lua

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
local server = require('test.luatest_helpers.server')
2+
local t = require('luatest')
3+
local g = t.group()
4+
5+
g.before_all(function()
6+
g.server = server:new({alias = 'bind'})
7+
g.server:start()
8+
end)
9+
10+
g.after_all(function()
11+
g.server:stop()
12+
end)
13+
14+
g.test_bind_1 = function()
15+
g.server:exec(function()
16+
local t = require('luatest')
17+
local sql = [[SELECT @1asd;]]
18+
local res = "At line 1 at or near position 9: unrecognized token '1asd'"
19+
local _, err = box.execute(sql, {{['@1asd'] = 123}})
20+
t.assert_equals(err.message, res)
21+
end)
22+
end
23+
24+
g.test_bind_2 = function()
25+
local conn = g.server.net_box
26+
local sql = [[SELECT @1asd;]]
27+
local res = [[At line 1 at or near position 9: unrecognized token '1asd']]
28+
local _, err = pcall(conn.execute, conn, sql, {{['@1asd'] = 123}})
29+
t.assert_equals(err.message, res)
30+
end

test/sql-tap/misc1.test.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,7 +1052,7 @@ test:do_catchsql_test(
10521052
select''like''like''like#0;
10531053
]], {
10541054
-- <misc1-21.1>
1055-
1, [[Syntax error at line 1 near '#0']]
1055+
1, [[Syntax error at line 1 near '#']]
10561056
-- </misc1-21.1>
10571057
})
10581058

@@ -1062,7 +1062,7 @@ test:do_catchsql_test(
10621062
VALUES(0,0x0MATCH#0;
10631063
]], {
10641064
-- <misc1-21.2>
1065-
1, [[Syntax error at line 1 near '#0']]
1065+
1, [[Syntax error at line 1 near '#']]
10661066
-- </misc1-21.2>
10671067
})
10681068

test/sql/iproto.result

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ cn:execute('drop table if exists test3')
351351
--
352352
cn:execute('select ?1, ?2, ?3', {1, 2, 3})
353353
---
354-
- error: Syntax error at line 1 near '?1'
354+
- error: Syntax error at line 1 near '1'
355355
...
356356
cn:execute('select $name, $name2', {1, 2})
357357
---

0 commit comments

Comments
 (0)