summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDeven Bansod <devenbansod.bits@gmail.com>2017-02-18 19:40:20 +0530
committerDeven Bansod <devenbansod.bits@gmail.com>2017-02-18 20:01:24 +0530
commitf5b511c8fff580196f2efe090a06709ce748bf10 (patch)
tree54393027a02372885fa26ecbb525b70075191c27 /src
parent4cd61e0a0528039ce56a5111880ae9f86a9662cf (diff)
downloadsql-parser-f5b511c8fff580196f2efe090a06709ce748bf10.zip
sql-parser-f5b511c8fff580196f2efe090a06709ce748bf10.tar.gz
sql-parser-f5b511c8fff580196f2efe090a06709ce748bf10.tar.bz2
Parse LOAD statement properly
Fix #131 Might help to fix phpmyadmin/phpmyadmin#12345 Signed-off-by: Deven Bansod <devenbansod.bits@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/Contexts/ContextMySql50000.php4
-rw-r--r--src/Contexts/ContextMySql50100.php4
-rw-r--r--src/Contexts/ContextMySql50500.php4
-rw-r--r--src/Contexts/ContextMySql50600.php4
-rw-r--r--src/Contexts/ContextMySql50700.php4
-rw-r--r--src/Parser.php2
-rw-r--r--src/Statements/LoadStatement.php404
-rw-r--r--src/Utils/Query.php5
8 files changed, 420 insertions, 11 deletions
diff --git a/src/Contexts/ContextMySql50000.php b/src/Contexts/ContextMySql50000.php
index 2ed3035..f949e97 100644
--- a/src/Contexts/ContextMySql50000.php
+++ b/src/Contexts/ContextMySql50000.php
@@ -139,8 +139,8 @@ class ContextMySql50000 extends Context
'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 7,
'AND CHAIN' => 7, 'FULL JOIN' => 7, 'IF EXISTS' => 7, 'LEFT JOIN' => 7,
- 'LESS THAN' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7,
- 'UNION ALL' => 7,
+ 'LESS THAN' => 7, 'LOAD DATA' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7,
+ 'ON UPDATE' => 7, 'UNION ALL' => 7,
'CROSS JOIN' => 7, 'ESCAPED BY' => 7, 'FOR UPDATE' => 7, 'INNER JOIN' => 7,
'LINEAR KEY' => 7, 'NO RELEASE' => 7, 'OR REPLACE' => 7, 'RIGHT JOIN' => 7,
'ENCLOSED BY' => 7, 'LINEAR HASH' => 7, 'STARTING BY' => 7,
diff --git a/src/Contexts/ContextMySql50100.php b/src/Contexts/ContextMySql50100.php
index e6d9ed3..eaa4872 100644
--- a/src/Contexts/ContextMySql50100.php
+++ b/src/Contexts/ContextMySql50100.php
@@ -153,8 +153,8 @@ class ContextMySql50100 extends Context
'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 7,
'AND CHAIN' => 7, 'FULL JOIN' => 7, 'IF EXISTS' => 7, 'LEFT JOIN' => 7,
- 'LESS THAN' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7,
- 'UNION ALL' => 7,
+ 'LESS THAN' => 7, 'LOAD DATA' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7,
+ 'ON UPDATE' => 7, 'UNION ALL' => 7,
'CROSS JOIN' => 7, 'ESCAPED BY' => 7, 'FOR UPDATE' => 7, 'INNER JOIN' => 7,
'LINEAR KEY' => 7, 'NO RELEASE' => 7, 'OR REPLACE' => 7, 'RIGHT JOIN' => 7,
'ENCLOSED BY' => 7, 'LINEAR HASH' => 7, 'STARTING BY' => 7,
diff --git a/src/Contexts/ContextMySql50500.php b/src/Contexts/ContextMySql50500.php
index 370319f..663736a 100644
--- a/src/Contexts/ContextMySql50500.php
+++ b/src/Contexts/ContextMySql50500.php
@@ -157,8 +157,8 @@ class ContextMySql50500 extends Context
'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 7,
'AND CHAIN' => 7, 'FULL JOIN' => 7, 'IF EXISTS' => 7, 'LEFT JOIN' => 7,
- 'LESS THAN' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7,
- 'UNION ALL' => 7,
+ 'LESS THAN' => 7, 'LOAD DATA' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7,
+ 'ON UPDATE' => 7, 'UNION ALL' => 7,
'CROSS JOIN' => 7, 'ESCAPED BY' => 7, 'FOR UPDATE' => 7, 'INNER JOIN' => 7,
'LINEAR KEY' => 7, 'NO RELEASE' => 7, 'OR REPLACE' => 7, 'RIGHT JOIN' => 7,
'ENCLOSED BY' => 7, 'LINEAR HASH' => 7, 'STARTING BY' => 7,
diff --git a/src/Contexts/ContextMySql50600.php b/src/Contexts/ContextMySql50600.php
index 3994a75..8b57a06 100644
--- a/src/Contexts/ContextMySql50600.php
+++ b/src/Contexts/ContextMySql50600.php
@@ -163,8 +163,8 @@ class ContextMySql50600 extends Context
'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 7,
'AND CHAIN' => 7, 'FULL JOIN' => 7, 'IF EXISTS' => 7, 'LEFT JOIN' => 7,
- 'LESS THAN' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7,
- 'UNION ALL' => 7,
+ 'LESS THAN' => 7, 'LOAD DATA' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7,
+ 'ON UPDATE' => 7, 'UNION ALL' => 7,
'CROSS JOIN' => 7, 'ESCAPED BY' => 7, 'FOR UPDATE' => 7, 'INNER JOIN' => 7,
'LINEAR KEY' => 7, 'NO RELEASE' => 7, 'OR REPLACE' => 7, 'RIGHT JOIN' => 7,
'ENCLOSED BY' => 7, 'LINEAR HASH' => 7, 'STARTING BY' => 7,
diff --git a/src/Contexts/ContextMySql50700.php b/src/Contexts/ContextMySql50700.php
index fbc276d..3d3c7c8 100644
--- a/src/Contexts/ContextMySql50700.php
+++ b/src/Contexts/ContextMySql50700.php
@@ -169,8 +169,8 @@ class ContextMySql50700 extends Context
'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 7,
'AND CHAIN' => 7, 'FULL JOIN' => 7, 'IF EXISTS' => 7, 'LEFT JOIN' => 7,
- 'LESS THAN' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7,
- 'UNION ALL' => 7,
+ 'LESS THAN' => 7, 'LOAD DATA' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7,
+ 'ON UPDATE' => 7, 'UNION ALL' => 7,
'CROSS JOIN' => 7, 'ESCAPED BY' => 7, 'FOR UPDATE' => 7, 'INNER JOIN' => 7,
'LINEAR KEY' => 7, 'NO RELEASE' => 7, 'OR REPLACE' => 7, 'RIGHT JOIN' => 7,
'ENCLOSED BY' => 7, 'LINEAR HASH' => 7, 'STARTING BY' => 7,
diff --git a/src/Parser.php b/src/Parser.php
index a69cc46..1aaa565 100644
--- a/src/Parser.php
+++ b/src/Parser.php
@@ -69,7 +69,7 @@ class Parser extends Core
'DO' => '',
'HANDLER' => '',
'INSERT' => 'PhpMyAdmin\\SqlParser\\Statements\\InsertStatement',
- 'LOAD' => '',
+ 'LOAD DATA' => 'PhpMyAdmin\\SqlParser\\Statements\\LoadStatement',
'REPLACE' => 'PhpMyAdmin\\SqlParser\\Statements\\ReplaceStatement',
'SELECT' => 'PhpMyAdmin\\SqlParser\\Statements\\SelectStatement',
'UPDATE' => 'PhpMyAdmin\\SqlParser\\Statements\\UpdateStatement',
diff --git a/src/Statements/LoadStatement.php b/src/Statements/LoadStatement.php
new file mode 100644
index 0000000..858885e
--- /dev/null
+++ b/src/Statements/LoadStatement.php
@@ -0,0 +1,404 @@
+<?php
+
+/**
+ * `LOAD` statement.
+ */
+
+namespace PhpMyAdmin\SqlParser\Statements;
+
+use PhpMyAdmin\SqlParser\Components\ArrayObj;
+use PhpMyAdmin\SqlParser\Components\Condition;
+use PhpMyAdmin\SqlParser\Components\Expression;
+use PhpMyAdmin\SqlParser\Components\ExpressionArray;
+use PhpMyAdmin\SqlParser\Components\FunctionCall;
+use PhpMyAdmin\SqlParser\Components\IntoKeyword;
+use PhpMyAdmin\SqlParser\Components\JoinKeyword;
+use PhpMyAdmin\SqlParser\Components\Limit;
+use PhpMyAdmin\SqlParser\Components\OrderKeyword;
+use PhpMyAdmin\SqlParser\Components\OptionsArray;
+use PhpMyAdmin\SqlParser\Components\SetOperation;
+use PhpMyAdmin\SqlParser\Parser;
+use PhpMyAdmin\SqlParser\Statement;
+use PhpMyAdmin\SqlParser\Token;
+use PhpMyAdmin\SqlParser\TokensList;
+
+/**
+ * `LOAD` statement.
+ *
+ * LOAD DATA [LOW_PRIORITY | CONCURRENT] [LOCAL] INFILE 'file_name'
+ * [REPLACE | IGNORE]
+ * INTO TABLE tbl_name
+ * [PARTITION (partition_name,...)]
+ * [CHARACTER SET charset_name]
+ * [{FIELDS | COLUMNS}
+ * [TERMINATED BY 'string']
+ * [[OPTIONALLY] ENCLOSED BY 'char']
+ * [ESCAPED BY 'char']
+ * ]
+ * [LINES
+ * [STARTING BY 'string']
+ * [TERMINATED BY 'string']
+ * ]
+ * [IGNORE number {LINES | ROWS}]
+ * [(col_name_or_user_var,...)]
+ * [SET col_name = expr,...]
+ *
+ *
+ * @category Statements
+ *
+ * @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
+ */
+class LoadStatement extends Statement
+{
+ /**
+ * Options for `LOAD` statements and their slot ID.
+ *
+ * @var array
+ */
+ public static $OPTIONS = array(
+ 'LOW_PRIORITY' => 1,
+ 'CONCURRENT' => 1,
+ 'LOCAL' => 2
+ );
+
+ /**
+ * FIELDS/COLUMNS Options for `LOAD DATA...INFILE` statements.
+ *
+ * @var array
+ */
+ public static $FIELDS_OPTIONS = array(
+ 'TERMINATED BY' => array(1, 'expr'),
+ 'OPTIONALLY' => 2,
+ 'ENCLOSED BY' => array(3, 'expr'),
+ 'ESCAPED BY' => array(4, 'expr'),
+ );
+
+ /**
+ * LINES Options for `LOAD DATA...INFILE` statements.
+ *
+ * @var array
+ */
+ public static $LINES_OPTIONS = array(
+ 'STARTING BY' => array(1, 'expr'),
+ 'TERMINATED BY' => array(2, 'expr'),
+ );
+
+ /**
+ * File name being used to load data
+ *
+ * @var Expression
+ */
+ public $file_name;
+
+ /**
+ * Table used as destination for this statement.
+ *
+ * @var Expression
+ */
+ public $table;
+
+ /**
+ * Partitions used as source for this statement.
+ *
+ * @var ArrayObj
+ */
+ public $partition;
+
+ /**
+ * Character set used in this statement.
+ *
+ * @var Expression
+ */
+ public $charset_name;
+
+ /**
+ * Options for FIELDS/COLUMNS keyword.
+ *
+ * @var OptionsArray
+ *
+ * @see static::$FIELDS_OPTIONS
+ */
+ public $fields_options;
+
+ /**
+ * Whether to use `FIELDS` or `COLUMNS` while building.
+ *
+ * @var bool
+ */
+ public $fields_keyword;
+
+ /**
+ * Options for OPTIONS keyword.
+ *
+ * @var OptionsArray
+ *
+ * @see static::$LINES_OPTIONS
+ */
+ public $lines_options;
+
+ /**
+ * Column names or user variables
+ *
+ * @var ExpressionArray
+ */
+ public $col_name_or_user_var;
+
+ /**
+ * SET clause's updated values(optional)
+ *
+ * @var SetOperation[]
+ */
+ public $set;
+
+ /**
+ * Ignore 'number' LINES/ROWS
+ *
+ * @var Expression
+ */
+ public $ignore_number;
+
+ /**
+ * REPLACE/IGNORE Keyword
+ *
+ * @var string
+ */
+ public $replace_ignore;
+
+ /**
+ * LINES/ROWS Keyword
+ *
+ * @var string
+ */
+ public $lines_rows;
+
+ /**
+ * @return string
+ */
+ public function build()
+ {
+ $ret = 'LOAD DATA ' . $this->options
+ . ' INFILE ' . $this->file_name;
+
+ if ($this->replace_ignore !== null) {
+ $ret .= ' ' . trim($this->replace_ignore);
+ }
+
+ $ret .= ' INTO TABLE ' . $this->table;
+
+ if ($this->partition !== null && count($this->partition) > 0) {
+ $ret .= ' PARTITION ' . ArrayObj::build($this->partition);
+ }
+
+ if ($this->charset_name !== null) {
+ $ret .= ' CHARACTER SET ' . $this->charset_name;
+ }
+
+ if ($this->fields_keyword !== null) {
+ $ret .= ' ' . $this->fields_keyword . ' ' . $this->fields_options;
+ }
+
+ if ($this->lines_options !== null && count($this->lines_options) > 0) {
+ $ret .= ' LINES ' . $this->lines_options;
+ }
+
+ if ($this->ignore_number !== null) {
+ $ret .= ' IGNORE ' . $this->ignore_number . ' ' . $this->lines_rows;
+ }
+
+ if ($this->col_name_or_user_var !== null && count($this->col_name_or_user_var) > 0) {
+ $ret .= ' ' . ExpressionArray::build($this->col_name_or_user_var);
+ }
+
+ if ($this->set !== null && count($this->set) > 0) {
+ $ret .= ' SET ' . SetOperation::build($this->set);
+ }
+
+ return $ret;
+ }
+
+ /**
+ * @param Parser $parser the instance that requests parsing
+ * @param TokensList $list the list of tokens to be parsed
+ */
+ public function parse(Parser $parser, TokensList $list)
+ {
+ ++$list->idx; // Skipping `LOAD DATA`.
+
+ // parse any options if provided
+ $this->options = OptionsArray::parse(
+ $parser,
+ $list,
+ static::$OPTIONS
+ );
+ ++$list->idx;
+
+ /**
+ * The state of the parser.
+ *
+ * @var int
+ */
+ $state = 0;
+
+ 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;
+ }
+
+ if ($state === 0) {
+ if ($token->type === Token::TYPE_KEYWORD
+ && $token->keyword !== 'INFILE'
+ ) {
+ $parser->error('Unexpected keyword.', $token);
+ break;
+ } elseif ($token->type !== Token::TYPE_KEYWORD) {
+ $parser->error('Unexpected token1.', $token);
+ break;
+ }
+
+ ++$list->idx;
+ $this->file_name = Expression::parse(
+ $parser,
+ $list,
+ array('parseField' => 'file')
+ );
+ $state = 1;
+ } elseif ($state === 1) {
+ if (($token->type === Token::TYPE_KEYWORD)
+ && ($token->keyword === 'REPLACE'
+ || $token->keyword === 'IGNORE')
+ ) {
+ $this->replace_ignore = trim($token->keyword);
+ } elseif ($token->type === Token::TYPE_KEYWORD
+ && $token->keyword === 'INTO'
+ ) {
+ $state = 2;
+ }
+ } elseif ($state === 2) {
+ if ($token->type === Token::TYPE_KEYWORD
+ && $token->keyword === 'TABLE'
+ ) {
+ $list->idx++;
+ $this->table = Expression::parse($parser, $list, array('parseField' => 'table'));
+ $state = 3;
+ } else {
+ $parser->error('Unexpected token2.', $token);
+ break;
+ }
+ } elseif ($state >= 3 && $state <= 7) {
+ if ($token->type === Token::TYPE_KEYWORD) {
+ $newState = $this->parseKeywordsAccordingToState(
+ $parser, $list, $state
+ );
+ if ($newState === $state) {
+ // Avoid infinite loop
+ break;
+ }
+ } elseif ($token->type === Token::TYPE_OPERATOR
+ && $token->token === '('
+ ) {
+ $this->col_name_or_user_var
+ = ExpressionArray::parse($parser, $list);
+ $state = 7;
+ } else {
+ $parser->error('Unexpected token3.', $token);
+ break;
+ }
+ }
+ }
+
+ --$list->idx;
+ }
+
+ public function parseFileOptions(Parser $parser, TokensList $list, $keyword = 'FIELDS')
+ {
+ ++$list->idx;
+
+ if ($keyword === 'FIELDS' || $keyword === 'COLUMNS') {
+ // parse field options
+ $this->fields_options = OptionsArray::parse(
+ $parser,
+ $list,
+ static::$FIELDS_OPTIONS
+ );
+
+ $this->fields_keyword = $keyword;
+ } else {
+ // parse line options
+ $this->lines_options = OptionsArray::parse(
+ $parser,
+ $list,
+ static::$LINES_OPTIONS
+ );
+ }
+ }
+
+
+ public function parseKeywordsAccordingToState($parser, $list, $state) {
+ $token = $list->tokens[$list->idx];
+
+ switch ($state) {
+ case 3:
+ if ($token->keyword === 'PARTITION') {
+ $list->idx++;
+ $this->partition = ArrayObj::parse($parser, $list);
+ $state = 4;
+ return $state;
+ }
+ case 4:
+ if ($token->keyword === 'CHARACTER SET') {
+ $list->idx++;
+ $this->charset_name = Expression::parse($parser, $list);
+ $state = 5;
+ return $state;
+ }
+ case 5:
+ if ($token->keyword === 'FIELDS'
+ || $token->keyword === 'COLUMNS'
+ || $token->keyword === 'LINES'
+ ) {
+ $this->parseFileOptions($parser, $list, $token->value);
+ $state = 6;
+ return $state;
+ }
+ case 6:
+ if ($token->keyword === 'IGNORE') {
+ $list->idx++;
+
+ $this->ignore_number = Expression::parse($parser, $list);
+ $nextToken = $list->getNextOfType(Token::TYPE_KEYWORD);
+
+ if ($nextToken->type === Token::TYPE_KEYWORD
+ && (($nextToken->keyword === 'LINES')
+ || ($nextToken->keyword === 'ROWS'))
+ ) {
+ $this->lines_rows = $nextToken->token;
+ }
+ $state = 7;
+ return $state;
+ }
+ case 7:
+ if ($token->keyword === 'SET') {
+ $list->idx++;
+ $this->set = SetOperation::parse($parser, $list);
+ $state = 8;
+ return $state;
+ }
+ default:
+ }
+ return $state;
+ }
+
+}
diff --git a/src/Utils/Query.php b/src/Utils/Query.php
index 5ad701e..a62537e 100644
--- a/src/Utils/Query.php
+++ b/src/Utils/Query.php
@@ -20,6 +20,7 @@ use PhpMyAdmin\SqlParser\Statements\DeleteStatement;
use PhpMyAdmin\SqlParser\Statements\DropStatement;
use PhpMyAdmin\SqlParser\Statements\ExplainStatement;
use PhpMyAdmin\SqlParser\Statements\InsertStatement;
+use PhpMyAdmin\SqlParser\Statements\LoadStatement;
use PhpMyAdmin\SqlParser\Statements\OptimizeStatement;
use PhpMyAdmin\SqlParser\Statements\RenameStatement;
use PhpMyAdmin\SqlParser\Statements\RepairStatement;
@@ -340,6 +341,10 @@ class Query
$flags['querytype'] = 'INSERT';
$flags['is_affected'] = true;
$flags['is_insert'] = true;
+ } elseif ($statement instanceof LoadStatement) {
+ $flags['querytype'] = 'LOAD';
+ $flags['is_affected'] = true;
+ $flags['is_insert'] = true;
} elseif ($statement instanceof ReplaceStatement) {
$flags['querytype'] = 'REPLACE';
$flags['is_affected'] = true;