diff options
author | Dan Ungureanu <udan1107@gmail.com> | 2015-07-03 21:18:10 +0300 |
---|---|---|
committer | Dan Ungureanu <udan1107@gmail.com> | 2015-07-04 23:41:52 +0300 |
commit | b3eff80030f9bd6d90e65360eb89e18a1be298b2 (patch) | |
tree | db420fef27f24856f4cfaeee008104fdfcb77942 /src | |
parent | 4dabcc2ae266c022e44294bfbe3344b05e66e266 (diff) | |
download | sql-parser-b3eff80030f9bd6d90e65360eb89e18a1be298b2.zip sql-parser-b3eff80030f9bd6d90e65360eb89e18a1be298b2.tar.gz sql-parser-b3eff80030f9bd6d90e65360eb89e18a1be298b2.tar.bz2 |
The context depends on the SQL mode.
Implemented a few more builders.
Improved some fragments and statement types.
Fixed the noAlias option in FieldFragment.
Reordered CREATE statements's options.
Updated contexts definitions.
Fixed typos. Improved tests.
Diffstat (limited to 'src')
-rw-r--r-- | src/Context.php | 46 | ||||
-rw-r--r-- | src/Contexts/ContextMySql50000.php | 32 | ||||
-rw-r--r-- | src/Contexts/ContextMySql50100.php | 32 | ||||
-rw-r--r-- | src/Contexts/ContextMySql50500.php | 32 | ||||
-rw-r--r-- | src/Contexts/ContextMySql50600.php | 32 | ||||
-rw-r--r-- | src/Contexts/ContextMySql50700.php | 32 | ||||
-rw-r--r-- | src/Fragments/AlterFragment.php | 201 | ||||
-rw-r--r-- | src/Fragments/DataTypeFragment.php | 38 | ||||
-rw-r--r-- | src/Fragments/FieldDefFragment.php | 61 | ||||
-rw-r--r-- | src/Fragments/FieldFragment.php | 20 | ||||
-rw-r--r-- | src/Fragments/KeyFragment.php | 25 | ||||
-rw-r--r-- | src/Fragments/OptionsFragment.php | 18 | ||||
-rw-r--r-- | src/Fragments/ReferencesKeyword.php | 29 | ||||
-rw-r--r-- | src/Lexer.php | 18 | ||||
-rw-r--r-- | src/Parser.php | 5 | ||||
-rw-r--r-- | src/Statement.php | 4 | ||||
-rw-r--r-- | src/Statements/AlterStatement.php | 143 | ||||
-rw-r--r-- | src/Statements/CreateStatement.php | 92 | ||||
-rw-r--r-- | src/Statements/DropStatement.php | 15 | ||||
-rw-r--r-- | src/Statements/NotImplementedStatement.php | 37 | ||||
-rw-r--r-- | src/Statements/SelectStatement.php | 2 | ||||
-rw-r--r-- | src/TokensList.php | 39 | ||||
-rw-r--r-- | src/Utils/Query.php | 2 | ||||
-rw-r--r-- | src/Utils/Table.php | 10 |
24 files changed, 750 insertions, 215 deletions
diff --git a/src/Context.php b/src/Context.php index 699d800..d42211f 100644 --- a/src/Context.php +++ b/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/src/Contexts/ContextMySql50000.php b/src/Contexts/ContextMySql50000.php index 26afac1..dc2759a 100644 --- a/src/Contexts/ContextMySql50000.php +++ b/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/src/Contexts/ContextMySql50100.php b/src/Contexts/ContextMySql50100.php index 23c619b..82db684 100644 --- a/src/Contexts/ContextMySql50100.php +++ b/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/src/Contexts/ContextMySql50500.php b/src/Contexts/ContextMySql50500.php index 03105ab..e338232 100644 --- a/src/Contexts/ContextMySql50500.php +++ b/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/src/Contexts/ContextMySql50600.php b/src/Contexts/ContextMySql50600.php index 91978cb..780ea52 100644 --- a/src/Contexts/ContextMySql50600.php +++ b/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/src/Contexts/ContextMySql50700.php b/src/Contexts/ContextMySql50700.php index 0837409..6a3cfd6 100644 --- a/src/Contexts/ContextMySql50700.php +++ b/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/src/Fragments/AlterFragment.php b/src/Fragments/AlterFragment.php new file mode 100644 index 0000000..54338f9 --- /dev/null +++ b/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/src/Fragments/DataTypeFragment.php b/src/Fragments/DataTypeFragment.php index 0b9e1a8..6a44b78 100644 --- a/src/Fragments/DataTypeFragment.php +++ b/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/src/Fragments/FieldDefFragment.php b/src/Fragments/FieldDefFragment.php index 6c5771e..9f43fcc 100644 --- a/src/Fragments/FieldDefFragment.php +++ b/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/src/Fragments/FieldFragment.php b/src/Fragments/FieldFragment.php index edd0a2b..c33a7d7 100644 --- a/src/Fragments/FieldFragment.php +++ b/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/src/Fragments/KeyFragment.php b/src/Fragments/KeyFragment.php index d84c9f6..1964cd2 100644 --- a/src/Fragments/KeyFragment.php +++ b/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/src/Fragments/OptionsFragment.php b/src/Fragments/OptionsFragment.php index 00ac282..fa06b0d 100644 --- a/src/Fragments/OptionsFragment.php +++ b/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/src/Fragments/ReferencesKeyword.php b/src/Fragments/ReferencesKeyword.php index 8de0233..ea10d20 100644 --- a/src/Fragments/ReferencesKeyword.php +++ b/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/src/Lexer.php b/src/Lexer.php index 0d0862d..01d41d8 100644 --- a/src/Lexer.php +++ b/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/src/Parser.php b/src/Parser.php index 11874fa..ff55f5f 100644 --- a/src/Parser.php +++ b/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/src/Statement.php b/src/Statement.php index 1591d66..5ef7aec 100644 --- a/src/Statement.php +++ b/src/Statement.php @@ -38,7 +38,7 @@ abstract class Statement * * @var array */ - public static $CLAUSES; + public static $CLAUSES = array(); /** * The options of this query. @@ -79,7 +79,7 @@ abstract class Statement /** * Builds the statement. * - * @return void + * @return string */ public function build() { diff --git a/src/Statements/AlterStatement.php b/src/Statements/AlterStatement.php index 81bca05..92f3744 100644 --- a/src/Statements/AlterStatement.php +++ b/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/src/Statements/CreateStatement.php b/src/Statements/CreateStatement.php index 7fd8ef0..306bfeb 100644 --- a/src/Statements/CreateStatement.php +++ b/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/src/Statements/DropStatement.php b/src/Statements/DropStatement.php index babf18a..f0cd1ce 100644 --- a/src/Statements/DropStatement.php +++ b/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/src/Statements/NotImplementedStatement.php b/src/Statements/NotImplementedStatement.php index b96c558..f5f8b8f 100644 --- a/src/Statements/NotImplementedStatement.php +++ b/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/src/Statements/SelectStatement.php b/src/Statements/SelectStatement.php index 84ec5a2..e376b47 100644 --- a/src/Statements/SelectStatement.php +++ b/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/src/TokensList.php b/src/TokensList.php index 52a464e..229bebc 100644 --- a/src/TokensList.php +++ b/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/src/Utils/Query.php b/src/Utils/Query.php index 11a08d0..f741620 100644 --- a/src/Utils/Query.php +++ b/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/src/Utils/Table.php b/src/Utils/Table.php index 86ccbfb..ba7f82f 100644 --- a/src/Utils/Table.php +++ b/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(); } |