diff options
author | Deven Bansod <devenbansod.bits@gmail.com> | 2018-12-22 20:43:49 +0530 |
---|---|---|
committer | Deven Bansod <devenbansod.bits@gmail.com> | 2018-12-23 10:21:46 +0530 |
commit | b422d7de078dc70d635b4fefd63ce8a8ec5cd857 (patch) | |
tree | 28ee1edc8e19146b326d41ba38f1679e26bca7da /src | |
parent | 09faf92c59feed5c2db7490372ba3dd3947707e8 (diff) | |
download | sql-parser-b422d7de078dc70d635b4fefd63ce8a8ec5cd857.zip sql-parser-b422d7de078dc70d635b4fefd63ce8a8ec5cd857.tar.gz sql-parser-b422d7de078dc70d635b4fefd63ce8a8ec5cd857.tar.bz2 |
Add support for INDEX hints in SELECT statement
Signed-off-by: Deven Bansod <devenbansod.bits@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/Components/IndexHint.php | 193 | ||||
-rw-r--r-- | src/Parser.php | 12 | ||||
-rw-r--r-- | src/Statement.php | 11 | ||||
-rw-r--r-- | src/Statements/SelectStatement.php | 10 |
4 files changed, 226 insertions, 0 deletions
diff --git a/src/Components/IndexHint.php b/src/Components/IndexHint.php new file mode 100644 index 0000000..9534d2d --- /dev/null +++ b/src/Components/IndexHint.php @@ -0,0 +1,193 @@ +<?php + +/** + * Parses an Index hint. + */ + +namespace PhpMyAdmin\SqlParser\Components; + +use PhpMyAdmin\SqlParser\Component; +use PhpMyAdmin\SqlParser\Parser; +use PhpMyAdmin\SqlParser\Token; +use PhpMyAdmin\SqlParser\TokensList; + +/** + * Parses an Index hint. + * + * @category Components + * + * @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+ + */ +class IndexHint extends Component +{ + /** + * The type of hint (USE/FORCE/IGNORE) + * + * @var string + */ + public $type; + + /** + * What the hint is for (INDEX/KEY) + * + * @var string + */ + public $indexOrKey; + + /** + * The clause for which this hint is (JOIN/ORDER BY/GROUP BY) + * + * @var string + */ + public $for; + + /** + * List of indexes in this hint + * + * @var array + */ + public $indexes = array(); + + /** + * Constructor. + * + * @param string $type the type of hint (USE/FORCE/IGNORE) + * @param string $indexOrKey What the hint is for (INDEX/KEY) + * @param string $for the clause for which this hint is (JOIN/ORDER BY/GROUP BY) + * @param string $indexes List of indexes in this hint + */ + public function __construct(string $type = null, string $indexOrKey = null, string $for = null, array $indexes = array()) + { + $this->type = $type; + $this->indexOrKey = $indexOrKey; + $this->for = $for; + $this->indexes = $indexes; + } + + /** + * @param Parser $parser the parser that serves as context + * @param TokensList $list the list of tokens that are being parsed + * @param array $options parameters for parsing + * + * @return IndexHint|Component[] + */ + public static function parse(Parser $parser, TokensList $list, array $options = array()) + { + $ret = array(); + $expr = new self(); + $expr->type = isset($options['type']) ? $options['type'] : null; + /** + * The state of the parser. + * + * Below are the states of the parser. + * 0 ----------------- [ USE/IGNORE/FORCE ]-----------------> 1 + * 1 -------------------- [ INDEX/KEY ] --------------------> 2 + * 2 ----------------------- [ FOR ] -----------------------> 3 + * 2 -------------------- [ expr_list ] --------------------> 0 + * 3 -------------- [ JOIN/GROUP BY/ORDER BY ] -------------> 4 + * 4 -------------------- [ expr_list ] --------------------> 0 + * @var int + */ + $state = 0; + + // By design, the parser will parse first token after the keyword. So, the keyword + // must be analyzed too, in order to determine the type of this index hint. + if ($list->idx > 0) { + --$list->idx; + } + for (; $list->idx < $list->count; ++$list->idx) { + /** + * Token parsed at this moment. + * + * @var Token + */ + $token = $list->tokens[$list->idx]; + + // End of statement. + if ($token->type === Token::TYPE_DELIMITER) { + break; + } + // Skipping whitespaces and comments. + if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) { + continue; + } + + switch ($state) { + case 0: + if ($token->type === Token::TYPE_KEYWORD) { + if ($token->keyword === 'USE' || $token->keyword === 'IGNORE' || $token->keyword === 'FORCE') { + $expr->type = $token->keyword; + $state = 1; + } else { + break 2; + } + } + break; + case 1: + if ($token->type === Token::TYPE_KEYWORD) { + if ($token->keyword === 'INDEX' || $token->keyword === 'KEY') { + $expr->indexOrKey = $token->keyword; + } else { + $parser->error('Unexpected keyword.', $token); + } + $state = 2; + } else { + // we expect the token to be a keyword + $parser->error('Unexpected token.', $token); + } + break; + case 2: + if ($token->type === Token::TYPE_KEYWORD && $token->keyword === 'FOR') { + $state = 3; + } else { + $expr->indexes = ExpressionArray::parse($parser, $list); + $state = 0; + $ret[] = $expr; + $expr = new self(); + } + break; + case 3: + if ($token->type === Token::TYPE_KEYWORD) { + if ($token->keyword === 'JOIN' || $token->keyword === 'GROUP BY' || $token->keyword === 'ORDER BY') { + $expr->for = $token->keyword; + } else { + $parser->error('Unexpected keyword.', $token); + } + $state = 4; + } else { + // we expect the token to be a keyword + $parser->error('Unexpected token.', $token); + } + break; + case 4: + $expr->indexes = ExpressionArray::parse($parser, $list); + $state = 0; + $ret[] = $expr; + $expr = new self(); + break; + } + } + --$list->idx; + + return $ret; + } + + /** + * @param ArrayObj|ArrayObj[] $component the component to be built + * @param array $options parameters for building + * + * @return string + */ + public static function build($component, array $options = array()) + { + if (is_array($component)) { + return implode(' ', $component); + } + + $ret = $component->type . ' ' . $component->indexOrKey . ' '; + if ($component->for !== null) { + $ret .= 'FOR ' . $component->for . ' '; + } + return $ret . ExpressionArray::build($component->indexes); + } +} diff --git a/src/Parser.php b/src/Parser.php index c4db416..ec8637a 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -177,6 +177,10 @@ class Parser extends Core 'field' => 'fields', 'options' => array('parseField' => 'table'), ), + 'FORCE' => array( + 'class' => 'PhpMyAdmin\\SqlParser\\Components\\IndexHint', + 'field' => 'index_hints', + ), 'FROM' => array( 'class' => 'PhpMyAdmin\\SqlParser\\Components\\ExpressionArray', 'field' => 'from', @@ -190,6 +194,10 @@ class Parser extends Core 'class' => 'PhpMyAdmin\\SqlParser\\Components\\Condition', 'field' => 'having', ), + 'IGNORE' => array( + 'class' => 'PhpMyAdmin\\SqlParser\\Components\\IndexHint', + 'field' => 'index_hints', + ), 'INTO' => array( 'class' => 'PhpMyAdmin\\SqlParser\\Components\\IntoKeyword', 'field' => 'into', @@ -308,6 +316,10 @@ class Parser extends Core 'field' => 'tables', 'options' => array('parseField' => 'table'), ), + 'USE' => array( + 'class' => 'PhpMyAdmin\\SqlParser\\Components\\IndexHint', + 'field' => 'index_hints', + ), 'VALUE' => array( 'class' => 'PhpMyAdmin\\SqlParser\\Components\\Array2d', 'field' => 'values', diff --git a/src/Statement.php b/src/Statement.php index ec715c2..6eb42c7 100644 --- a/src/Statement.php +++ b/src/Statement.php @@ -497,6 +497,17 @@ abstract class Statement $clauseType ); + if ($clauseStartIdx !== -1 + && $this instanceof Statements\SelectStatement + && ($clauseType === 'FORCE' + || $clauseType === 'IGNORE' + || $clauseType === 'USE') + ) { + // TODO: ordering of clauses in a SELECT statement with + // Index hints is not supported + return true; + } + // Handle ordering of Multiple Joins in a query if ($clauseStartIdx !== -1) { if ($minJoin === 0 && stripos($clauseType, 'JOIN')) { diff --git a/src/Statements/SelectStatement.php b/src/Statements/SelectStatement.php index f2f94a8..fbcae48 100644 --- a/src/Statements/SelectStatement.php +++ b/src/Statements/SelectStatement.php @@ -91,6 +91,9 @@ class SelectStatement extends Statement '_SELECT' => array('SELECT', 1), 'INTO' => array('INTO', 3), 'FROM' => array('FROM', 3), + 'FORCE' => array('FORCE', 1), + 'USE' => array('USE', 1), + 'IGNORE' => array('IGNORE', 3), 'PARTITION' => array('PARTITION', 3), 'JOIN' => array('JOIN', 1), @@ -136,6 +139,13 @@ class SelectStatement extends Statement public $from = array(); /** + * Index hints + * + * @var IndexHint[] + */ + public $index_hints; + + /** * Partitions used as source for this statement. * * @var ArrayObj |