summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDeven Bansod <devenbansod.bits@gmail.com>2018-12-22 20:43:49 +0530
committerDeven Bansod <devenbansod.bits@gmail.com>2018-12-23 10:21:46 +0530
commitb422d7de078dc70d635b4fefd63ce8a8ec5cd857 (patch)
tree28ee1edc8e19146b326d41ba38f1679e26bca7da /src
parent09faf92c59feed5c2db7490372ba3dd3947707e8 (diff)
downloadsql-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.php193
-rw-r--r--src/Parser.php12
-rw-r--r--src/Statement.php11
-rw-r--r--src/Statements/SelectStatement.php10
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