diff options
-rw-r--r-- | CHANGELOG.md | 2 | ||||
-rw-r--r-- | src/Statement.php | 36 | ||||
-rw-r--r-- | src/Utils/Formatter.php | 10 | ||||
-rw-r--r-- | tests/Parser/SelectStatementTest.php | 1 | ||||
-rw-r--r-- | tests/Utils/FormatterTest.php | 7 | ||||
-rw-r--r-- | tests/data/parser/parseSelectWrongOrder2.in | 1 | ||||
-rw-r--r-- | tests/data/parser/parseSelectWrongOrder2.out | 1 |
7 files changed, 47 insertions, 11 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 5080711..45593fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * Coding style improvements. * Chinese localization. +* Improved order validatin for JOIN clauses. +* Improved pretty printing of JOIN clauses. ## [4.1.1] - 2017-02-07 diff --git a/src/Statement.php b/src/Statement.php index dc5f945..50b1285 100644 --- a/src/Statement.php +++ b/src/Statement.php @@ -451,16 +451,26 @@ abstract class Statement /** * For tracking JOIN clauses in a query - * 0 - JOIN not found till now - * 1 - JOIN has been found - * 2 - A Non-JOIN clause has been found - * after a previously found JOIN clause. + * = 0 - JOIN not found till now + * > 0 - Index of first JOIN clause in the statement * * @var int */ - $joinStart = 0; + $minJoin = 0; + + /** + * For tracking JOIN clauses in a query + * = 0 - JOIN not found till now + * > 0 - Index of last JOIN clause + * (which appears together with other JOINs) + * in the statement + * + * @var int + */ + $maxJoin = 0; $error = 0; + $lastIdx = 0; foreach ($clauses as $clauseType => $index) { $clauseStartIdx = Utils\Query::getClauseStartOffset( $this, @@ -470,17 +480,19 @@ abstract class Statement // Handle ordering of Multiple Joins in a query if ($clauseStartIdx != -1) { - if ($joinStart == 0 && stripos($clauseType, 'JOIN') !== false) { - $joinStart = 1; - } elseif ($joinStart == 1 && stripos($clauseType, 'JOIN') === false) { - $joinStart = 2; - } elseif ($joinStart == 2 && stripos($clauseType, 'JOIN') !== false) { + if ($minJoin === 0 && stripos($clauseType, 'JOIN')) { + // First JOIN clause is detected + $minJoin = $maxJoin = $clauseStartIdx; + } elseif ($minJoin !== 0 && ! stripos($clauseType, 'JOIN')) { + // After a previous JOIN clause, a non-JOIN clause has been detected + $maxJoin = $lastIdx; + } elseif ($maxJoin < $clauseStartIdx && stripos($clauseType, 'JOIN')) { $error = 1; } } if ($clauseStartIdx != -1 && $clauseStartIdx < $minIdx) { - if ($joinStart == 0 || ($joinStart == 2 && $error = 1)) { + if ($minJoin === 0 || $error === 1) { $token = $list->tokens[$clauseStartIdx]; $parser->error( 'Unexpected ordering of clauses.', @@ -493,6 +505,8 @@ abstract class Statement } elseif ($clauseStartIdx != -1) { $minIdx = $clauseStartIdx; } + + $lastIdx = ($clauseStartIdx !== -1) ? $clauseStartIdx : $lastIdx; } return true; diff --git a/src/Utils/Formatter.php b/src/Utils/Formatter.php index 16c0778..e07394c 100644 --- a/src/Utils/Formatter.php +++ b/src/Utils/Formatter.php @@ -6,6 +6,7 @@ namespace PhpMyAdmin\SqlParser\Utils; +use PhpMyAdmin\SqlParser\Components\JoinKeyword; use PhpMyAdmin\SqlParser\Lexer; use PhpMyAdmin\SqlParser\Parser; use PhpMyAdmin\SqlParser\Token; @@ -383,6 +384,15 @@ class Formatter } } + // Inline JOINs + if (($prev->type === Token::TYPE_KEYWORD && isset(JoinKeyword::$JOINS[$prev->value])) + || (in_array($curr->value, array('ON', 'USING'), true) && isset(JoinKeyword::$JOINS[$list->tokens[$list->idx - 2]->value])) + || (isset($list->tokens[$list->idx - 4]) && isset(JoinKeyword::$JOINS[$list->tokens[$list->idx - 4]->value])) + || (isset($list->tokens[$list->idx - 6]) && isset(JoinKeyword::$JOINS[$list->tokens[$list->idx - 6]->value])) + ) { + $lineEnded = false; + } + // Indenting BEGIN ... END blocks. if ($prev->type === Token::TYPE_KEYWORD && $prev->keyword === 'BEGIN') { $lineEnded = true; diff --git a/tests/Parser/SelectStatementTest.php b/tests/Parser/SelectStatementTest.php index 5be345f..42fb955 100644 --- a/tests/Parser/SelectStatementTest.php +++ b/tests/Parser/SelectStatementTest.php @@ -63,6 +63,7 @@ class SelectStatementTest extends TestCase array('parser/parseSelectJoinMultiple'), array('parser/parseSelectJoinMultiple2'), array('parser/parseSelectWrongOrder'), + array('parser/parseSelectWrongOrder2'), array('parser/parseSelectEndOptions1'), array('parser/parseSelectEndOptions2'), array('parser/parseSelectEndOptionsErr'), diff --git a/tests/Utils/FormatterTest.php b/tests/Utils/FormatterTest.php index b8a8ff2..c209d27 100644 --- a/tests/Utils/FormatterTest.php +++ b/tests/Utils/FormatterTest.php @@ -508,6 +508,13 @@ class FormatTest extends TestCase ' <span class="sql-variable">`query`</span> <span class="sql-keyword">TEXT</span> <span class="sql-reserved">NOT NULL</span>,' . '<br/>' . ' <span class="sql-reserved">PRIMARY KEY</span>(<span class="sql-variable">`id`</span>)', ), + 'join' => array( + 'query' => 'join tbl2 on c1=c2', + 'text' => 'JOIN tbl2 ON c1 = c2', + 'cli' => "\x1b[35mJOIN \x1b[39mtbl2 \x1b[35mON \x1b[39mc1 \x1b[39m= \x1b[39mc2" . + "\x1b[0m", + 'html' => '<span class="sql-reserved">JOIN</span> tbl2 <span class="sql-reserved">ON</span> c1 = c2', + ), ); } } diff --git a/tests/data/parser/parseSelectWrongOrder2.in b/tests/data/parser/parseSelectWrongOrder2.in new file mode 100644 index 0000000..3a000ec --- /dev/null +++ b/tests/data/parser/parseSelectWrongOrder2.in @@ -0,0 +1 @@ +SELECT DISTINCT * FROM tbl1 INNER JOIN tbl2 ON id1 = id2 WHERE 1=1 LEFT OUTER JOIN l3;
\ No newline at end of file diff --git a/tests/data/parser/parseSelectWrongOrder2.out b/tests/data/parser/parseSelectWrongOrder2.out new file mode 100644 index 0000000..b83f6fb --- /dev/null +++ b/tests/data/parser/parseSelectWrongOrder2.out @@ -0,0 +1 @@ +a:4:{s:5:"query";s:86:"SELECT DISTINCT * FROM tbl1 INNER JOIN tbl2 ON id1 = id2 WHERE 1=1 LEFT OUTER JOIN l3;";s:5:"lexer";O:15:"SqlParser\Lexer":8:{s:6:"strict";b:0;s:3:"str";s:86:"SELECT DISTINCT * FROM tbl1 INNER JOIN tbl2 ON id1 = id2 WHERE 1=1 LEFT OUTER JOIN l3;";s:3:"len";i:86;s:4:"last";i:86;s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:33:{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:8:"DISTINCT";s:5:"value";s:8:"DISTINCT";s:4:"type";i:1;s:5:"flags";i:3;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:15;}i:4;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:16;}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:17;}i:6;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:18;}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:22;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"tbl1";s:5:"value";s:4:"tbl1";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:23;}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:10:"INNER JOIN";s:5:"value";s:10:"INNER JOIN";s:4:"type";i:1;s:5:"flags";i:7;s:8:"position";i:28;}i:11;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:38;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"tbl2";s:5:"value";s:4:"tbl2";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:39;}i:13;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:14;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"ON";s:5:"value";s:2:"ON";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:44;}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:46;}i:16;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"id1";s:5:"value";s:3:"id1";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:47;}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:50;}i:18;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:51;}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:52;}i:20;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"id2";s:5:"value";s:3:"id2";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:53;}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:56;}i:22;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:57;}i:23;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:62;}i:24;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"1";s:5:"value";i:1;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:63;}i:25;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:64;}i:26;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"1";s:5:"value";i:1;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:65;}i:27;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:66;}i:28;O:15:"SqlParser\Token":5:{s:5:"token";s:15:"LEFT OUTER JOIN";s:5:"value";s:15:"LEFT OUTER JOIN";s:4:"type";i:1;s:5:"flags";i:7;s:8:"position";i:67;}i:29;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:82;}i:30;O:15:"SqlParser\Token":5:{s:5:"token";s:2:"l3";s:5:"value";s:2:"l3";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:83;}i:31;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:85;}i:32;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:33;s:3:"idx";i:33;}s:9:"delimiter";s:1:";";s:12:"delimiterLen";i:1;s:6:"errors";a:0:{}}s:6:"parser";O:16:"SqlParser\Parser":5:{s:4:"list";r:8;s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\SelectStatement":16:{s:4:"expr";a:1:{i:0;O:31:"SqlParser\Components\Expression":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:31:"SqlParser\Components\Expression":7:{s:8:"database";N;s:5:"table";s:4:"tbl1";s:6:"column";N;s:4:"expr";s:4:"tbl1";s:5:"alias";N;s:8:"function";N;s:8:"subquery";N;}}s:9:"partition";N;s:5:"where";a:1:{i:0;O:30:"SqlParser\Components\Condition":3:{s:11:"identifiers";a:0:{}s:10:"isOperator";b:0;s:4:"expr";s:3:"1=1";}}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";a:1:{i:0;O:32:"SqlParser\Components\JoinKeyword":4:{s:4:"type";s:4:"LEFT";s:4:"expr";O:31:"SqlParser\Components\Expression":7:{s:8:"database";N;s:5:"table";s:2:"l3";s:6:"column";N;s:4:"expr";s:2:"l3";s:5:"alias";N;s:8:"function";N;s:8:"subquery";N;}s:2:"on";N;s:5:"using";N;}}s:5:"union";a:0:{}s:11:"end_options";N;s:7:"options";O:33:"SqlParser\Components\OptionsArray":1:{s:7:"options";a:1:{i:1;s:8:"DISTINCT";}}s:5:"first";i:0;s:4:"last";i:30;}}s:8:"brackets";i:0;}s:6:"errors";a:2:{s:5:"lexer";a:0:{}s:6:"parser";a:1:{i:0;a:3:{i:0;s:31:"Unexpected ordering of clauses.";i:1;r:142;i:2;i:0;}}}}
\ No newline at end of file |