diff options
author | Dan Ungureanu <udan1107@gmail.com> | 2015-07-03 21:28:45 +0300 |
---|---|---|
committer | Dan Ungureanu <udan1107@gmail.com> | 2015-07-10 23:18:16 +0300 |
commit | 889b49ee135c03a96f38d74a48e62d07104d87a0 (patch) | |
tree | bcee0d2cb6d352baa6c76e98015bdf1a482aae7c | |
parent | c8993af15a6d15eca95a72823888bef4c46297a9 (diff) |
Refactored Table.class.php.
Updated sql-parser library to udan11/sql-parser@b3eff80.
Fixed failing test.
Signed-off-by: Dan Ungureanu <udan1107@gmail.com>
30 files changed, 996 insertions, 617 deletions
diff --git a/libraries/DisplayResults.class.php b/libraries/DisplayResults.class.php index 175dcf0f12..ac00642b8c 100644 --- a/libraries/DisplayResults.class.php +++ b/libraries/DisplayResults.class.php @@ -3344,12 +3344,17 @@ class PMA_DisplayResults } $query = 'SELECT ' . SqlParser\Utils\Query::getClause( - $parser->statements[0], $parser->list, 'SELECT' + $analyzed_sql_results['statement'], + $analyzed_sql_results['parser']->list, + 'SELECT' ); $from_clause = SqlParser\Utils\Query::getClause( - $parser->statements[0], $parser->list, 'FROM' + $analyzed_sql_results['statement'], + $analyzed_sql_results['parser']->list, + 'FROM' ); + if (!empty($from_clause)) { $query .= ' FROM ' . $from_clause; } @@ -4345,7 +4350,7 @@ class PMA_DisplayResults } - if (($displayParts['nav_bar'] == '1') && (empty($statemet->limit))) { + if (($displayParts['nav_bar'] == '1') && (empty($statement->limit))) { $table_html .= $this->_getPlacedTableNavigations( $pos_next, $pos_prev, self::PLACE_TOP_DIRECTION_DROPDOWN, $is_innodb diff --git a/libraries/Table.class.php b/libraries/Table.class.php index f6eff4c8bb..ae76bca900 100644 --- a/libraries/Table.class.php +++ b/libraries/Table.class.php @@ -714,9 +714,10 @@ class PMA_Table static public function moveCopy($source_db, $source_table, $target_db, $target_table, $what, $move, $mode ) { + global $err_url; - /* Try moving table directly */ + // Try moving the tables directly, using native `RENAME` statement. if ($move && $what == 'data') { $tbl = new PMA_Table($source_table, $source_db); $result = $tbl->rename($target_table, $target_db); @@ -726,11 +727,11 @@ class PMA_Table } } - // set export settings we need + // Setting required export settings. $GLOBALS['sql_backquotes'] = 1; $GLOBALS['asfile'] = 1; - // Ensure the target is valid + // Ensuring the target database is valid. if (! $GLOBALS['pma']->databases->exists($source_db, $target_db)) { if (! $GLOBALS['pma']->databases->exists($source_db)) { $GLOBALS['message'] = PMA_Message::rawError( @@ -751,24 +752,40 @@ class PMA_Table return false; } + /** + * The full name of source table, quoted. + * @var string + */ $source = PMA_Util::backquote($source_db) . '.' . PMA_Util::backquote($source_table); + + // If the target database is not specified, the operation is taking + // place in the same database. if (! isset($target_db) || ! /*overload*/mb_strlen($target_db)) { $target_db = $source_db; } - // Doing a select_db could avoid some problems with replicated databases, - // when moving table from replicated one to not replicated one + // Selecting the database could avoid some problems with replicated + // databases, when moving table from replicated one to not replicated one. $GLOBALS['dbi']->selectDb($target_db); + /** + * The full name of target table, quoted. + * @var string + */ $target = PMA_Util::backquote($target_db) . '.' . PMA_Util::backquote($target_table); - // do not create the table if dataonly + // No table is created when this is a data-only operation. if ($what != 'dataonly') { + include_once "libraries/plugin_interface.lib.php"; - // get Export SQL instance - /* @var $export_sql_plugin ExportSql */ + + /** + * Instance used for exporting the current structure of the table. + * + * @var ExportSql + */ $export_sql_plugin = PMA_getPlugin( "export", "sql", @@ -786,141 +803,131 @@ class PMA_Table $GLOBALS['sql_auto_increment'] = $_POST['sql_auto_increment']; } + /** + * The old structure of the table.. + * @var string + */ $sql_structure = $export_sql_plugin->getTableDef( $source_db, $source_table, "\n", $err_url, false, false ); + unset($no_constraints_comments); - $parsed_sql = PMA_SQP_parse($sql_structure); - $analyzed_sql = PMA_SQP_analyze($parsed_sql); - $i = 0; - if (empty($analyzed_sql[0]['create_table_fields'])) { - // this is not a CREATE TABLE, so find the first VIEW - $target_for_view = PMA_Util::backquote($target_db); - while (true) { - if ($parsed_sql[$i]['type'] == 'alpha_reservedWord' - && $parsed_sql[$i]['data'] == 'VIEW' - ) { - break; - } - $i++; - } - } - unset($analyzed_sql); - if (PMA_DRIZZLE) { - $table_delimiter = 'quote_backtick'; - } else { - $server_sql_mode = $GLOBALS['dbi']->fetchValue( - "SHOW VARIABLES LIKE 'sql_mode'", - 0, - 1 - ); - // ANSI_QUOTES might be a subset of sql_mode, for example - // REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,ANSI - if (false !== /*overload*/mb_strpos($server_sql_mode, 'ANSI_QUOTES') - ) { - $table_delimiter = 'quote_double'; - } else { - $table_delimiter = 'quote_backtick'; - } - unset($server_sql_mode); - } - /* Find table name in query and replace it */ - while ($parsed_sql[$i]['type'] != $table_delimiter) { - $i++; - } + // ----------------------------------------------------------------- + // Phase 0: Preparing structures used. - /* no need to backquote() */ - if (isset($target_for_view)) { - // this a view definition; we just found the first db name - // that follows DEFINER VIEW - // so change it for the new db name - $parsed_sql[$i]['data'] = $target_for_view; - // then we have to find all references to the source db - // and change them to the target db, ensuring we stay into - // the $parsed_sql limits - $last = $parsed_sql['len'] - 1; - $backquoted_source_db = PMA_Util::backquote($source_db); - for (++$i; $i <= $last; $i++) { - if ($parsed_sql[$i]['type'] == $table_delimiter - && $parsed_sql[$i]['data'] == $backquoted_source_db - && $parsed_sql[$i - 1]['type'] != 'punct_qualifier' - ) { - $parsed_sql[$i]['data'] = $target_for_view; - } - } - unset($last,$backquoted_source_db); - } else { - $parsed_sql[$i]['data'] = $target; + /** + * The destination where the table is moved or copied to. + * @var SqlParser\Fragments\FieldFragment + */ + $destination = new SqlParser\Fragments\FieldFragment( + $target_db, $target_table, '' + ); + + // Find server's SQL mode so the builder can generate correct + // queries. + // One of the options that alters the behaviour is `ANSI_QUOTES`. + // This is not availabile for Drizzle. + if (!PMA_DRIZZLE) { + SqlParser\Context::setMode( + $GLOBALS['dbi']->fetchValue( + "SHOW VARIABLES LIKE 'sql_mode'", 0, 1 + ) + ); } - /* Generate query back */ - $sql_structure = PMA_SQP_format($parsed_sql, 'query_only'); - // If table exists, and 'add drop table' is selected: Drop it! + // ----------------------------------------------------------------- + // Phase 1: Dropping existent element of the same name (if exists + // and required). + if (isset($_REQUEST['drop_if_exists']) && $_REQUEST['drop_if_exists'] == 'true' ) { - if (PMA_Table::isView($target_db, $target_table)) { - $drop_query = 'DROP VIEW'; - } else { - $drop_query = 'DROP TABLE'; - } - $drop_query .= ' IF EXISTS ' - . PMA_Util::backquote($target_db) . '.' - . PMA_Util::backquote($target_table); - $GLOBALS['dbi']->query($drop_query); - $GLOBALS['sql_query'] .= "\n" . $drop_query . ';'; + /** + * Drop statement used for building the query. + * @var SqlParser\Statements\DropStatement + */ + $statement = new SqlParser\Statements\DropStatement(); + + $statement->options = new SqlParser\Fragments\OptionsFragment( + array( + PMA_Table::isView($target_db, $target_table) ? + 'VIEW' : 'TABLE', + 'IF EXISTS', + ) + ); + + $statement->fields = array($destination); + + // Building the query. + $drop_query = $statement->build() . ';'; + + // Executing it. + $GLOBALS['dbi']->query($drop_query); + $GLOBALS['sql_query'] .= "\n" . $drop_query; - // If an existing table gets deleted, maintain any - // entries for the PMA_* tables + // If an existing table gets deleted, maintain any entries for + // the PMA_* tables. $maintain_relations = true; } - @$GLOBALS['dbi']->query($sql_structure); - $GLOBALS['sql_query'] .= "\n" . $sql_structure . ';'; + // ----------------------------------------------------------------- + // Phase 2: Generating the new query of this structure. + + /** + * The parser responsible for parsing the old queries. + * @var SqlParser\Parser + */ + $parser = new SqlParser\Parser($sql_structure); + + /** + * The CREATE statement of this structure. + * @var SqlParser\Statements\CreateStatement + */ + $statement = $parser->statements[0]; + + // Changing the destination. + $statement->name = $destination; + + // Building back the query. + $sql_structure = $statement->build() . ';'; + + // Executing it. + $GLOBALS['dbi']->query($sql_structure); + $GLOBALS['sql_query'] .= "\n" . $sql_structure; + + // ----------------------------------------------------------------- + // Phase 3: Adding constraints. + // All constraint names are removed because they must be unique. if (($move || isset($GLOBALS['add_constraints'])) && !empty($GLOBALS['sql_constraints_query']) ) { - $parsed_sql = PMA_SQP_parse($GLOBALS['sql_constraints_query']); - $i = 0; - - // find the first $table_delimiter, it must be the source - // table name - while ($parsed_sql[$i]['type'] != $table_delimiter) { - $i++; - // maybe someday we should guard against going over limit - //if ($i == $parsed_sql['len']) { - // break; - //} - } - // replace it by the target table name, no need - // to backquote() - $parsed_sql[$i]['data'] = $target; + $parser = new SqlParser\Parser($GLOBALS['sql_constraints_query']); - // now we must remove all $table_delimiter that follow a - // CONSTRAINT keyword, because a constraint name must be - // unique in a db + /** + * The ALTER statement that generates the constraints. + * @var SqlParser\Statements\AlterStatement + */ + $statement = $parser->statements[0]; - $cnt = $parsed_sql['len'] - 1; + // Changing the altered table to the destination. + $statement->table = $destination; - for ($j = $i; $j < $cnt; $j++) { - $dataUpper = /*overload*/mb_strtoupper($parsed_sql[$j]['data']); - if ($parsed_sql[$j]['type'] == 'alpha_reservedWord' - && $dataUpper == 'CONSTRAINT' - ) { - if ($parsed_sql[$j+1]['type'] == $table_delimiter) { - $parsed_sql[$j+1]['data'] = ''; - } + // Removing the name of the constraints. + foreach ($statement->altered as $idx => $altered) { + // All constraint names are removed because they must be unique. + if ($altered->options->has('CONSTRAINT')) { + $altered->field = null; } } - // Generate query back - $GLOBALS['sql_constraints_query'] = PMA_SQP_format( - $parsed_sql, 'query_only' - ); + // Building back the query. + $GLOBALS['sql_constraints_query'] = $statement->build() . ';'; + + // Executing it. if ($mode == 'one_table') { $GLOBALS['dbi']->query($GLOBALS['sql_constraints_query']); } @@ -930,95 +937,70 @@ class PMA_Table } } - // add indexes to the table - if (!empty($GLOBALS['sql_indexes'])) { - - $index_queries = array(); - $sql_indexes = $GLOBALS['sql_indexes']; - $GLOBALS['sql_indexes'] = ''; - - $parsed_sql = PMA_SQP_parse($sql_indexes); - $cnt = $parsed_sql['len'] - 1; - $k = 0; - - for ($j = 0; $j < $cnt; $j++) { - if ($parsed_sql[$j]['type'] == 'punct_queryend') { - $index_queries[] = substr( - $sql_indexes, $k, $parsed_sql[$j]['pos'] - $k - ); - $k = $parsed_sql[$j]['pos']; - } - } + // ----------------------------------------------------------------- + // Phase 4: Adding indexes. + // View phase 3. - foreach ($index_queries as $index_query) { - - $parsed_sql = PMA_SQP_parse($index_query); - $i = 0; + if (!empty($GLOBALS['sql_indexes'])) { - while ($parsed_sql[$i]['type'] != $table_delimiter) { - $i++; - } + $parser = new SqlParser\Parser($GLOBALS['sql_indexes']); - $parsed_sql[$i]['data'] = $target; + /** + * The ALTER statement that generates the indexes. + * @var SqlParser\Statements\AlterStatement + */ + $statement = $parser->statements[0]; - $cnt = $parsed_sql['len'] - 1; + // Changing the altered table to the destination. + $statement->table = $destination; - for ($j = $i; $j < $cnt; $j++) { - $dataUpper = /*overload*/mb_strtoupper($parsed_sql[$j]['data']); - if ($parsed_sql[$j]['type'] == 'alpha_reservedWord' - && $dataUpper == 'CONSTRAINT' - ) { - if ($parsed_sql[$j+1]['type'] == $table_delimiter) { - $parsed_sql[$j+1]['data'] = ''; - } - } + // Removing the name of the constraints. + foreach ($statement->altered as $idx => $altered) { + // All constraint names are removed because they must be unique. + if ($altered->options->has('CONSTRAINT')) { + $altered->field = null; } + } - $sql_index = PMA_SQP_format($parsed_sql, 'query_only'); - if ($mode == 'one_table' || $mode == 'db_copy') { - $GLOBALS['dbi']->query($sql_index); - } + // Building back the query. + $GLOBALS['sql_indexes'] = $statement->build() . ';'; - $GLOBALS['sql_indexes'] .= $sql_index; + // Executing it. + if ($mode == 'one_table' || $mode == 'db_copy') { + $GLOBALS['dbi']->query($GLOBALS['sql_indexes']); } - $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_indexes']; if ($mode == 'one_table' || $mode == 'db_copy') { unset($GLOBALS['sql_indexes']); - } } - /* - * add AUTO_INCREMENT to the table - * - * @todo refactor with similar code above - */ + // ----------------------------------------------------------------- + // Phase 5: Adding AUTO_INCREMENT. + if (! empty($GLOBALS['sql_auto_increments'])) { if ($mode == 'one_table' || $mode == 'db_copy') { - $parsed_sql = PMA_SQP_parse($GLOBALS['sql_auto_increments']); - $i = 0; - // find the first $table_delimiter, it must be the source - // table name - while ($parsed_sql[$i]['type'] != $table_delimiter) { - $i++; - } + $parser = new SqlParser\Parser($GLOBALS['sql_auto_increments']); - // replace it by the target table name, no need - // to backquote() - $parsed_sql[$i]['data'] = $target; + /** + * The ALTER statement that alters the AUTO_INCREMENT value. + * @var SqlParser\Statements\AlterStatement + */ + $statement = $parser->statements[0]; - // Generate query back - $GLOBALS['sql_auto_increments'] = PMA_SQP_format( - $parsed_sql, 'query_only' - ); + // Changing the altered table to the destination. + $statement->table = $destination; + + // Building back the query. + $GLOBALS['sql_auto_increments'] = $statement->build() . ';'; + + // Executing it. $GLOBALS['dbi']->query($GLOBALS['sql_auto_increments']); $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_auto_increments']; unset($GLOBALS['sql_auto_increments']); } } - } else { $GLOBALS['sql_query'] = ''; } @@ -1036,7 +1018,7 @@ class PMA_Table $sql_insert_data = 'INSERT INTO ' . $target . ' SELECT * FROM ' . $source; $GLOBALS['dbi']->query($sql_insert_data); - $GLOBALS['sql_query'] .= "\n\n" . $sql_insert_data . ';'; + $GLOBALS['sql_query'] .= "\n\n" . $sql_insert_data . ';'; } $GLOBALS['cfgRelation'] = PMA_getRelationsParam(); @@ -1062,7 +1044,7 @@ class PMA_Table $source_table, $target_table ); - $GLOBALS['sql_query'] .= "\n\n" . $sql_drop_query . ';'; + $GLOBALS['sql_query'] .= "\n\n" . $sql_drop_query . ';'; // end if ($move) } else { // we are copying @@ -1240,6 +1222,8 @@ class PMA_Table */ } } + +file_put_contents('/tmp/debug_table.txt', "-- GLOBALS[sql_query] (2) = \n" . $GLOBALS['sql_query'] . "\n", FILE_APPEND); return true; } diff --git a/libraries/sql-parser/src/Builder.php b/libraries/sql-parser/src/Builder.php deleted file mode 100644 index 5cb9d66fdb..0000000000 --- a/libraries/sql-parser/src/Builder.php +++ /dev/null @@ -1,87 +0,0 @@ -<?php - -/** - * This is one of the most important components, along with the lexer and the - * parser. - * - * @package SqlParser - */ -namespace SqlParser; - -use SqlParser\Exceptions\ParserException; - -/** - * Builds the string representation of a Statement. - * - * @category Parser - * @package SqlParser - * @author Dan Ungureanu <udan1107@gmail.com> - * @license http://opensource.org/licenses/GPL-2.0 GNU Public License - */ -class Builder -{ - - /** - * Statement to be build. - * - * @var Statement - */ - public $statement; - - /** - * Built query. - * - * @var string - */ - public $query; - - /** - * Constructor. - * - * @param Statement $statement - */ - public function __construct($statement = null) - { - $this->statement = $statement; - - if ($this->statement != null) { - $this->build(); - } - } - - /** - * Builds the statement. - * - * @return void - */ - public function build() - { - $statement = $this->statement; - - foreach ($statement::$CLAUSES as $clause) { - $name = $clause[0]; - $type = $clause[1]; - - if (empty(Parser::$KEYWORD_PARSERS[$name])) { - continue; - } - - $class = Parser::$KEYWORD_PARSERS[$name]['class']; - $field = Parser::$KEYWORD_PARSERS[$name]['field']; - - if (empty($statement->$field)) { - continue; - } - - if ($type & 2) { - $this->query .= $name . ' '; - } - - if ($type & 1) { - $this->query .= $class::build($statement->$field) . ' '; - } - } - - return $this->qeury; - } -} diff --git a/libraries/sql-parser/src/Context.php b/libraries/sql-parser/src/Context.php index 699d800b87..d42211f639 100644 --- a/libraries/sql-parser/src/Context.php +++ b/libraries/sql-parser/src/Context.php @@ -110,6 +110,14 @@ abstract class Context '(' => 16, ')' => 16, '.' => 16, ',' => 16, ); + /** + * The mode of the MySQL server that will be used in lexing, parsing and + * building the statements. + * + * @var int + */ + public static $MODE = 0; + /* * Server SQL Modes * https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html @@ -413,6 +421,44 @@ abstract class Context self::$loadedContext = $context; self::$KEYWORDS = $context::$KEYWORDS; } + + /** + * Sets the SQL mode. + * + * @param string $mode The list of modes. If empty, the mode is reset. + */ + public static function setMode($mode = '') + { + static::$MODE = 0; + if (empty($mode)) { + return; + } + $mode = explode(',', $mode); + foreach ($mode as $m) { + static::$MODE |= constant('static::' . $m); + } + } + + /** + * Escapes the symbol by adding surrounding backticks. + * + * @param array|string $str The string to be escaped. + * + * @return string + */ + public static function escape($str) + { + if (is_array($str)) { + foreach ($str as $key => $value) { + $str[$key] = static::escape($value); + } + return $str; + } + if (static::$MODE & Context::ANSI_QUOTES) { + return '"' . str_replace('"', '""', $str) . '"'; + } + return '`' . str_replace('`', '``', $str) . '`'; + } } // Initializng the default context. diff --git a/libraries/sql-parser/src/Contexts/ContextMySql50000.php b/libraries/sql-parser/src/Contexts/ContextMySql50000.php index 26afac1649..dc2759a30b 100644 --- a/libraries/sql-parser/src/Contexts/ContextMySql50000.php +++ b/libraries/sql-parser/src/Contexts/ContextMySql50000.php @@ -143,14 +143,14 @@ class ContextMySql50000 extends Context 'MINUTE_MICROSECOND' => 3, 'NO_WRITE_TO_BINLOG' => 3, 'SECOND_MICROSECOND' => 3, 'SQL_CALC_FOUND_ROWS' => 3, - 'NOT NULL' => 5, 'SET NULL' => 5, - 'NO ACTION' => 5, 'ON DELETE' => 5, 'ON UPDATE' => 5, - 'CHARACTER SET' => 5, 'IF NOT EXISTS' => 5, - 'DATA DIRECTORY' => 5, - 'DEFAULT COLLATE' => 5, 'INDEX DIRECTORY' => 5, - 'DEFAULT CHARACTER SET' => 5, - - 'GROUP BY' => 7, 'ORDER BY' => 7, + 'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 7, + 'IF EXISTS' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7, + 'OR REPLACE' => 7, + 'SQL SECURITY' => 7, + 'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7, + 'DATA DIRECTORY' => 7, + 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7, + 'DEFAULT CHARACTER SET' => 7, 'XML' => 9, 'ENUM' => 9, 'TEXT' => 9, @@ -168,18 +168,18 @@ class ContextMySql50000 extends Context 'CHARACTER' => 11, 'MEDIUMINT' => 11, 'VARBINARY' => 11, 'MEDIUMBLOB' => 11, 'MEDIUMTEXT' => 11, - 'BINARY VARYING' => 13, + 'BINARY VARYING' => 15, 'KEY' => 19, 'INDEX' => 19, 'UNIQUE' => 19, - 'INDEX KEY' => 21, - 'UNIQUE KEY' => 21, - 'FOREIGN KEY' => 21, 'PRIMARY KEY' => 21, 'SPATIAL KEY' => 21, - 'FULLTEXT KEY' => 21, 'UNIQUE INDEX' => 21, - 'SPATIAL INDEX' => 21, - 'FULLTEXT INDEX' => 21, + 'INDEX KEY' => 23, + 'UNIQUE KEY' => 23, + 'FOREIGN KEY' => 23, 'PRIMARY KEY' => 23, 'SPATIAL KEY' => 23, + 'FULLTEXT KEY' => 23, 'UNIQUE INDEX' => 23, + 'SPATIAL INDEX' => 23, + 'FULLTEXT INDEX' => 23, 'X' => 33, 'Y' => 33, 'LN' => 33, 'PI' => 33, @@ -258,7 +258,7 @@ class ContextMySql50000 extends Context 'LOCALTIMESTAMP' => 35, 'CURRENT_TIMESTAMP' => 35, - 'NOT IN' => 37, + 'NOT IN' => 39, 'DATE' => 41, 'TIME' => 41, 'YEAR' => 41, 'TIMESTAMP' => 41, diff --git a/libraries/sql-parser/src/Contexts/ContextMySql50100.php b/libraries/sql-parser/src/Contexts/ContextMySql50100.php index 23c619b797..82db6843ed 100644 --- a/libraries/sql-parser/src/Contexts/ContextMySql50100.php +++ b/libraries/sql-parser/src/Contexts/ContextMySql50100.php @@ -154,14 +154,14 @@ class ContextMySql50100 extends Context 'SQL_CALC_FOUND_ROWS' => 3, 'MASTER_SSL_VERIFY_SERVER_CERT' => 3, - 'NOT NULL' => 5, 'SET NULL' => 5, - 'NO ACTION' => 5, 'ON DELETE' => 5, 'ON UPDATE' => 5, - 'CHARACTER SET' => 5, 'IF NOT EXISTS' => 5, - 'DATA DIRECTORY' => 5, - 'DEFAULT COLLATE' => 5, 'INDEX DIRECTORY' => 5, - 'DEFAULT CHARACTER SET' => 5, - - 'GROUP BY' => 7, 'ORDER BY' => 7, + 'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 7, + 'IF EXISTS' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7, + 'OR REPLACE' => 7, + 'SQL SECURITY' => 7, + 'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7, + 'DATA DIRECTORY' => 7, + 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7, + 'DEFAULT CHARACTER SET' => 7, 'XML' => 9, 'ENUM' => 9, 'TEXT' => 9, @@ -179,18 +179,18 @@ class ContextMySql50100 extends Context 'CHARACTER' => 11, 'MEDIUMINT' => 11, 'VARBINARY' => 11, 'MEDIUMBLOB' => 11, 'MEDIUMTEXT' => 11, - 'BINARY VARYING' => 13, + 'BINARY VARYING' => 15, 'KEY' => 19, 'INDEX' => 19, 'UNIQUE' => 19, - 'INDEX KEY' => 21, - 'UNIQUE KEY' => 21, - 'FOREIGN KEY' => 21, 'PRIMARY KEY' => 21, 'SPATIAL KEY' => 21, - 'FULLTEXT KEY' => 21, 'UNIQUE INDEX' => 21, - 'SPATIAL INDEX' => 21, - 'FULLTEXT INDEX' => 21, + 'INDEX KEY' => 23, + 'UNIQUE KEY' => 23, + 'FOREIGN KEY' => 23, 'PRIMARY KEY' => 23, 'SPATIAL KEY' => 23, + 'FULLTEXT KEY' => 23, 'UNIQUE INDEX' => 23, + 'SPATIAL INDEX' => 23, + 'FULLTEXT INDEX' => 23, 'X' => 33, 'Y' => 33, 'LN' => 33, 'PI' => 33, @@ -280,7 +280,7 @@ class ContextMySql50100 extends Context 'LOCALTIMESTAMP' => 35, 'CURRENT_TIMESTAMP' => 35, - 'NOT IN' => 37, + 'NOT IN' => 39, 'DATE' => 41, 'TIME' => 41, 'YEAR' => 41, 'TIMESTAMP' => 41, diff --git a/libraries/sql-parser/src/Contexts/ContextMySql50500.php b/libraries/sql-parser/src/Contexts/ContextMySql50500.php index 03105abd1b..e338232eb0 100644 --- a/libraries/sql-parser/src/Contexts/ContextMySql50500.php +++ b/libraries/sql-parser/src/Contexts/ContextMySql50500.php @@ -159,14 +159,14 @@ class ContextMySql50500 extends Context 'SQL_CALC_FOUND_ROWS' => 3, 'MASTER_SSL_VERIFY_SERVER_CERT' => 3, - 'NOT NULL' => 5, 'SET NULL' => 5, - 'NO ACTION' => 5, 'ON DELETE' => 5, 'ON UPDATE' => 5, - 'CHARACTER SET' => 5, 'IF NOT EXISTS' => 5, - 'DATA DIRECTORY' => 5, - 'DEFAULT COLLATE' => 5, 'INDEX DIRECTORY' => 5, - 'DEFAULT CHARACTER SET' => 5, - - 'GROUP BY' => 7, 'ORDER BY' => 7, + 'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 7, + 'IF EXISTS' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7, + 'OR REPLACE' => 7, + 'SQL SECURITY' => 7, + 'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7, + 'DATA DIRECTORY' => 7, + 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7, + 'DEFAULT CHARACTER SET' => 7, 'XML' => 9, 'ENUM' => 9, 'TEXT' => 9, @@ -184,18 +184,18 @@ class ContextMySql50500 extends Context 'CHARACTER' => 11, 'MEDIUMINT' => 11, 'VARBINARY' => 11, 'MEDIUMBLOB' => 11, 'MEDIUMTEXT' => 11, - 'BINARY VARYING' => 13, + 'BINARY VARYING' => 15, 'KEY' => 19, 'INDEX' => 19, 'UNIQUE' => 19, - 'INDEX KEY' => 21, - 'UNIQUE KEY' => 21, - 'FOREIGN KEY' => 21, 'PRIMARY KEY' => 21, 'SPATIAL KEY' => 21, - 'FULLTEXT KEY' => 21, 'UNIQUE INDEX' => 21, - 'SPATIAL INDEX' => 21, - 'FULLTEXT INDEX' => 21, + 'INDEX KEY' => 23, + 'UNIQUE KEY' => 23, + 'FOREIGN KEY' => 23, 'PRIMARY KEY' => 23, 'SPATIAL KEY' => 23, + 'FULLTEXT KEY' => 23, 'UNIQUE INDEX' => 23, + 'SPATIAL INDEX' => 23, + 'FULLTEXT INDEX' => 23, 'X' => 33, 'Y' => 33, 'LN' => 33, 'PI' => 33, @@ -284,7 +284,7 @@ class ContextMySql50500 extends Context 'LOCALTIMESTAMP' => 35, 'CURRENT_TIMESTAMP' => 35, - 'NOT IN' => 37, + 'NOT IN' => 39, 'DATE' => 41, 'TIME' => 41, 'YEAR' => 41, 'TIMESTAMP' => 41, diff --git a/libraries/sql-parser/src/Contexts/ContextMySql50600.php b/libraries/sql-parser/src/Contexts/ContextMySql50600.php index 91978cbd82..780ea5208b 100644 --- a/libraries/sql-parser/src/Contexts/ContextMySql50600.php +++ b/libraries/sql-parser/src/Contexts/ContextMySql50600.php @@ -165,14 +165,14 @@ class ContextMySql50600 extends Context 'SQL_CALC_FOUND_ROWS' => 3, 'MASTER_SSL_VERIFY_SERVER_CERT' => 3, - 'NOT NULL' => 5, 'SET NULL' => 5, - 'NO ACTION' => 5, 'ON DELETE' => 5, 'ON UPDATE' => 5, - 'CHARACTER SET' => 5, 'IF NOT EXISTS' => 5, - 'DATA DIRECTORY' => 5, - 'DEFAULT COLLATE' => 5, 'INDEX DIRECTORY' => 5, - 'DEFAULT CHARACTER SET' => 5, - - 'GROUP BY' => 7, 'ORDER BY' => 7, + 'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 7, + 'IF EXISTS' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7, + 'OR REPLACE' => 7, + 'SQL SECURITY' => 7, + 'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7, + 'DATA DIRECTORY' => 7, + 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7, + 'DEFAULT CHARACTER SET' => 7, 'XML' => 9, 'ENUM' => 9, 'TEXT' => 9, @@ -190,18 +190,18 @@ class ContextMySql50600 extends Context 'CHARACTER' => 11, 'MEDIUMINT' => 11, 'VARBINARY' => 11, 'MEDIUMBLOB' => 11, 'MEDIUMTEXT' => 11, - 'BINARY VARYING' => 13, + 'BINARY VARYING' => 15, 'KEY' => 19, 'INDEX' => 19, 'UNIQUE' => 19, - 'INDEX KEY' => 21, - 'UNIQUE KEY' => 21, - 'FOREIGN KEY' => 21, 'PRIMARY KEY' => 21, 'SPATIAL KEY' => 21, - 'FULLTEXT KEY' => 21, 'UNIQUE INDEX' => 21, - 'SPATIAL INDEX' => 21, - 'FULLTEXT INDEX' => 21, + 'INDEX KEY' => 23, + 'UNIQUE KEY' => 23, + 'FOREIGN KEY' => 23, 'PRIMARY KEY' => 23, 'SPATIAL KEY' => 23, + 'FULLTEXT KEY' => 23, 'UNIQUE INDEX' => 23, + 'SPATIAL INDEX' => 23, + 'FULLTEXT INDEX' => 23, 'X' => 33, 'Y' => 33, 'LN' => 33, 'PI' => 33, @@ -315,7 +315,7 @@ class ContextMySql50600 extends Context 'LOCALTIMESTAMP' => 35, 'CURRENT_TIMESTAMP' => 35, - 'NOT IN' => 37, + 'NOT IN' => 39, 'DATE' => 41, 'TIME' => 41, 'YEAR' => 41, 'TIMESTAMP' => 41, diff --git a/libraries/sql-parser/src/Contexts/ContextMySql50700.php b/libraries/sql-parser/src/Contexts/ContextMySql50700.php index 083740996e..6a3cfd638b 100644 --- a/libraries/sql-parser/src/Contexts/ContextMySql50700.php +++ b/libraries/sql-parser/src/Contexts/ContextMySql50700.php @@ -173,14 +173,14 @@ class ContextMySql50700 extends Context 'SQL_CALC_FOUND_ROWS' => 3, 'MASTER_SSL_VERIFY_SERVER_CERT' => 3, - 'NOT NULL' => 5, 'SET NULL' => 5, - 'NO ACTION' => 5, 'ON DELETE' => 5, 'ON UPDATE' => 5, - 'CHARACTER SET' => 5, 'IF NOT EXISTS' => 5, - 'DATA DIRECTORY' => 5, - 'DEFAULT COLLATE' => 5, 'INDEX DIRECTORY' => 5, - 'DEFAULT CHARACTER SET' => 5, - - 'GROUP BY' => 7, 'ORDER BY' => 7, + 'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 7, + 'IF EXISTS' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7, + 'OR REPLACE' => 7, + 'SQL SECURITY' => 7, + 'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7, + 'DATA DIRECTORY' => 7, + 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7, + 'DEFAULT CHARACTER SET' => 7, 'XML' => 9, 'ENUM' => 9, 'TEXT' => 9, @@ -198,18 +198,18 @@ class ContextMySql50700 extends Context 'CHARACTER' => 11, 'MEDIUMINT' => 11, 'VARBINARY' => 11, 'MEDIUMBLOB' => 11, 'MEDIUMTEXT' => 11, - 'BINARY VARYING' => 13, + 'BINARY VARYING' => 15, 'KEY' => 19, 'INDEX' => 19, 'UNIQUE' => 19, - 'INDEX KEY' => 21, - 'UNIQUE KEY' => 21, - 'FOREIGN KEY' => 21, 'PRIMARY KEY' => 21, 'SPATIAL KEY' => 21, - 'FULLTEXT KEY' => 21, 'UNIQUE INDEX' => 21, - 'SPATIAL INDEX' => 21, - 'FULLTEXT INDEX' => 21, + 'INDEX KEY' => 23, + 'UNIQUE KEY' => 23, + 'FOREIGN KEY' => 23, 'PRIMARY KEY' => 23, 'SPATIAL KEY' => 23, + 'FULLTEXT KEY' => 23, 'UNIQUE INDEX' => 23, + 'SPATIAL INDEX' => 23, + 'FULLTEXT INDEX' => 23, 'X' => 33, 'Y' => 33, 'LN' => 33, 'PI' => 33, @@ -327,7 +327,7 @@ class ContextMySql50700 extends Context 'LOCALTIMESTAMP' => 35, 'CURRENT_TIMESTAMP' => 35, - 'NOT IN' => 37, + 'NOT IN' => 39, 'DATE' => 41, 'TIME' => 41, 'YEAR' => 41, 'TIMESTAMP' => 41, diff --git a/libraries/sql-parser/src/Fragments/AlterFragment.php b/libraries/sql-parser/src/Fragments/AlterFragment.php new file mode 100644 index 0000000000..54338f9dab --- /dev/null +++ b/libraries/sql-parser/src/Fragments/AlterFragment.php @@ -0,0 +1,201 @@ +<?php + +/** + * Parses a reference to a field. + * + * @package SqlParser + * @subpackage Fragments + */ +namespace SqlParser\Fragments; + +use SqlParser\Fragment; +use SqlParser\Parser; +use SqlParser\Token; +use SqlParser\TokensList; + +/** + * Parses a reference to a field. + * + * @category Fragments + * @package SqlParser + * @subpackage Fragments + * @author Dan Ungureanu <udan1107@gmail.com> + * @license http://opensource.org/licenses/GPL-2.0 GNU Public License + */ +class AlterFragment extends Fragment +{ + + /** + * All alter operations. + * + * @var array + */ + public static $OPTIONS = array( + 'ADD' => 3, + 'ALTER' => 3, + 'ANALYZE' => 3, + 'CHANGE' => 3, + 'CHECK' => 3, + 'COALESCE' => 3, + 'CONVERT' => 3, + 'DISABLE' => 3, + 'DISCARD' => 3, + 'DROP' => 3, + 'ENABLE' => 3, + 'IMPORT' => 3, + 'MODIFY' => 3, + 'OPTIMIZE' => 3, + 'ORDER' => 3, + 'PARTITION' => 3, + 'REBUILD' => 3, + 'REMOVE' => 3, + 'RENAME' => 3, + 'REORGANIZE' => 3, + 'REPAIR' => 3, + + 'COLUMN' => 4, + 'CONSTRAINT' => 4, + 'DEFAULT' => 4, + 'TO' => 4, + 'BY' => 4, + 'FOREIGN' => 4, + 'FULLTEXT' => 4, + 'KEY' => 4, + 'KEYS' => 4, + 'PARTITIONING' => 4, + 'PRIMARY KEY' => 4, + 'SPATIAL' => 4, + 'TABLESPACE' => 4, + 'INDEX' => 4, + + 'DEFAULT CHARACTER SET' => array(5, 'var'), + + 'COLLATE' => array(6, 'var'), + ); + + /** + * Options of this operation. + * + * @var OptionsFragment + */ + public $options; + + /** + * The altered field. + * + * @var FieldFragment + */ + public $field; + + /** + * Unparsed tokens. + * + * @var Token[] + */ + public $unknown = array(); + + /** + * @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 AlterFragment + */ + public static function parse(Parser $parser, TokensList $list, array $options = array()) + { + $ret = new AlterFragment(); + + /** + * Counts brackets. + * @var int + */ + $brackets = 0; + + /** + * The state of the parser. + * + * Below are the states of the parser. + * + * 0 ---------------------[ options ]---------------------> 1 + * + * 1 ----------------------[ field ]----------------------> 2 + * + * 2 -------------------------[ , ]-----------------------> 0 + * + * @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)) { + if ($state !== 2) { + // State 2 parses the unknown part which must include whitespaces as well. + continue; + } + } + + if ($state === 0) { + $ret->options = OptionsFragment::parse($parser, $list, static::$OPTIONS); + $state = 1; + } elseif ($state === 1) { + $ret->field = FieldFragment::parse( + $parser, + $list, + array( + 'noAlias' => true, + 'noBrackets' => true, + ) + ); + if ($ret->field === null) { + // No field was read. We go back one token so the next + // iteration will parse the same on, but in state 2. + --$list->idx; + } + $state = 2; + } elseif ($state === 2) { + if ($token->type === Token::TYPE_OPERATOR) { + if ($token->value === '(') { + ++$brackets; + } elseif ($token->value === ')') { + --$brackets; + } elseif ($token->value === ',') { + break; + } + } + $ret->unknown[] = $token; + } + } + + --$list->idx; + return $ret; + } + + /** + * @param AlterFragment $fragment The fragment to be built. + * + * @return string + */ + public static function build($fragment) + { + $ret = OptionsFragment::build($fragment->options) . ' '; + if (!empty($fragment->field)) { + $ret .= FieldFragment::build($fragment->field) . ' '; + } + foreach ($fragment->unknown as $token) { + $ret .= $token->token; + } + return $ret; + } +} diff --git a/libraries/sql-parser/src/Fragments/CreateDefFragment.php b/libraries/sql-parser/src/Fragments/CreateDefFragment.php deleted file mode 100644 index 933ea5ef5e..0000000000 --- a/libraries/sql-parser/src/Fragments/CreateDefFragment.php +++ /dev/null @@ -1,125 +0,0 @@ -<?php - -/** - * Parses the definition that follows the `CREATE` keyword. - * - * @package SqlParser - * @subpackage Fragments - */ -namespace SqlParser\Fragments; - -use SqlParser\Fragment; -use SqlParser\Parser; -use SqlParser\Token; -use SqlParser\TokensList; - -/** - * Parses the definition that follows the `CREATE` keyword. - * - * @category Fragments - * @package SqlParser - * @subpackage Fragments - * @author Dan Ungureanu <udan1107@gmail.com> - * @license http://opensource.org/licenses/GPL-2.0 GNU Public License - */ -class CreateDefFragment extends Fragment -{ - - /** - * All table options. - * - * @var array - */ - public static $TABLE_OPTIONS = array( - 'ENGINE' => array(1, 'var'), - 'AUTO_INCREMENT' => array(2, 'var'), - 'AVG_ROW_LENGTH' => array(3, 'var'), - 'DEFAULT CHARACTER SET' => array(4, 'var'), - 'CHARACTER SET' => array(4, 'var'), - 'CHECKSUM' => array(5, 'var'), - 'DEFAULT COLLATE' => array(5, 'var'), - 'COLLATE' => array(6, 'var'), - 'COMMENT' => array(7, 'var'), - 'CONNECTION' => array(8, 'var'), - 'DATA DIRECTORY' => array(9, 'var'), - 'DELAY_KEY_WRITE' => array(10, 'var'), - 'INDEX DIRECTORY' => array(11, 'var'), - 'INSERT_METHOD' => array(12, 'var'), - 'KEY_BLOCK_SIZE' => array(13, 'var'), - 'MAX_ROWS' => array(14, 'var'), - 'MIN_ROWS' => array(15, 'var'), - 'PACK_KEYS' => array(16, 'var'), - 'PASSWORD' => array(17, 'var'), - 'ROW_FORMAT' => array(18, 'var'), - 'TABLESPACE' => array(19, 'var'), - 'STORAGE' => array(20, 'var'), - 'UNION' => array(21, 'var'), - ); - - /** - * All function options. - * - * @var array - */ - public static $FUNC_OPTIONS = array( - 'COMMENT' => array(1, 'var'), - 'LANGUAGE SQL' => 2, - 'DETERMINISTIC' => 3, - 'NOT DETERMINISTIC' => 3, - 'CONSTAINS SQL' => 4, - 'NO SQL' => 4, - 'READS SQL DATA' => 4, - 'MODIFIES SQL DATA' => 4, - 'SQL SEQURITY DEFINER' => array(5, 'var'), - ); - - /** - * The name of the new table. - * - * @var string - */ - public $name; - - /** - * @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 CreateDefFragment - */ - public static function parse(Parser $parser, TokensList $list, array $options = array()) - { - $ret = new CreateDefFragment(); - - 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 (($token->type === Token::TYPE_OPERATOR) - && ($token->value === '(') - ) { - break; - } - - $ret->name .= $token->value; - } - - --$list->idx; - return $ret; - } -} diff --git a/libraries/sql-parser/src/Fragments/DataTypeFragment.php b/libraries/sql-parser/src/Fragments/DataTypeFragment.php index 0b9e1a81b2..6a44b78b48 100644 --- a/libraries/sql-parser/src/Fragments/DataTypeFragment.php +++ b/libraries/sql-parser/src/Fragments/DataTypeFragment.php @@ -35,7 +35,7 @@ class DataTypeFragment extends Fragment 'BINARY' => 1, 'CHARACTER SET' => array(2, 'var'), 'CHARSET' => array(2, 'var'), - 'COLLATE' => 3, + 'COLLATE' => array(3, 'var'), 'UNSIGNED' => 4, 'ZEROFILL' => 5, ); @@ -70,6 +70,21 @@ class DataTypeFragment extends Fragment public $options; /** + * Constructor. + * + * @param string $name The name of this data type. + * @param array $parameters The parameters (size or possible values). + * @param OptionsFragment $options The options of this data type. + */ + public function __construct($name = null, array $parameters = array(), + $options = null + ) { + $this->name = $name; + $this->parameters = $parameters; + $this->options = $options; + } + + /** * @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. @@ -113,10 +128,10 @@ class DataTypeFragment extends Fragment $state = 1; } elseif ($state === 1) { if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) { - $size = ArrayFragment::parse($parser, $list); + $parameters = ArrayFragment::parse($parser, $list); ++$list->idx; $ret->parameters = (($ret->name === 'ENUM') || ($ret->name === 'SET')) ? - $size->raw : $size->values; + $parameters->raw : $parameters->values; } $ret->options = OptionsFragment::parse($parser, $list, static::$DATA_TYPE_OPTIONS); ++$list->idx; @@ -132,4 +147,21 @@ class DataTypeFragment extends Fragment --$list->idx; return $ret; } + + /** + * @param DataTypeFragment $fragment The fragment to be built. + * + * @return string + */ + public static function build($fragment) + { + $tmp = ''; + if (!empty($fragment->parameters)) { + $tmp = '('. implode(', ', $fragment->parameters) . ')'; + } + return trim( + $fragment->name . ' ' . $tmp . ' ' + . OptionsFragment::build($fragment->options) + ); + } } diff --git a/libraries/sql-parser/src/Fragments/FieldDefFragment.php b/libraries/sql-parser/src/Fragments/FieldDefFragment.php index 6c5771e95a..9f43fcc2fe 100644 --- a/libraries/sql-parser/src/Fragments/FieldDefFragment.php +++ b/libraries/sql-parser/src/Fragments/FieldDefFragment.php @@ -92,6 +92,29 @@ class FieldDefFragment extends Fragment public $options; /** + * Constructor. + * + * @param string $name The name of the field. + * @param OptionsFragment $options The options of this field. + * @param DataTypeFragment|KeyFragment $type The data type of this field or the key. + * @param bool $isConstraint Whether this field is a constraint or not. + * @param ReferencesKeyword $references References. + */ + public function __construct($name = null, $options = null, $type = null, + $isConstraint = false, $references = null + ) { + $this->name = $name; + $this->options = $options; + if ($type instanceof DataTypeFragment) { + $this->type = $type; + } elseif ($type instanceof KeyFragment) { + $this->key = $type; + $this->isConstraint = $isConstraint; + $this->references = $references; + } + } + + /** * @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. @@ -200,4 +223,42 @@ class FieldDefFragment extends Fragment return $ret; } + + /** + * @param FieldDefFragment[] $fragment The fragment to be built. + * + * @return string + */ + public static function build($fragment) + { + $ret = array(); + foreach ($fragment as $f) { + $tmp = ''; + + if ($f->isConstraint) { + $tmp .= 'CONSTRAINT '; + } + + if (!empty($f->name)) { + $tmp .= Context::escape($f->name) . ' '; + } + + if (!empty($f->type)) { + $tmp .= DataTypeFragment::build($f->type) . ' '; + } + + if (!empty($f->key)) { + $tmp .= KeyFragment::build($f->key) . ' '; + } + + if (!empty($f->references)) { + $tmp .= 'REFERENCES ' . ReferencesKeyword::build($f->references) . ' '; + } + + $tmp .= OptionsFragment::build($f->options); + + $ret[] = trim($tmp); + } + return '(' . implode(', ', $ret) . ')'; + } } diff --git a/libraries/sql-parser/src/Fragments/FieldFragment.php b/libraries/sql-parser/src/Fragments/FieldFragment.php index edd0a2bbba..c33a7d7ea3 100644 --- a/libraries/sql-parser/src/Fragments/FieldFragment.php +++ b/libraries/sql-parser/src/Fragments/FieldFragment.php @@ -8,6 +8,7 @@ */ namespace SqlParser\Fragments; +use SqlParser\Context; use SqlParser\Fragment; use SqlParser\Parser; use SqlParser\Token; @@ -168,7 +169,7 @@ class FieldFragment extends Fragment if (($isExpr) && (!$alias)) { $ret->expr .= $token->token; } - if (($alias === 0) && (!$isExpr) && (!$period) && (!empty($ret->expr))) { + if (($alias === 0) && (empty($options['noAlias'])) && (!$isExpr) && (!$period) && (!empty($ret->expr))) { $alias = 1; } continue; @@ -194,14 +195,14 @@ class FieldFragment extends Fragment } if ($token->type === Token::TYPE_OPERATOR) { - if ((!empty($options['noBrackets'])) && - (($token->value === '(') || ($token->value === ')')) + if ((!empty($options['noBrackets'])) + && (($token->value === '(') || ($token->value === ')')) ) { break; } if ($token->value === '(') { ++$brackets; - // We don't check to see if `$prev` is `true` (open bracke + // We don't check to see if `$prev` is `true` (open bracket // was found before) because the brackets count is one (the // only bracket we found is this one). if (($brackets === 1) && (empty($ret->function)) && ($prev !== null) && ($prev !== true)) { @@ -246,13 +247,20 @@ class FieldFragment extends Fragment } $ret->database = $ret->table; $ret->table = $ret->column; + $ret->column = null; $period = true; } else { // We found the name of a column (or table if column // field should be skipped; used to parse table names). if (!empty($options['skipColumn'])) { + if (!empty($ret->table)) { + break; + } $ret->table = $token->value; } else { + if (!empty($ret->column)) { + break; + } $ret->column = $token->value; } $period = false; @@ -317,11 +325,11 @@ class FieldFragment extends Fragment if (!empty($fragment->column)) { $fields[] = $fragment->column; } - $ret = implode('.', $fields); + $ret = implode('.', Context::escape($fields)); } if (!empty($fragment->alias)) { - $ret .= ' AS ' . $fragment->alias; + $ret .= ' AS ' . Context::escape($fragment->alias); } return $ret; diff --git a/libraries/sql-parser/src/Fragments/KeyFragment.php b/libraries/sql-parser/src/Fragments/KeyFragment.php index d84c9f6565..1964cd2bc7 100644 --- a/libraries/sql-parser/src/Fragments/KeyFragment.php +++ b/libraries/sql-parser/src/Fragments/KeyFragment.php @@ -67,6 +67,16 @@ class KeyFragment extends Fragment */ public $options; + + public function __construct($name = null, array $columns = array(), + $type = null, $options = null + ) { + $this->name = $name; + $this->columns = $columns; + $this->type = $type; + $this->options = $options; + } + /** * @param Parser $parser The parser that serves as context. * @param TokensList $list The list of tokens that are being parsed. @@ -131,6 +141,21 @@ class KeyFragment extends Fragment --$list->idx; return $ret; + } + /** + * @param KeyFragment $fragment The fragment to be built. + * + * @return string + */ + public static function build($fragment) + { + $ret = $fragment->type . ' '; + if (!empty($fragment->name)) { + $ret .= Context::escape($fragment->name) . ' '; + } + $ret .= '(' . implode(', ', Context::escape($fragment->columns)) . ')'; + $ret .= OptionsFragment::build($fragment->options); + return trim($ret); } } diff --git a/libraries/sql-parser/src/Fragments/OptionsFragment.php b/libraries/sql-parser/src/Fragments/OptionsFragment.php index 00ac282c16..fa06b0d780 100644 --- a/libraries/sql-parser/src/Fragments/OptionsFragment.php +++ b/libraries/sql-parser/src/Fragments/OptionsFragment.php @@ -110,7 +110,12 @@ class OptionsFragment extends Fragment if (is_array($lastOption)) { if (empty($ret->options[$lastOptionId])) { - $ret->options[$lastOptionId] = array('name' => $token->value, 'value' => ''); + $ret->options[$lastOptionId] = array( + 'name' => $token->value, + 'equal' => $lastOption[1] === 'var=', + 'value' => '', + 'value_' => '', + ); } else { if ($token->value !== '=') { if ($token->value === '(') { @@ -118,7 +123,9 @@ class OptionsFragment extends Fragment } elseif ($token->value === ')') { --$brackets; } else { - $ret->options[$lastOptionId]['value'] .= $token->value; + // Raw and processed value. + $ret->options[$lastOptionId]['value'] .= $token->token; + $ret->options[$lastOptionId]['value_'] .= $token->value; } if ($brackets === 0) { $lastOption = null; @@ -144,10 +151,15 @@ class OptionsFragment extends Fragment */ public static function build($fragment) { + if ((empty($fragment)) || (!is_array($fragment->options))) { + return ''; + } $options = array(); foreach ($fragment->options as $option) { if (is_array($option)) { - $options[] = $option['name'] . '=' . $option['value']; + $options[] = $option['name'] + . (!empty($option['equal']) ? '=' : ' ') + . $option['value']; } else { $options[] = $option; } diff --git a/libraries/sql-parser/src/Fragments/ReferencesKeyword.php b/libraries/sql-parser/src/Fragments/ReferencesKeyword.php index 8de0233a71..ea10d20267 100644 --- a/libraries/sql-parser/src/Fragments/ReferencesKeyword.php +++ b/libraries/sql-parser/src/Fragments/ReferencesKeyword.php @@ -8,6 +8,7 @@ */ namespace SqlParser\Fragments; +use SqlParser\Context; use SqlParser\Fragment; use SqlParser\Parser; use SqlParser\Token; @@ -58,6 +59,20 @@ class ReferencesKeyword extends Fragment public $options; /** + * Constructor. + * + * @param string $table The name of the table referenced. + * @param array $columns The columns referenced. + * @param OptionsFragment $options The options. + */ + public function __construct($table = null, array $columns = array(), $options = null) + { + $this->table = $table; + $this->columns = $columns; + $this->options = $options; + } + + /** * @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. @@ -117,4 +132,18 @@ class ReferencesKeyword extends Fragment --$list->idx; return $ret; } + + /** + * @param ReferencesKeyword $fragment The fragment to be built. + * + * @return string + */ + public static function build($fragment) + { + return trim( + Context::escape($fragment->table) + . ' (' . implode(', ', Context::escape($fragment->columns)) . ') ' + . OptionsFragment::build($fragment->options) + ); + } } diff --git a/libraries/sql-parser/src/Lexer.php b/libraries/sql-parser/src/Lexer.php index 0d0862d908..01d41d88c7 100644 --- a/libraries/sql-parser/src/Lexer.php +++ b/libraries/sql-parser/src/Lexer.php @@ -290,7 +290,25 @@ class Lexer */ $iEnd = $this->last; + /** + * Whether last parsed character is a whitespace. + * @var bool + */ + $lastSpace = false; + for ($j = 1; $j < Context::KEYWORD_MAX_LENGTH && $this->last < $this->len; ++$j, ++$this->last) { + // Composed keywords shouldn't have more than one whitespace between + // keywords. + if (Context::isWhitespace($this->str[$this->last])) { + if ($lastSpace) { + --$j; // The size of the keyword didn't increase. + continue; + } else { + $lastSpace = true; + } + } else { + $lastSpace = false; + } $token .= $this->str[$this->last]; if (($this->last + 1 === $this->len) || (Context::isSeparator($this->str[$this->last + 1]))) { if (($flags = Context::isKeyword($token))) { diff --git a/libraries/sql-parser/src/Parser.php b/libraries/sql-parser/src/Parser.php index 11874fac61..ff55f5f7d1 100644 --- a/libraries/sql-parser/src/Parser.php +++ b/libraries/sql-parser/src/Parser.php @@ -337,10 +337,7 @@ class Parser * Processed statement. * @var Statement */ - $stmt = new $class(); - - // Parsing the actual statement. - $stmt->parse($this, $this->list); + $stmt = new $class($this, $this->list); // The first token that is a part of this token is the next token // unprocessed by the previous statement. diff --git a/libraries/sql-parser/src/Statement.php b/libraries/sql-parser/src/Statement.php index 41fba6e117..5ef7aec4a4 100644 --- a/libraries/sql-parser/src/Statement.php +++ b/libraries/sql-parser/src/Statement.php @@ -38,7 +38,7 @@ abstract class Statement * * @var array */ - public static $CLAUSES; + public static $CLAUSES = array(); /** * The options of this query. @@ -77,6 +77,71 @@ abstract class Statement } /** + * Builds the statement. + * + * @return string + */ + public function build() + { + /** + * Query to be returned. + * @var string + */ + $query = ''; + + foreach (static::$CLAUSES as $clause) { + + /** + * The name of the clause. + * @var string + */ + $name = $clause[0]; + + /** + * The type of the clause. + * @see self::$CLAUSES + * @var int + */ + $type = $clause[1]; + + // Checking if there is any parser (builder) for this clause. + if (empty(Parser::$KEYWORD_PARSERS[$name])) { + continue; + } + + /** + * The builder (parser) of this clause. + * @var string + */ + $class = Parser::$KEYWORD_PARSERS[$name]['class']; + + /** + * The name of the field that is used as source for the builder. + * Same field is used to store the result of parsing. + * @var string + */ + $field = Parser::$KEYWORD_PARSERS[$name]['field']; + + // The field is empty, there is nothing to be built. + if (empty($this->$field)) { + continue; + } + + // Checking if the name of the clause should be added. + if ($type & 2) { + $query .= $name . ' '; + } + + // Checking if the result of the builder should be added. + if ($type & 1) { + $query .= $class::build($this->$field) . ' '; + } + } + + return $query; + } + + /** * Parses the statements defined by the tokens list. * * @param Parser $parser The instance that requests parsing. diff --git a/libraries/sql-parser/src/Statements/AlterStatement.php b/libraries/sql-parser/src/Statements/AlterStatement.php index 81bca056f9..92f3744a7d 100644 --- a/libraries/sql-parser/src/Statements/AlterStatement.php +++ b/libraries/sql-parser/src/Statements/AlterStatement.php @@ -9,11 +9,12 @@ namespace SqlParser\Statements; use SqlParser\Parser; +use SqlParser\Statement; use SqlParser\Token; use SqlParser\TokensList; +use SqlParser\Fragments\AlterFragment; use SqlParser\Fragments\FieldFragment; use SqlParser\Fragments\OptionsFragment; -use SqlParser\Statements\NotImplementedStatement; /** * `ALTER` statement. @@ -24,7 +25,7 @@ use SqlParser\Statements\NotImplementedStatement; * @author Dan Ungureanu <udan1107@gmail.com> * @license http://opensource.org/licenses/GPL-2.0 GNU Public License */ -class AlterStatement extends NotImplementedStatement +class AlterStatement extends Statement { /** @@ -37,9 +38,9 @@ class AlterStatement extends NotImplementedStatement /** * Column affected by this statement. * - * @var FieldFragment + * @var AlterFragment[] */ - public $altered; + public $altered = array(); /** * Options of this statement. @@ -47,83 +48,91 @@ class AlterStatement extends NotImplementedStatement * @var array */ public static $OPTIONS = array( - 'ONLINE' => 1, 'OFFLINE' => 1, - 'IGNORE' => 2, - - 'TABLE' => 3, - - 'ADD' => 4, - 'ALTER' => 4, - 'ANALYZE' => 4, - 'CHANGE' => 4, - 'CHECK' => 4, - 'COALESCE' => 4, - 'CONVERT' => 4, - 'DISABLE' => 4, - 'DISCARD' => 4, - 'DROP' => 4, - 'ENABLE' => 4, - 'IMPORT' => 4, - 'MODIFY' => 4, - 'OPTIMIZE' => 4, - 'ORDER' => 4, - 'PARTITION' => 4, - 'REBUILD' => 4, - 'REMOVE' => 4, - 'RENAME' => 4, - 'REORGANIZE' => 4, - 'REPAIR' => 4, - - 'COLUMN' => 5, - 'CONSTRAINT' => 5, - 'DEFAULT' => 5, - 'TO' => 5, - 'BY' => 5, - 'FOREIGN' => 5, - 'FULLTEXT' => 5, - 'KEYS' => 5, - 'PARTITIONING' => 5, - 'PRIMARY KEY' => 5, - 'SPATIAL' => 5, - 'TABLESPACE' => 5, - 'INDEX' => 5, - - 'DEFAULT CHARACTER SET' => array(6, 'var'), - - 'COLLATE' => array(7, 'var'), ); /** - * Function called after the token was processed. - * - * Extracts the name of affected column. - * * @param Parser $parser The instance that requests parsing. * @param TokensList $list The list of tokens to be parsed. - * @param Token $token The token that is being parsed. * * @return void */ - public function after(Parser $parser, TokensList $list, Token $token) + public function parse(Parser $parser, TokensList $list) { - // Parsing operation. - ++$list->idx; - $this->options->merge( - OptionsFragment::parse( - $parser, - $list, - static::$OPTIONS - ) + ++$list->idx; // Skipping `ALTER`. + $this->options = OptionsFragment::parse( + $parser, + $list, + static::$OPTIONS ); - // Parsing affected field. - ++$list->idx; - $this->altered = FieldFragment::parse($parser, $list); + // Skipping `TABLE`. + $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'TABLE'); - // - parent::after($parser, $list, $token); + // Parsing affected table. + $this->table = FieldFragment::parse( + $parser, $list, array( + 'noAlias' => true, + 'noBrackets' => true, + ) + ); + ++$list->idx; // Skipping field. + + /** + * The state of the parser. + * + * Below are the states of the parser. + * + * 0 -----------------[ alter operation ]-----------------> 1 + * + * 1 -------------------------[ , ]-----------------------> 0 + * + * @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) { + $this->altered[] = AlterFragment::parse($parser, $list); + $state = 1; + } else if ($state === 1) { + if (($token->type === Token::TYPE_OPERATOR) && ($token->value === ',')) { + $state = 0; + } + } + } + } + + /** + * @return string + */ + public function build() + { + $tmp = array(); + foreach ($this->altered as $altered) { + $tmp[] = $altered::build($altered); + } + + return 'ALTER ' . OptionsFragment::build($this->options) + . ' TABLE ' . FieldFragment::build($this->table) + . ' ' . implode(', ', $tmp); } } diff --git a/libraries/sql-parser/src/Statements/CreateStatement.php b/libraries/sql-parser/src/Statements/CreateStatement.php index 7fd8ef0501..306bfebceb 100644 --- a/libraries/sql-parser/src/Statements/CreateStatement.php +++ b/libraries/sql-parser/src/Statements/CreateStatement.php @@ -38,30 +38,30 @@ class CreateStatement extends Statement */ public static $OPTIONS = array( - 'DATABASE' => 1, - 'EVENT' => 1, - 'FUNCTION' => 1, - 'INDEX' => 1, - 'PROCEDURE' => 1, - 'SERVER' => 1, - 'TABLE' => 1, - 'TABLESPACE' => 1, - 'TRIGGER' => 1, - 'USER' => 1, - 'VIEW' => 1, - // CREATE TABLE - 'TEMPORARY' => 2, - 'IF NOT EXISTS' => 3, + 'TEMPORARY' => 1, + 'IF NOT EXISTS' => 2, // CREATE FUNCTION / PROCEDURE and CREATE VIEW - 'DEFINER' => array(2, 'var'), + 'DEFINER' => array(1, 'var='), // CREATE VIEW - 'OR REPLACE' => array(3, 'var'), - 'ALGORITHM' => array(4, 'var'), - 'DEFINER' => array(5, 'var'), - 'SQL SECURITY' => array(6, 'var'), + 'OR REPLACE' => array(2, 'var='), + 'ALGORITHM' => array(3, 'var='), + 'DEFINER' => array(4, 'var='), + 'SQL SECURITY' => array(5, 'var'), + + 'DATABASE' => 6, + 'EVENT' => 6, + 'FUNCTION' => 6, + 'INDEX' => 6, + 'PROCEDURE' => 6, + 'SERVER' => 6, + 'TABLE' => 6, + 'TABLESPACE' => 6, + 'TRIGGER' => 6, + 'USER' => 6, + 'VIEW' => 6, ); /** @@ -70,8 +70,8 @@ class CreateStatement extends Statement * @var array */ public static $TABLE_OPTIONS = array( - 'ENGINE' => array(1, 'var'), - 'AUTO_INCREMENT' => array(2, 'var'), + 'ENGINE' => array(1, 'var='), + 'AUTO_INCREMENT' => array(2, 'var='), 'AVG_ROW_LENGTH' => array(3, 'var'), 'DEFAULT CHARACTER SET' => array(4, 'var'), 'CHARACTER SET' => array(4, 'var'), @@ -144,15 +144,6 @@ class CreateStatement extends Statement public $fields; /** - * The `SELECT` statement that defines this view. - * - * Used by `CREATE VIEW`. - * - * @var SelectStatement - */ - public $select; - - /** * The return data type of this routine. * * Used by `CREATE FUNCTION`. @@ -170,15 +161,36 @@ class CreateStatement extends Statement */ public $parameters; - /** - * The body of this function or procedure. + * The body of this function or procedure. For views, it is the select + * statement that gets the * - * Used by `CREATE FUNCTION` and `CREATE PROCEDURE`. + * Used by `CREATE FUNCTION`, `CREATE PROCEDURE` and `CREATE VIEW`. * * @var Token[] */ - public $body; + public $body = array(); + + /** + * @return string + */ + public function build() + { + $tmp = ''; + if ($this->options->has('TABLE')) { + $tmp = FieldDefFragment::build($this->fields); + } elseif ($this->options->has('VIEW')) { + if (!empty($this->fields)) { + $tmp .= ArrayFragment::build($this->fields) . ' '; + } + $tmp .= 'AS ' . TokensList::build($this->body); + } + return 'CREATE ' + . OptionsFragment::build($this->options) . ' ' + . FieldFragment::build($this->name) . ' ' + . $tmp . ' ' + . OptionsFragment::build($this->entityOptions); + } /** * @param Parser $parser The instance that requests parsing. @@ -258,11 +270,17 @@ class CreateStatement extends Statement --$list->idx; // getNext() also goes forward one field. $this->fields = ArrayFragment::parse($parser, $list); ++$list->idx; // Skipping last token from the array. - $token = $list->getNext(); + $list->getNext(); } - // Parsing the 'SELECT' statement. - $this->select = new SelectStatement($parser, $list); + // Parsing the `AS` keyword. + for (; $list->idx < $list->count; ++$list->idx) { + $token = $list->tokens[$list->idx]; + if ($token->type === Token::TYPE_DELIMITER) { + break; + } + $this->body[] = $token; + } } } } diff --git a/libraries/sql-parser/src/Statements/DropStatement.php b/libraries/sql-parser/src/Statements/DropStatement.php index babf18ac58..f0cd1ced87 100644 --- a/libraries/sql-parser/src/Statements/DropStatement.php +++ b/libraries/sql-parser/src/Statements/DropStatement.php @@ -46,6 +46,21 @@ class DropStatement extends Statement ); /** + * The clauses of this statement, in order. + * + * @see Statement::$CLAUSES + * + * @var array + */ + public static $CLAUSES = array( + 'DROP' => array('DROP', 2), + // Used for options. + '_OPTIONS' => array('_OPTIONS', 1), + // Used for select expressions. + 'DROP_' => array('DROP', 1), + ); + + /** * Dropped elements. * * @var FieldFragment[] diff --git a/libraries/sql-parser/src/Statements/NotImplementedStatement.php b/libraries/sql-parser/src/Statements/NotImplementedStatement.php index b96c5583fd..f5f8b8f0a1 100644 --- a/libraries/sql-parser/src/Statements/NotImplementedStatement.php +++ b/libraries/sql-parser/src/Statements/NotImplementedStatement.php @@ -28,19 +28,40 @@ class NotImplementedStatement extends Statement { /** - * Function called after the token was processed. - * - * Jump to the end of the delimiter. - * + * The part of the statement that can't be parsed. + * @var Token[] + */ + public $unknown = array(); + + /** + * @return string + */ + public function build() + { + // Building the parsed part of the query (if any). + $query = parent::build() . ' '; + + // Rebuilding the unknown part from tokens. + foreach ($this->unknown as $token) { + $query .= $token->token; + } + + return $query; + } + + /** * @param Parser $parser The instance that requests parsing. * @param TokensList $list The list of tokens to be parsed. - * @param Token $token The token that is being parsed. * * @return void */ - public function after(Parser $parser, TokensList $list, Token $token) + public function parse(Parser $parser, TokensList $list) { - $list->getNextOfType(Token::TYPE_DELIMITER); - --$list->idx; + for (; $list->idx < $list->count; ++$list->idx) { + if ($list->tokens[$list->idx]->type === Token::TYPE_DELIMITER) { + break; + } + $this->unknown[] = $list->tokens[$list->idx]; + } } } diff --git a/libraries/sql-parser/src/Statements/SelectStatement.php b/libraries/sql-parser/src/Statements/SelectStatement.php index 84ec5a29f3..e376b47a0a 100644 --- a/libraries/sql-parser/src/Statements/SelectStatement.php +++ b/libraries/sql-parser/src/Statements/SelectStatement.php @@ -57,7 +57,7 @@ class SelectStatement extends Statement 'DISTINCT' => 1, 'DISTINCTROW' => 1, 'HIGH_PRIORITY' => 2, - 'MAX_STATEMENT_TIME' => array(3, 'var'), + 'MAX_STATEMENT_TIME' => array(3, 'var='), 'STRAIGHT_JOIN' => 4, 'SQL_SMALL_RESULT' => 5, 'SQL_BIG_RESULT' => 6, diff --git a/libraries/sql-parser/src/TokensList.php b/libraries/sql-parser/src/TokensList.php index 52a464e624..229bebcec4 100644 --- a/libraries/sql-parser/src/TokensList.php +++ b/libraries/sql-parser/src/TokensList.php @@ -40,6 +40,42 @@ class TokensList implements \ArrayAccess public $idx = 0; /** + * Constructor. + * + * @param array $tokens The initial array of tokens. + */ + public function __construct(array $tokens = array(), $count = -1) + { + if (!empty($tokens)) { + $this->tokens = $tokens; + if ($count === -1) { + $this->count = count($tokens); + } + } + } + + /** + * Builds an array of tokens by merging their raw value. + * + * @param array $tokens + * + * @return string + */ + public static function build($list) + { + if ($list instanceof TokensList) { + $list = $list->tokens; + } + $ret = ''; + if (is_array($list)) { + foreach ($list as $tok) { + $ret .= $tok->token; + } + } + return $ret; + } + + /** * Adds a new token. * * @param Token $token Token to be added in list. @@ -61,7 +97,8 @@ class TokensList implements \ArrayAccess { for (; $this->idx < $this->count; ++$this->idx) { if (($this->tokens[$this->idx]->type !== Token::TYPE_WHITESPACE) - && ($this->tokens[$this->idx]->type !== Token::TYPE_COMMENT)) { + && ($this->tokens[$this->idx]->type !== Token::TYPE_COMMENT) + ) { return $this->tokens[$this->idx++]; } } diff --git a/libraries/sql-parser/src/Utils/Query.php b/libraries/sql-parser/src/Utils/Query.php index 11a08d0212..f74162067f 100644 --- a/libraries/sql-parser/src/Utils/Query.php +++ b/libraries/sql-parser/src/Utils/Query.php @@ -531,7 +531,7 @@ class Query if ($onlyType) { return static::getClause($statement, $list, $old, -1, false) . ' ' . - $new . ' ' . static::getCLause($statement, $list, $old, 0) . ' ' . + $new . ' ' . static::getClause($statement, $list, $old, 0) . ' ' . static::getClause($statement, $list, $old, 1, false); } diff --git a/libraries/sql-parser/src/Utils/Table.php b/libraries/sql-parser/src/Utils/Table.php index 86ccbfb4d1..ba7f82fd80 100644 --- a/libraries/sql-parser/src/Utils/Table.php +++ b/libraries/sql-parser/src/Utils/Table.php @@ -36,7 +36,10 @@ class Table */ public static function getForeignKeys($statement) { - if ((empty($statement->fields)) || (!$statement->options->has('TABLE'))) { + if ((empty($statement->fields)) + || (!is_array($statement->fields)) + || (!$statement->options->has('TABLE')) + ) { return array(); } @@ -85,7 +88,10 @@ class Table */ public static function getFields($statement) { - if ((empty($statement->fields)) || (!$statement->options->has('TABLE'))) { + if ((empty($statement->fields)) + || (!is_array($statement->fields)) + || (!$statement->options->has('TABLE')) + ) { return array(); } diff --git a/libraries/sql.lib.php b/libraries/sql.lib.php index 71704d6da6..69dc46c1a9 100644 --- a/libraries/sql.lib.php +++ b/libraries/sql.lib.php @@ -1311,9 +1311,13 @@ function PMA_deleteTransformationInfo($db, $table, $analyzed_sql_results) include_once 'libraries/transformations.lib.php'; $statement = $analyzed_sql_results['statement']; if ($statement instanceof SqlParser\Statements\AlterStatement) { - if ($statement->options->has('DROP')) { - if (!empty($statement->altered->column)) { - PMA_clearTransformations($db, $table, $statement->altered->column); + if ($statement->altered[0]->options->has('DROP')) { + if (!empty($statement->altered[0]->field->column)) { + PMA_clearTransformations( + $db, + $table, + $statement->altered[0]->field->column + ); } } } elseif ($statement instanceof SqlParser\Statements\DropStatement) { diff --git a/test/classes/PMA_DisplayResults_test.php b/test/classes/PMA_DisplayResults_test.php index 3485a80bf1..db8d10dcb5 100644 --- a/test/classes/PMA_DisplayResults_test.php +++ b/test/classes/PMA_DisplayResults_test.php @@ -101,9 +101,7 @@ class PMA_DisplayResults_Test extends PHPUnit_Framework_TestCase array( array( 'statement' => $parser->statements[0], - 'queryflags' => array( - 'select_from' => true, - ), + 'select_from' => true, ), ) ) |