summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Lexer.php14
-rw-r--r--src/Parser.php2
-rw-r--r--src/Statement.php4
-rw-r--r--src/Statements/SelectStatement.php14
-rw-r--r--src/Utils/Query.php320
-rw-r--r--src/Utils/Routine.php4
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('', '', '', '', '');