Skip to content

Commit 0b186b1

Browse files
committed
Merge #399 - Improving the Expression component's allowed keywords
Pull-request: #399 Fixes: phpmyadmin/phpmyadmin#17422 Fixes: #396 Fixes: #171 Signed-off-by: William Desportes <williamdes@wdes.fr>
2 parents 6d873db + d3e3c6c commit 0b186b1

File tree

11 files changed

+1967
-44
lines changed

11 files changed

+1967
-44
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
* Fixed differentiating between `ANALYZE` and `EXPLAIN` statements (#386)
1414
* Added "NOT" to the select options (#374)
1515
* Implement the `EXPLAIN` Parser (#389)
16-
* Context: Updated contexts to contain `multipoint` and `multipolygon` data types. (#393)
16+
* Context: Updated contexts to contain `multipoint` and `multipolygon` data types (#393)
17+
* Support more keywords on `Expression` component (#399)
1718

1819
## [5.5.0] - 2021-12-08
1920

src/Components/Expression.php

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use function implode;
1515
use function is_array;
16+
use function rtrim;
1617
use function strlen;
1718
use function trim;
1819

@@ -31,19 +32,22 @@ class Expression extends Component
3132
* @var array<string, int>
3233
*/
3334
private static $ALLOWED_KEYWORDS = [
35+
'AND' => 1,
3436
'AS' => 1,
35-
'DUAL' => 1,
36-
'NULL' => 1,
37-
'REGEXP' => 1,
37+
'BETWEEN' => 1,
3838
'CASE' => 1,
39+
'DUAL' => 1,
3940
'DIV' => 1,
40-
'AND' => 1,
41+
'IS' => 1,
42+
'MOD' => 1,
43+
'NOT' => 1,
44+
'NOT NULL' => 1,
45+
'NULL' => 1,
4146
'OR' => 1,
47+
'OVER' => 1,
48+
'REGEXP' => 1,
49+
'RLIKE' => 1,
4250
'XOR' => 1,
43-
'NOT' => 1,
44-
'MOD' => 1,
45-
46-
'OVER' => 2,
4751
];
4852

4953
/**
@@ -371,6 +375,23 @@ public static function parse(Parser $parser, TokensList $list, array $options =
371375

372376
$ret->alias = $prev[1]->value;
373377
} else {
378+
$currIdx = $list->idx;
379+
--$list->idx;
380+
$beforeToken = $list->getPrevious();
381+
$list->idx = $currIdx;
382+
// columns names tokens are of type NONE, or SYMBOL (`col`), and the columns options
383+
// would start with a token of type KEYWORD, in that case, we want to have a space
384+
// between the tokens.
385+
if (
386+
$ret->expr !== null &&
387+
$beforeToken &&
388+
($beforeToken->type === Token::TYPE_NONE ||
389+
$beforeToken->type === Token::TYPE_SYMBOL || $beforeToken->type === Token::TYPE_STRING) &&
390+
$token->type === Token::TYPE_KEYWORD
391+
) {
392+
$ret->expr = rtrim($ret->expr, ' ') . ' ';
393+
}
394+
374395
$ret->expr .= $token->token;
375396
}
376397
} elseif (! $isExpr) {

tests/Builder/SelectStatementTest.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,31 @@ public function testBuilder(): void
2222
. 'ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)',
2323
$stmt->build()
2424
);
25+
26+
$parser = new Parser('SELECT NULL IS NULL');
27+
$stmt = $parser->statements[0];
28+
29+
$this->assertEquals('SELECT NULL IS NULL', $stmt->build());
30+
31+
$parser = new Parser('SELECT NOT 1');
32+
$stmt = $parser->statements[0];
33+
34+
$this->assertEquals('SELECT NOT 1', $stmt->build());
35+
36+
$parser = new Parser('SELECT 1 BETWEEN 0 AND 2');
37+
$stmt = $parser->statements[0];
38+
39+
$this->assertEquals('SELECT 1 BETWEEN 0 AND 2', $stmt->build());
40+
41+
$parser = new Parser("SELECT 'a' NOT REGEXP '^[a-d]'");
42+
$stmt = $parser->statements[0];
43+
44+
$this->assertEquals("SELECT 'a' NOT REGEXP '^[a-d]'", $stmt->build());
45+
46+
$parser = new Parser("SELECT 'a' RLIKE 'a'");
47+
$stmt = $parser->statements[0];
48+
49+
$this->assertEquals("SELECT 'a' RLIKE 'a'", $stmt->build());
2550
}
2651

2752
public function testBuilderUnion(): void
@@ -35,6 +60,34 @@ public function testBuilderUnion(): void
3560
);
3661
}
3762

63+
public function testBuilderWithIsNull(): void
64+
{
65+
$parser = new Parser('SELECT `test3`.`t1` is not null AS `is_not_null` FROM `test3` ;');
66+
$stmt = $parser->statements[0];
67+
68+
$this->assertEquals('SELECT `test3`.`t1` is not null AS `is_not_null` FROM `test3`', $stmt->build());
69+
70+
$parser = new Parser('SELECT test3.t1 is null AS `col1` FROM test3');
71+
$stmt = $parser->statements[0];
72+
73+
$this->assertEquals('SELECT test3.t1 is null AS `col1` FROM test3', $stmt->build());
74+
}
75+
76+
public function testBuilderOrderByNull(): void
77+
{
78+
$query = 'SELECT * FROM some_table ORDER BY some_col IS NULL DESC;';
79+
$parser = new Parser($query);
80+
$stmt = $parser->statements[0];
81+
82+
$this->assertEquals('SELECT * FROM some_table ORDER BY some_col IS NULL DESC', $stmt->build());
83+
84+
$query = 'SELECT * FROM some_table ORDER BY some_col IS NOT NULL;';
85+
$parser = new Parser($query);
86+
$stmt = $parser->statements[0];
87+
88+
$this->assertEquals('SELECT * FROM some_table ORDER BY some_col IS NOT NULL ASC', $stmt->build());
89+
}
90+
3891
public function testBuilderAlias(): void
3992
{
4093
$parser = new Parser(

tests/Parser/CreateStatementTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public function createProvider(): array
6060
['parser/parseCreateView2'],
6161
['parser/parseCreateView3'],
6262
['parser/parseCreateView4'],
63+
['parser/parseCreateView5'],
6364
['parser/parseCreateViewMultiple'],
6465
['parser/parseCreateViewWithoutQuotes'],
6566
['parser/parseCreateViewWithQuotes'],

tests/Parser/SelectStatementTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public function selectProvider(): array
8989
['parser/parseSelectWhere'],
9090
['parser/parseSelectIndexHint1'],
9191
['parser/parseSelectIndexHint2'],
92+
['parser/parseSelectOrderByIsNull'],
9293
['parser/parseSelectIndexHintErr1'],
9394
['parser/parseSelectIndexHintErr2'],
9495
['parser/parseSelectIndexHintErr3'],

tests/data/bugs/pma11800.out

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -567,23 +567,27 @@
567567
{
568568
"@type": "PhpMyAdmin\\SqlParser\\Components\\Expression",
569569
"ALLOWED_KEYWORDS": {
570+
"AND": 1,
570571
"AS": 1,
571-
"DUAL": 1,
572-
"NULL": 1,
573-
"REGEXP": 1,
572+
"BETWEEN": 1,
574573
"CASE": 1,
574+
"DUAL": 1,
575575
"DIV": 1,
576-
"AND": 1,
577-
"OR": 1,
578-
"XOR": 1,
579-
"NOT": 1,
576+
"IS": 1,
580577
"MOD": 1,
581-
"OVER": 2
578+
"NOT": 1,
579+
"NOT NULL": 1,
580+
"NULL": 1,
581+
"OR": 1,
582+
"OVER": 1,
583+
"REGEXP": 1,
584+
"RLIKE": 1,
585+
"XOR": 1
582586
},
583587
"database": null,
584588
"table": null,
585589
"column": "a",
586-
"expr": "'a'REGEXP '^[a-d]'",
590+
"expr": "'a' REGEXP '^[a-d]'",
587591
"alias": null,
588592
"function": null,
589593
"subquery": null

tests/data/parser/parseAlterUser.out

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -491,18 +491,22 @@
491491
"table": {
492492
"@type": "PhpMyAdmin\\SqlParser\\Components\\Expression",
493493
"ALLOWED_KEYWORDS": {
494+
"AND": 1,
494495
"AS": 1,
495-
"DUAL": 1,
496-
"NULL": 1,
497-
"REGEXP": 1,
496+
"BETWEEN": 1,
498497
"CASE": 1,
498+
"DUAL": 1,
499499
"DIV": 1,
500-
"AND": 1,
501-
"OR": 1,
502-
"XOR": 1,
503-
"NOT": 1,
500+
"IS": 1,
504501
"MOD": 1,
505-
"OVER": 2
502+
"NOT": 1,
503+
"NOT NULL": 1,
504+
"NULL": 1,
505+
"OR": 1,
506+
"OVER": 1,
507+
"REGEXP": 1,
508+
"RLIKE": 1,
509+
"XOR": 1
506510
},
507511
"database": null,
508512
"table": "jeffrey@localhost",
@@ -654,47 +658,55 @@
654658
"expr": {
655659
"@type": "PhpMyAdmin\\SqlParser\\Components\\Expression",
656660
"ALLOWED_KEYWORDS": {
661+
"AND": 1,
657662
"AS": 1,
658-
"DUAL": 1,
659-
"NULL": 1,
660-
"REGEXP": 1,
663+
"BETWEEN": 1,
661664
"CASE": 1,
665+
"DUAL": 1,
662666
"DIV": 1,
663-
"AND": 1,
664-
"OR": 1,
665-
"XOR": 1,
666-
"NOT": 1,
667+
"IS": 1,
667668
"MOD": 1,
668-
"OVER": 2
669+
"NOT": 1,
670+
"NOT NULL": 1,
671+
"NULL": 1,
672+
"OR": 1,
673+
"OVER": 1,
674+
"REGEXP": 1,
675+
"RLIKE": 1,
676+
"XOR": 1
669677
},
670678
"database": null,
671679
"table": null,
672680
"column": "new_password",
673-
"expr": "'new_password'PASSWORD",
681+
"expr": "'new_password' PASSWORD",
674682
"alias": null,
675683
"function": null,
676684
"subquery": null
677685
},
678-
"value": "'new_password'PASSWORD"
686+
"value": "'new_password' PASSWORD"
679687
},
680688
"3": "IDENTIFIED"
681689
}
682690
},
683691
"field": {
684692
"@type": "PhpMyAdmin\\SqlParser\\Components\\Expression",
685693
"ALLOWED_KEYWORDS": {
694+
"AND": 1,
686695
"AS": 1,
687-
"DUAL": 1,
688-
"NULL": 1,
689-
"REGEXP": 1,
696+
"BETWEEN": 1,
690697
"CASE": 1,
698+
"DUAL": 1,
691699
"DIV": 1,
692-
"AND": 1,
693-
"OR": 1,
694-
"XOR": 1,
695-
"NOT": 1,
700+
"IS": 1,
696701
"MOD": 1,
697-
"OVER": 2
702+
"NOT": 1,
703+
"NOT NULL": 1,
704+
"NULL": 1,
705+
"OR": 1,
706+
"OVER": 1,
707+
"REGEXP": 1,
708+
"RLIKE": 1,
709+
"XOR": 1
698710
},
699711
"database": null,
700712
"table": null,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CREATE VIEW `t3` as SELECT `t1` IS NOT NULL AS `is_not_null` FROM `test3`;

0 commit comments

Comments
 (0)