Skip to content

Commit fe19973

Browse files
committed
Improved WHERE keyword parser and query's utilities.
1 parent 532ada6 commit fe19973

8 files changed

Lines changed: 82 additions & 68 deletions

File tree

src/Fragments/WhereKeyword.php

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,14 @@ class WhereKeyword extends Fragment
3030
*
3131
* @var array
3232
*/
33-
public static $OPERATORS = array('&&', '(', ')', 'AND', 'OR', 'XOR', '||');
33+
public static $OPERATORS = array('&&', 'AND', 'OR', 'XOR', '||');
34+
35+
/**
36+
* Identifiers recognized.
37+
*
38+
* @var array
39+
*/
40+
public $identifiers = array();
3441

3542
/**
3643
* Whether this fragment is an operator.
@@ -67,11 +74,13 @@ public static function parse(Parser $parser, TokensList $list, array $options =
6774
{
6875
$ret = array();
6976

77+
$expr = new WhereKeyword();
78+
7079
/**
71-
* The condition that was parsed so far.
72-
* @var string
80+
* Counts brackets.
81+
* @var int
7382
*/
74-
$tmp = '';
83+
$brackets = 0;
7584

7685
for (; $list->idx < $list->count; ++$list->idx) {
7786

@@ -93,32 +102,50 @@ public static function parse(Parser $parser, TokensList $list, array $options =
93102

94103
// Conditions are delimited by logical operators.
95104
if (in_array($token->value, static::$OPERATORS, true)) {
96-
if (!empty(trim($tmp))) {
105+
$expr->expr = trim($expr->expr);
106+
if (!empty($expr->expr)) {
97107
// Adding the condition that is delimited by this operator.
98-
$ret[] = new WhereKeyword($tmp);
99-
$tmp = '';
108+
$ret[] = $expr;
100109
}
101110

102111
// Adding the operator.
103112
$expr = new WhereKeyword($token->value);
104113
$expr->isOperator = true;
105114
$ret[] = $expr;
106115

116+
$expr = new WhereKeyword();
107117
continue;
108118
}
109119

120+
if ($token->type === Token::TYPE_OPERATOR) {
121+
if ($token->value === '(') {
122+
++$brackets;
123+
} elseif ($token->value === ')') {
124+
--$brackets;
125+
}
126+
}
127+
110128
// No keyword is expected.
111129
if (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_RESERVED)) {
112-
break;
130+
if ($brackets == 0) {
131+
break;
132+
}
113133
}
114134

115-
$tmp .= $token->token;
116-
135+
$expr->expr .= $token->token;
136+
if (($token->type === Token::TYPE_NONE)
137+
|| (($token->type === Token::TYPE_KEYWORD) && (!($token->flags & Token::FLAG_KEYWORD_RESERVED)))
138+
|| ($token->type === Token::TYPE_STRING)
139+
|| ($token->type === Token::TYPE_SYMBOL)
140+
) {
141+
$expr->identifiers[] = $token->value;
142+
}
117143
}
118144

119145
// Last iteration was not processed.
120-
if (!empty(trim($tmp))) {
121-
$ret[] = new WhereKeyword($tmp);
146+
$expr->expr = trim($expr->expr);
147+
if (!empty($expr->expr)) {
148+
$ret[] = $expr;
122149
}
123150

124151
--$list->idx;

src/Utils/Query.php

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99
namespace SqlParser\Utils;
1010

11+
use SqlParser\Lexer;
1112
use SqlParser\Parser;
1213
use SqlParser\Statement;
1314
use SqlParser\Token;
@@ -406,32 +407,6 @@ public static function getAll($query)
406407
return $ret;
407408
}
408409

409-
/**
410-
* Gets the type of clause.
411-
*
412-
* @param string $clause The clause.
413-
*
414-
* @return string
415-
*/
416-
public static function getClauseType($clause)
417-
{
418-
$type = '';
419-
for ($i = 0, $len = strlen($clause); $i < $len; ++$i) {
420-
if ((empty($type)) && (ctype_space($clause[$i]))) {
421-
// Skipping whitespaces if we haven't started determining the
422-
// type.
423-
continue;
424-
}
425-
if (!ctype_alnum($clause[$i])) {
426-
// The type contains only alphanumeric characters.
427-
break;
428-
}
429-
// Adding character.
430-
$type .= $clause[$i];
431-
}
432-
return $type;
433-
}
434-
435410
/**
436411
* Gets a specific clause.
437412
*
@@ -474,11 +449,23 @@ public static function getClause($statement, $list, $clause, $type = 0, $skipFir
474449
*/
475450
$clauses = array_flip(array_keys($statement::$CLAUSES));
476451

452+
/**
453+
* Lexer used for lexing the clause.
454+
* @var Lexer
455+
*/
456+
$lexer = new Lexer($clause);
457+
458+
/**
459+
* The type of this clause.
460+
* @var string
461+
*/
462+
$clauseType = $lexer->list->getNextOfType(Token::TYPE_KEYWORD)->value;
463+
477464
/**
478465
* The index of this clause.
479466
* @var int
480467
*/
481-
$clauseIdx = $clauses[static::getClauseType($clause)];
468+
$clauseIdx = $clauses[$clauseType];
482469

483470
for ($i = $statement->first; $i <= $statement->last; ++$i) {
484471
$token = $list->tokens[$i];
@@ -493,16 +480,15 @@ public static function getClause($statement, $list, $clause, $type = 0, $skipFir
493480

494481
if ($brackets == 0) {
495482
// Checking if we changed sections.
496-
if ($token->type === Token::TYPE_KEYWORD) {
497-
if (isset($clauses[$token->value])) {
498-
if ($clauses[$token->value] >= $currIdx) {
499-
$currIdx = $clauses[$token->value];
500-
if (($skipFirst) && ($currIdx == $clauseIdx)) {
501-
// This token is skipped (not added to the old
502-
// clause) because it will be replaced.
503-
continue;
504-
}
505-
}
483+
if (($token->type === Token::TYPE_KEYWORD)
484+
&& (isset($clauses[$token->value]))
485+
&& ($clauses[$token->value] >= $currIdx)
486+
) {
487+
$currIdx = $clauses[$token->value];
488+
if (($skipFirst) && ($currIdx == $clauseIdx)) {
489+
// This token is skipped (not added to the old
490+
// clause) because it will be replaced.
491+
continue;
506492
}
507493
}
508494
}
@@ -526,25 +512,30 @@ public static function getClause($statement, $list, $clause, $type = 0, $skipFir
526512
*
527513
* @param Statement $statement The parsed query that has to be modified.
528514
* @param TokensList $list The list of tokens.
529-
* @param string $clause The clause to be replaced.
515+
* @param string $old The type of the clause that should be
516+
* replaced. This can be an entire clause.
517+
* @param string $new The new clause. If this parameter is omitted
518+
* it is considered to be equal with `$old`.
530519
* @param bool $onlyType Whether only the type of the clause should
531520
* be replaced or the entire clause.
532521
*
533522
* @return string
534523
*/
535-
public static function replaceClause($statement, $list, $clause, $onlyType = false)
524+
public static function replaceClause($statement, $list, $old, $new = null, $onlyType = false)
536525
{
537526
// TODO: Update the tokens list and the statement.
538527

528+
if ($new === null) {
529+
$new = $old;
530+
}
531+
539532
if ($onlyType) {
540-
return static::getClause($statement, $list, $clause, -1, false) . ' ' .
541-
$clause . ' ' .
542-
static::getCLause($statement, $list, $clause, 0) . ' ' .
543-
static::getClause($statement, $list, $clause, 1, false);
533+
return static::getClause($statement, $list, $old, -1, false) . ' ' .
534+
$new . ' ' . static::getCLause($statement, $list, $old, 0) . ' ' .
535+
static::getClause($statement, $list, $old, 1, false);
544536
}
545537

546-
return static::getClause($statement, $list, $clause, -1, false) . ' ' .
547-
$clause . ' ' .
548-
static::getClause($statement, $list, $clause, 1, false);
538+
return static::getClause($statement, $list, $old, -1, false) . ' ' .
539+
$new . ' ' . static::getClause($statement, $list, $old, 1, false);
549540
}
550541
}

tests/Utils/QueryTest.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -294,11 +294,6 @@ public function testGetAllEmpty()
294294
$this->assertEquals(array(), Query::getAll(''));
295295
}
296296

297-
public function testGetClauseType()
298-
{
299-
$this->assertEquals('LIMIT', Query::getClauseType(' LIMIT 0, 10 '));
300-
}
301-
302297
public function testReplaceClause()
303298
{
304299
$parser = new Parser('SELECT *, (SELECT 1) FROM film LIMIT 0, 10;');
@@ -321,6 +316,7 @@ public function testReplaceClauseOnlyKeyword()
321316
$parser->statements[0],
322317
$parser->list,
323318
'SELECT SQL_CALC_FOUND_ROWS',
319+
null,
324320
true
325321
)
326322
);

tests/data/parseArrayErr2.out

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
a:2:{s:6:"parser";O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:25:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"*";s:5:"value";s:1:"*";s:4:"type";i:2;s:5:"flags";i:1;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:8;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"FROM";s:5:"value";s:4:"FROM";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:9;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:13;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"foo";s:5:"value";s:3:"foo";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:14;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:17;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:9:"PARTITION";s:5:"value";s:9:"PARTITION";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:18;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:27;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:28;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"bar";s:5:"value";s:3:"bar";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:29;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:32;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"baz";s:5:"value";s:3:"baz";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:33;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:36;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:37;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"WHERE";s:5:"value";s:5:"WHERE";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:38;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:43;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"id";s:5:"value";s:2:"id";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:44;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:46;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:1:">";s:5:"value";s:1:">";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:47;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:48;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"0";s:5:"value";i:0;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:49;}i:23;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:50;}i:24;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:25;s:3:"idx";i:25;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\SelectStatement":15:{s:4:"expr";a:1:{i:0;O:33:"SqlParser\Fragments\FieldFragment":7:{s:8:"database";N;s:5:"table";N;s:6:"column";N;s:4:"expr";s:1:"*";s:5:"alias";N;s:8:"function";N;s:8:"subquery";N;}}s:4:"from";a:1:{i:0;O:33:"SqlParser\Fragments\FieldFragment":7:{s:8:"database";N;s:5:"table";s:3:"foo";s:6:"column";N;s:4:"expr";s:3:"foo";s:5:"alias";N;s:8:"function";N;s:8:"subquery";N;}}s:9:"partition";O:33:"SqlParser\Fragments\ArrayFragment":2:{s:3:"raw";a:1:{i:0;s:3:"bar";}s:6:"values";a:1:{i:0;s:3:"bar";}}s:5:"where";a:1:{i:0;O:32:"SqlParser\Fragments\WhereKeyword":2:{s:10:"isOperator";b:0;s:4:"expr";s:6:"id > 0";}}s:5:"group";N;s:6:"having";N;s:5:"order";N;s:5:"limit";N;s:9:"procedure";N;s:4:"into";N;s:4:"join";N;s:5:"union";a:0:{}s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":1:{s:7:"options";a:0:{}}s:5:"first";i:0;s:4:"last";i:22;}}}s:6:"errors";a:1:{i:0;a:3:{i:0;s:32:"Symbols ')' or ',' were expected";i:1;r:83;i:2;i:0;}}}
1+
a:2:{s:6:"parser";O:16:"SqlParser\Parser":4:{s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:25:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"*";s:5:"value";s:1:"*";s:4:"type";i:2;s:5:"flags";i:1;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:8;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"FROM";s:5:"value";s:4:"FROM";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:9;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:13;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"foo";s:5:"value";s:3:"foo";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:14;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:17;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:9:"PARTITION";s:5:"value";s:9:"PARTITION";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:18;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:27;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"(";s:5:"value";s:1:"(";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:28;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"bar";s:5:"value";s:3:"bar";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:29;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:32;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"baz";s:5:"value";s:3:"baz";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:33;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:1:")";s:5:"value";s:1:")";s:4:"type";i:2;s:5:"flags";i:16;s:8:"position";i:36;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:37;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"WHERE";s:5:"value";s:5:"WHERE";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:38;}i:17;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:43;}i:18;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"id";s:5:"value";s:2:"id";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:44;}i:19;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:46;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:1:">";s:5:"value";s:1:">";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:47;}i:21;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:48;}i:22;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"0";s:5:"value";i:0;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:49;}i:23;O:15:"SqlParser\Token":5:{s:5:"token";s:1:";";s:5:"value";s:1:";";s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";i:50;}i:24;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:25;s:3:"idx";i:25;}s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\SelectStatement":15:{s:4:"expr";a:1:{i:0;O:33:"SqlParser\Fragments\FieldFragment":7:{s:8:"database";N;s:5:"table";N;s:6:"column";N;s:4:"expr";s:1:"*";s:5:"alias";N;s:8:"function";N;s:8:"subquery";N;}}s:4:"from";a:1:{i:0;O:33:"SqlParser\Fragments\FieldFragment":7:{s:8:"database";N;s:5:"table";s:3:"foo";s:6:"column";N;s:4:"expr";s:3:"foo";s:5:"alias";N;s:8:"function";N;s:8:"subquery";N;}}s:9:"partition";O:33:"SqlParser\Fragments\ArrayFragment":2:{s:3:"raw";a:1:{i:0;s:3:"bar";}s:6:"values";a:1:{i:0;s:3:"bar";}}s:5:"where";a:1:{i:0;O:32:"SqlParser\Fragments\WhereKeyword":3:{s:11:"identifiers";a:1:{i:0;s:2:"id";}s:10:"isOperator";b:0;s:4:"expr";s:6:"id > 0";}}s:5:"group";N;s:6:"having";N;s:5:"order";N;s:5:"limit";N;s:9:"procedure";N;s:4:"into";N;s:4:"join";N;s:5:"union";a:0:{}s:7:"options";O:35:"SqlParser\Fragments\OptionsFragment":1:{s:7:"options";a:0:{}}s:5:"first";i:0;s:4:"last";i:22;}}}s:6:"errors";a:1:{i:0;a:3:{i:0;s:32:"Symbols ')' or ',' were expected";i:1;r:83;i:2;i:0;}}}

0 commit comments

Comments
 (0)