diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Lexer.php | 14 | ||||
-rw-r--r-- | src/Parser.php | 2 | ||||
-rw-r--r-- | src/Statement.php | 4 | ||||
-rw-r--r-- | src/Statements/SelectStatement.php | 14 | ||||
-rw-r--r-- | src/Utils/Query.php | 320 | ||||
-rw-r--r-- | src/Utils/Routine.php | 4 |
6 files changed, 330 insertions, 28 deletions
diff --git a/src/Lexer.php b/src/Lexer.php index d681425..b91db8b 100644 --- a/src/Lexer.php +++ b/src/Lexer.php @@ -100,7 +100,7 @@ class Lexer * * @var TokensList */ - public $tokens; + public $list; /** * Statements delimiter. @@ -164,7 +164,7 @@ class Lexer // context and compare again with `false`. // Another example is `parseComment`. - $tokens = new TokensList(); + $list = new TokensList(); $lastToken = null; for ($this->last = 0, $lastIdx = 0; $this->last < $this->len; $lastIdx = ++$this->last) { @@ -203,7 +203,7 @@ class Lexer } $token->position = $lastIdx; - $tokens->tokens[$tokens->count++] = $token; + $list->tokens[$list->count++] = $token; // Handling delimiters. if (($token->type === Token::TYPE_NONE) && ($token->value === 'DELIMITER')) { @@ -217,7 +217,7 @@ class Lexer $pos = ++$this->last; if (($token = $this->parseWhitespace()) !== null) { $token->position = $pos; - $tokens->tokens[$tokens->count++] = $token; + $list->tokens[$list->count++] = $token; } // Preparing the token that holds the new delimiter. @@ -238,17 +238,17 @@ class Lexer $this->delimiterLen = strlen($this->delimiter); $token = new Token($this->delimiter, Token::TYPE_DELIMITER); $token->position = $pos; - $tokens->tokens[$tokens->count++] = $token; + $list->tokens[$list->count++] = $token; } $lastToken = $token; } // Adding a final delimite at the end to mark the ending. - $tokens->tokens[$tokens->count++] = new Token(null, Token::TYPE_DELIMITER); + $list->tokens[$list->count++] = new Token(null, Token::TYPE_DELIMITER); // Saving the tokens list. - $this->tokens = $tokens; + $this->list = $list; } /** diff --git a/src/Parser.php b/src/Parser.php index a2f7ef7..0a876b7 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -225,7 +225,7 @@ class Parser { if ((is_string($list)) || ($list instanceof UtfString)) { $lexer = new Lexer($list, $strict); - $this->list = $lexer->tokens; + $this->list = $lexer->list; } elseif ($list instanceof TokensList) { $this->list = $list; } diff --git a/src/Statement.php b/src/Statement.php index a813fff..74b2cc3 100644 --- a/src/Statement.php +++ b/src/Statement.php @@ -59,6 +59,8 @@ abstract class Statement */ public function parse(Parser $parser, TokensList $list) { + $this->first = $list->idx; + /** * Whether options were parsed or not. * For statements that do not have any options this is set to `true` by @@ -134,7 +136,7 @@ abstract class Statement $this->after($parser, $list, $token); } - $this->last = --$list->idx; // Go back to last used token. + $this->last = $list->idx--; // Go back to last used token. } /** diff --git a/src/Statements/SelectStatement.php b/src/Statements/SelectStatement.php index 8cdee82..0c4c354 100644 --- a/src/Statements/SelectStatement.php +++ b/src/Statements/SelectStatement.php @@ -68,6 +68,20 @@ class SelectStatement extends Statement ); /** + * The sections of this statement, in order. + * + * Used by the query builder to arrange the clauses. + * + * @var array + */ + public static $SECTIONS = array( + 'SELECT' => 0, '%OPTIONS' => 1,'FROM' => 2, 'PARTITION' => 3, + 'WHERE' => 4, 'GROUP BY' => 5, 'HAVING' => 6, 'ORDER BY' => 7, + 'LIMIT' => 8, 'PROCEDURE' => 9, 'INTO' => 10, 'UNION' => 11, + 'JOIN' => 12, '%OPTIONS' => 13 + ); + + /** * Expressions that are being selected by this statement. * * @var FieldFragment[] diff --git a/src/Utils/Query.php b/src/Utils/Query.php index b1a95b1..8cb9dfd 100644 --- a/src/Utils/Query.php +++ b/src/Utils/Query.php @@ -10,6 +10,7 @@ namespace SqlParser\Utils; use SqlParser\Parser; use SqlParser\Statement; +use SqlParser\Token; use SqlParser\Statements\AlterStatement; use SqlParser\Statements\AnalyzeStatement; use SqlParser\Statements\CallStatement; @@ -61,49 +62,193 @@ class Query $flags = array(); if ($all) { $flags = array( + + /** + * select ... DISTINCT ... + */ 'distinct' => false, + + /** + * drop ... DATABASE ... + */ 'drop_database' => false, + + /** + * ... GROUP BY ... + */ + 'group' => false, + + /** + * ... HAVING ... + */ + 'having' => false, + + /** + * INSERT ... + * or + * REPLACE ... + * or + * DELETE ... + */ 'is_affected' => false, + + /** + * select ... PROCEDURE ANALYSE( ... ) ... + */ 'is_analyse' => false, + + /** + * select COUNT( ... ) ... + */ 'is_count' => false, - 'is_delete' => false, - 'is_explain' => false, + + /** + * DELETE ... + */ + 'is_delete' => false, // @deprecated; use `querytype` + + /** + * EXPLAIN ... + */ + 'is_explain' => false, // @deprecated; use `querytype` + + /** + * select ... INTO OUTFILE ... + */ 'is_export' => false, + + /** + * select FUNC( ... ) ... + */ 'is_func' => false, + + /** + * select ... GROUP BY ... + * or + * select ... HAVING ... + */ 'is_group' => false, + + /** + * INSERT ... + * or + * REPLACE ... + * or + * TODO: LOAD DATA ... + */ 'is_insert' => false, + + /** + * ANALYZE ... + * or + * CHECK ... + * or + * CHECKSUM ... + * or + * OPTIMIZE ... + * or + * REPAIR ... + */ 'is_maint' => false, + + /** + * CALL ... + */ 'is_procedure' => false, - 'is_replace' => false, - 'is_select' => false, - 'is_show' => false, + + /** + * REPLACE ... + */ + 'is_replace' => false, // @deprecated; use `querytype` + + /** + * SELECT ... + */ + 'is_select' => false, // @deprecated; use `querytype` + + /** + * SHOW ... + */ + 'is_show' => false, // @deprecated; use `querytype` + + /** + * Contains a subquery. + */ 'is_subquery' => false, + + /** + * ... JOIN ... + */ 'join' => false, + + /** + * ... LIMIT ... + */ + 'limit' => false, + + /** + * TODO + */ 'offset' => false, + + /** + * ... ORDER ... + */ + 'order' => false, + + /** + * The type of the query (which is usually the first keyword of + * the statement). + */ + 'querytype' => false, + + /** + * Whether a page reload is required. + */ 'reload' => false, + + /** + * SELECT ... FROM ... + */ 'select_from' => false, + + /** + * ... UNION ... + */ 'union' => false ); } - // TODO: 'union', 'join', 'offset' - if (($statement instanceof AlterStatement) - || ($statement instanceof CreateStatement) - ) { + if ($statement instanceof AlterStatement) { + $flags['querytype'] = 'ALTER'; $flags['reload'] = true; - } else if (($statement instanceof AnalyzeStatement) - || ($statement instanceof CheckStatement) - || ($statement instanceof ChecksumStatement) - || ($statement instanceof OptimizeStatement) - || ($statement instanceof RepairStatement) - ) { + } else if ($statement instanceof CreateStatement) { + $flags['querytype'] = 'CREATE'; + $flags['reload'] = true; + } else if ($statement instanceof AnalyzeStatement) { + $flags['querytype'] = 'ANALYZE'; + $flags['is_maint'] = true; + } else if ($statement instanceof CheckStatement) { + $flags['querytype'] = 'CHECK'; + $flags['is_maint'] = true; + } else if ($statement instanceof ChecksumStatement) { + $flags['querytype'] = 'CHECKSUM'; + $flags['is_maint'] = true; + } else if ($statement instanceof OptimizeStatement) { + $flags['querytype'] = 'OPTIMIZE'; + $flags['is_maint'] = true; + } else if ($statement instanceof RepairStatement) { + $flags['querytype'] = 'REPAIR'; $flags['is_maint'] = true; } else if ($statement instanceof CallStatement) { + $flags['querytype'] = 'CALL'; $flags['is_procedure'] = true; } else if ($statement instanceof DeleteStatement) { + $flags['querytype'] = 'DELETE'; $flags['is_delete'] = true; $flags['is_affected'] = true; } else if ($statement instanceof DropStatement) { + $flags['querytype'] = 'DROP'; $flags['reload'] = true; if (($statement->options->has('DATABASE') @@ -112,14 +257,19 @@ class Query $flags['drop_database'] = true; } } else if ($statement instanceof ExplainStatement) { + $flags['querytype'] = 'EXPLAIN'; $flags['is_explain'] = true; } else if ($statement instanceof InsertStatement) { + $flags['querytype'] = 'INSERT'; $flags['is_affected'] = true; $flags['is_insert'] = true; } else if ($statement instanceof ReplaceStatement) { + $flags['querytype'] = 'REPLACE'; $flags['is_affected'] = true; $flags['is_replace'] = true; + $flags['is_insert'] = true; } else if ($statement instanceof SelectStatement) { + $flags['querytype'] = 'SELECT'; $flags['is_select'] = true; if (!empty($statement->from)) { @@ -158,12 +308,43 @@ class Query ) { $flags['is_analyse'] = true; } + + if (!empty($statement->group)) { + $flags['group'] = true; + } + + if (!empty($statement->having)) { + $flags['having'] = true; + } + + if (!empty($statement->union)) { + $flags['union'] = true; + } + + if (!empty($statement->join)) { + $flags['join'] = true; + } + } else if ($statement instanceof ShowStatement) { + $flags['querytype'] = 'SHOW'; $flags['is_show'] = true; } else if ($statement instanceof UpdateStatement) { + $flags['querytype'] = 'UPDATE'; $flags['is_affected'] = true; } + if (($statement instanceof SelectStatement) + || ($statement instanceof UpdateStatement) + || ($statement instanceof DeleteStatement) + ) { + if (!empty($statement->limit)) { + $flags['limit'] = true; + } + if (!empty($statement->order)) { + $flags['order'] = true; + } + } + return $flags; } @@ -190,13 +371,16 @@ class Query $ret['statement'] = $statement; if ($statement instanceof SelectStatement) { - $ret['tables'] = array(); + $ret['select_tables'] = array(); + $ret['select_expr'] = array(); foreach ($statement->expr as $expr) { if (!empty($expr->table)) { - $ret['tables'][] = array( + $ret['select_tables'][] = array( $expr->table, !empty($expr->database) ? $expr->database : null ); + } else { + $ret['select_expr'][] = $expr->expr; } } } @@ -204,4 +388,106 @@ class Query return $ret; } + public static function getClauseType($clause) + { + $type = ''; + for ($i = 0, $len = strlen($clause); $i < $len; ++$i) { + if ((empty($type)) && (ctype_space($type))) { + // Skipping whitespaces if we haven't started determining the + // type. + continue; + } + if (!ctype_alnum($clause[$i])) { + // The type contains only alphanumeric characters. + break; + } + // Adding character. + $type .= $clause[$i]; + } + return $type; + } + + /** + * Replaces the clause in the query. If the clause does not exist, it is + * inserted. + * + * It is a very basic version of a query builder. + * + * @param Statement $statement The parsed query that has to be modified. + * @param TokensList $list The list of tokens. + * @param string $clause The clause to be replaced. + * + * @return string + */ + public static function replaceClause($statement, $list, $clause) + { + + /** + * Current location. + * @var int + */ + $currIdx = 0; + + /** + * The count of brackets. + * We keep track of them so we won't insert the clause in a subquery. + * @var int + */ + $brackets = 0; + + /** + * The sections before the clause + * @var string + */ + $before = ''; + + /** + * The sections after the clause. + * @var string + */ + $after = ''; + + /** + * The place where the clause should be added. + * @var int + */ + $clauseIdx = $statement::$SECTIONS[static::getClauseType($clause)]; + + for ($i = $statement->first; $i <= $statement->last; ++$i) { + + $token = $list->tokens[$i]; + + if ($token->type === Token::TYPE_DELIMITER) { + break; + } + + if ($token->type === Token::TYPE_OPERATOR) { + if ($token->value === '(') { + ++$brackets; + } elseif ($token->value === ')') { + --$brackets; + } + } + + if ($brackets == 0) { + // Checking if we changed sections. + if ($token->type === Token::TYPE_KEYWORD) { + if (isset($statement::$SECTIONS[$token->value])) { + if ($statement::$SECTIONS[$token->value] > $currIdx) { + $currIdx = $statement::$SECTIONS[$token->value]; + } + } + } + } + + if ($currIdx < $clauseIdx) { + $before .= $token->token; + } elseif ($currIdx > $clauseIdx) { + $after .= $token->value; + } + } + + return $before . ' ' . $clause . ' ' . $after; + } + } diff --git a/src/Utils/Routine.php b/src/Utils/Routine.php index 4b9b21d..8298d8d 100644 --- a/src/Utils/Routine.php +++ b/src/Utils/Routine.php @@ -39,7 +39,7 @@ class Routine $lexer = new Lexer($param); // A dummy parser is used for error reporting. - $type = DataTypeFragment::parse(new Parser(), $lexer->tokens); + $type = DataTypeFragment::parse(new Parser(), $lexer->list); if ($type === null) { return array('', '', '', '', ''); @@ -71,7 +71,7 @@ class Routine $lexer = new Lexer('(' . $param . ')'); // A dummy parser is used for error reporting. - $param = ParamDefFragment::parse(new Parser(), $lexer->tokens); + $param = ParamDefFragment::parse(new Parser(), $lexer->list); if (empty($param[0])) { return array('', '', '', '', ''); |