Skip to content

Commit 6b05c19

Browse files
committed
Merge branch 'QA'
Signed-off-by: William Desportes <[email protected]>
2 parents 00e03bc + 614b944 commit 6b05c19

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+723
-81
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
* Replace sscanf by equivalent native PHP functions because sscanf can be disabled for security reasons. (#270)
77
* Allow phpunit 9
88
* Fix for php error when "INSERT INTO x SET a = 1" is "INSERT INTO x SET = 1" (#295)
9+
* Fixed lexer fails to detect "*" as a wildcard (#288)
10+
* Fixed ANSI_QUOTES support (#284)
11+
* Fixed parser mistakes with comments (#156)
912

1013
## [5.2.0] - 2020-01-07
1114

src/Components/Expression.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ public function __construct($database = null, $table = null, $column = null, $al
152152
* @param array $options parameters for parsing
153153
*
154154
* @return Expression|null
155+
* @throws \PhpMyAdmin\SqlParser\Exceptions\ParserException
155156
*/
156157
public static function parse(Parser $parser, TokensList $list, array $options = [])
157158
{

src/Components/ExpressionArray.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class ExpressionArray extends Component
2323
* @param array $options parameters for parsing
2424
*
2525
* @return Expression[]
26+
* @throws \PhpMyAdmin\SqlParser\Exceptions\ParserException
2627
*/
2728
public static function parse(Parser $parser, TokensList $list, array $options = [])
2829
{
@@ -104,12 +105,20 @@ public static function parse(Parser $parser, TokensList $list, array $options =
104105

105106
--$list->idx;
106107

108+
if (is_array($ret)) {
109+
$expr = $ret[count($ret) - 1]->expr;
110+
if (preg_match('/\s*--\s.*$/', $expr, $matches)) {
111+
$found = $matches[0];
112+
$ret[count($ret) - 1]->expr = substr($expr, 0, strlen($expr) - strlen($found));
113+
}
114+
}
115+
107116
return $ret;
108117
}
109118

110119
/**
111-
* @param ExpressionArray[] $component the component to be built
112-
* @param array $options parameters for building
120+
* @param Expression[] $component the component to be built
121+
* @param array $options parameters for building
113122
*
114123
* @return string
115124
*/

src/Context.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ public static function isSymbol($str)
423423

424424
if ($str[0] === '@') {
425425
return Token::FLAG_SYMBOL_VARIABLE;
426-
} elseif ($str[0] === '`') {
426+
} elseif ($str[0] === self::getIdentifierQuote()) {
427427
return Token::FLAG_SYMBOL_BACKTICK;
428428
} elseif ($str[0] === ':' || $str[0] === '?') {
429429
return Token::FLAG_SYMBOL_PARAMETER;
@@ -450,6 +450,8 @@ public static function isString($str)
450450

451451
if ($str[0] === '\'') {
452452
return Token::FLAG_STRING_SINGLE_QUOTES;
453+
} elseif (self::hasMode(self::SQL_MODE_ANSI_QUOTES) && $str[0] === '"') {
454+
return null;
453455
} elseif ($str[0] === '"') {
454456
return Token::FLAG_STRING_DOUBLE_QUOTES;
455457
}
@@ -604,6 +606,29 @@ public static function escape($str, $quote = '`')
604606

605607
return $quote . str_replace($quote, $quote . $quote, $str) . $quote;
606608
}
609+
610+
/**
611+
* Returns char used to quote identifiers based on currently set SQL Mode (ie. standard or ANSI_QUOTES)
612+
* @return string either " (double quote, ansi_quotes mode) or ` (backtick, standard mode)
613+
*/
614+
public static function getIdentifierQuote()
615+
{
616+
return self::hasMode(self::SQL_MODE_ANSI_QUOTES) ? '"' : '`';
617+
}
618+
619+
/**
620+
* Function verifies that given SQL Mode constant is currently set
621+
*
622+
* @return boolean false on empty param, true/false on given constant/int value
623+
* @param int $flag for example Context::SQL_MODE_ANSI_QUOTES
624+
*/
625+
public static function hasMode($flag = null)
626+
{
627+
if (empty($flag)) {
628+
return false;
629+
}
630+
return (self::$MODE & $flag) === $flag;
631+
}
607632
}
608633

609634
// Initializing the default context.

src/Lexer.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,39 @@ public function lex()
346346

347347
// Saving the tokens list.
348348
$this->list = $list;
349+
350+
$this->solveAmbiguityOnStarOperator();
351+
}
352+
353+
/**
354+
* Resolves the ambiguity when dealing with the "*" operator.
355+
*
356+
* In SQL statements, the "*" operator can be an arithmetic operator (like in 2*3) or an SQL wildcard (like in
357+
* SELECT a.* FROM ...). To solve this ambiguity, the solution is to find the next token, excluding whitespaces and
358+
* comments, right after the "*" position. The "*" is for sure an SQL wildcard if the next token found is any of:
359+
* - "FROM" (the FROM keyword like in "SELECT * FROM...");
360+
* - "USING" (the USING keyword like in "DELETE table_name.* USING...");
361+
* - "," (a comma separator like in "SELECT *, field FROM...");
362+
* - ")" (a closing parenthesis like in "COUNT(*)").
363+
* This methods will change the flag of the "*" tokens when any of those condition above is true. Otherwise, the
364+
* default flag (arithmetic) will be kept.
365+
*
366+
* @return void
367+
*/
368+
private function solveAmbiguityOnStarOperator()
369+
{
370+
$iBak = $this->list->idx;
371+
while (null !== ($starToken = $this->list->getNextOfTypeAndValue(Token::TYPE_OPERATOR, '*'))) {
372+
// ::getNext already gets rid of whitespaces and comments.
373+
if (($next = $this->list->getNext()) !== null) {
374+
if (($next->type === Token::TYPE_KEYWORD && in_array($next->value, ['FROM', 'USING'], true))
375+
|| ($next->type === Token::TYPE_OPERATOR && in_array($next->value, [',', ')'], true))
376+
) {
377+
$starToken->flags = Token::FLAG_OPERATOR_SQL;
378+
}
379+
}
380+
}
381+
$this->list->idx = $iBak;
349382
}
350383

351384
/**
@@ -842,6 +875,7 @@ public function parseNumber()
842875
* @param string $quote additional starting symbol
843876
*
844877
* @return null|Token
878+
* @throws LexerException
845879
*/
846880
public function parseString($quote = '')
847881
{
@@ -889,6 +923,7 @@ public function parseString($quote = '')
889923
* Parses a symbol.
890924
*
891925
* @return null|Token
926+
* @throws LexerException
892927
*/
893928
public function parseSymbol()
894929
{
@@ -914,7 +949,7 @@ public function parseSymbol()
914949
$str = null;
915950

916951
if ($this->last < $this->len) {
917-
if (($str = $this->parseString('`')) === null) {
952+
if (($str = $this->parseString(Context::getIdentifierQuote())) === null) {
918953
if (($str = $this->parseUnknown()) === null) {
919954
$this->error(
920955
'Variable name was expected.',

src/Parser.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ public function __construct($list = null, $strict = false)
375375

376376
/**
377377
* Builds the parse trees.
378+
* @throws ParserException
378379
*/
379380
public function parse()
380381
{

src/Statement.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ public function build()
194194
*
195195
* @param Parser $parser the instance that requests parsing
196196
* @param TokensList $list the list of tokens to be parsed
197+
* @throws Exceptions\ParserException
197198
*/
198199
public function parse(Parser $parser, TokensList $list)
199200
{
@@ -478,6 +479,7 @@ public function __toString()
478479
* @param TokensList $list the list of tokens to be parsed
479480
*
480481
* @return bool
482+
* @throws Exceptions\ParserException
481483
*/
482484
public function validateClauseOrder($parser, $list)
483485
{

src/Utils/CLI.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public function mergeLongOpts(&$params, &$longopts)
3636

3737
public function usageHighlight()
3838
{
39-
echo "Usage: highlight-query --query SQL [--format html|cli|text]\n";
39+
echo "Usage: highlight-query --query SQL [--format html|cli|text] [--ansi]\n";
4040
echo " cat file.sql | highlight-query\n";
4141
}
4242

@@ -51,9 +51,10 @@ public function parseHighlight()
5151
'help',
5252
'query:',
5353
'format:',
54+
'ansi',
5455
];
5556
$params = $this->getopt(
56-
'hq:f:',
57+
'hq:f:a',
5758
$longopts
5859
);
5960
if ($params === false) {
@@ -93,6 +94,9 @@ public function runHighlight()
9394
}
9495
}
9596

97+
if (isset($params['a'])) {
98+
Context::setMode('ANSI_QUOTES');
99+
}
96100
if (isset($params['q'])) {
97101
echo Formatter::format(
98102
$params['q'],
@@ -111,7 +115,7 @@ public function runHighlight()
111115

112116
public function usageLint()
113117
{
114-
echo "Usage: lint-query --query SQL\n";
118+
echo "Usage: lint-query --query SQL [--ansi]\n";
115119
echo " cat file.sql | lint-query\n";
116120
}
117121

@@ -121,9 +125,10 @@ public function parseLint()
121125
'help',
122126
'query:',
123127
'context:',
128+
'ansi',
124129
];
125130
$params = $this->getopt(
126-
'hq:c:',
131+
'hq:c:a',
127132
$longopts
128133
);
129134
$this->mergeLongOpts($params, $longopts);
@@ -153,6 +158,9 @@ public function runLint()
153158
$params['q'] = $stdIn;
154159
}
155160
}
161+
if (isset($params['a'])) {
162+
Context::setMode('ANSI_QUOTES');
163+
}
156164

157165
if (isset($params['q'])) {
158166
$lexer = new Lexer($params['q'], false);
@@ -177,7 +185,7 @@ public function runLint()
177185

178186
public function usageTokenize()
179187
{
180-
echo "Usage: tokenize-query --query SQL\n";
188+
echo "Usage: tokenize-query --query SQL [--ansi]\n";
181189
echo " cat file.sql | tokenize-query\n";
182190
}
183191

@@ -186,9 +194,10 @@ public function parseTokenize()
186194
$longopts = [
187195
'help',
188196
'query:',
197+
'ansi',
189198
];
190199
$params = $this->getopt(
191-
'hq:',
200+
'hq:a',
192201
$longopts
193202
);
194203
$this->mergeLongOpts($params, $longopts);
@@ -215,6 +224,9 @@ public function runTokenize()
215224
}
216225
}
217226

227+
if (isset($params['a'])) {
228+
Context::setMode('ANSI_QUOTES');
229+
}
218230
if (isset($params['q'])) {
219231
$lexer = new Lexer($params['q'], false);
220232
foreach ($lexer->list->tokens as $idx => $token) {

tests/Lexer/LexerTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ public function lexProvider()
7474
['lexer/lexKeyword2'],
7575
['lexer/lexNumber'],
7676
['lexer/lexOperator'],
77+
['lexer/lexOperatorStarIsArithmetic'],
78+
['lexer/lexOperatorStarIsWildcard'],
7779
['lexer/lexString'],
7880
['lexer/lexStringErr1'],
7981
['lexer/lexSymbol'],

tests/Parser/ParserTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public function parseProvider()
2828
['parser/parse'],
2929
['parser/parse2'],
3030
['parser/parseDelimiter'],
31+
['parser/ansi/parseAnsi'],
3132
];
3233
}
3334

tests/TestCase.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use PhpMyAdmin\SqlParser\Lexer;
1212
use PhpMyAdmin\SqlParser\Parser;
1313
use PhpMyAdmin\SqlParser\TokensList;
14+
use PhpMyAdmin\SqlParser\Context;
1415
use PHPUnit\Framework\TestCase as BaseTestCase;
1516
use function file_get_contents;
1617
use function unserialize;
@@ -99,6 +100,11 @@ public function runParserTest($name)
99100
*/
100101
$data = $this->getData($name);
101102

103+
if (strpos($name, '/ansi/') !== false) {
104+
// set mode if appropriate
105+
Context::setMode('ANSI_QUOTES');
106+
}
107+
102108
// Lexer.
103109
$lexer = new Lexer($data['query']);
104110
$lexerErrors = $this->getErrorsAsArray($lexer);
@@ -119,5 +125,8 @@ public function runParserTest($name)
119125
// Testing errors.
120126
$this->assertEquals($data['errors']['parser'], $parserErrors);
121127
$this->assertEquals($data['errors']['lexer'], $lexerErrors);
128+
129+
// reset mode after test run
130+
Context::setMode();
122131
}
123132
}

0 commit comments

Comments
 (0)