Commit 97b4eb3f authored by jepso's avatar jepso

added new dql parser to draft folder

parent efa43480
<?php
class Doctrine_Query_Parser
{
/**
* The minimum number of tokens read after last detected error before
* another error can be reported.
*
* @var int
*/
const MIN_ERROR_DISTANCE = 2;
/**
* A scanner object.
*
* @var Doctrine_Query_Scanner
*/
protected $_scanner;
/**
* An array of production objects with their names as keys.
*
* @var array
*/
protected $_productions = array();
/**
* The next token in the query string.
*
* @var Doctrine_Query_Token
*/
public $lookahead;
/**
* Array containing syntax and semantical errors detected in the query
* string during parsing process.
*
* @var array
*/
protected $_errors = array();
/**
* The number of tokens read since last error in the input string
*
* @var int
*/
protected $_errorDistance = self::MIN_ERROR_DISTANCE;
/**
* A query printer object used to print a parse tree from the input string.
*
* @var Doctrine_Query_Printer
*/
protected $_printer;
/**
* Creates a new query parser object.
*
* @param string $input query string to be parsed
*/
public function __construct($input)
{
$this->_scanner = new Doctrine_Query_Scanner($input);
$this->_printer = new Doctrine_Query_Printer(true);
}
public function getProduction($name)
{
if ( ! isset($this->_productions[$name])) {
$class = 'Doctrine_Query_Production_' . $name;
$this->_productions[$name] = new $class($this);
}
return $this->_productions[$name];
}
/**
* Attempts to match the given token with the current lookahead token.
*
* If they match, updates the lookahead token; otherwise raises a syntax
* error.
*
* @param int|string token type or value
*/
public function match($token)
{
if (is_string($token)) {
$isMatch = ($this->lookahead['value'] === $token);
} else {
$isMatch = ($this->lookahead['type'] === $token);
}
if ($isMatch) {
//$this->_printer->println($this->lookahead['value']);
$this->lookahead = $this->_scanner->scan();
$this->_errorDistance++;
} else {
$this->syntaxError();
}
}
public function syntaxError()
{
$this->_error('Unexpected "' . $this->lookahead['value'] . '"');
}
public function semanticalError($message)
{
$this->_error($message);
}
protected function _error($message)
{
if ($this->_errorDistance >= self::MIN_ERROR_DISTANCE) {
$message .= 'at line ' . $this->lookahead['line']
. ', column ' . $this->lookahead['column'];
$this->_errors[] = $message;
}
$this->_errorDistance = 0;
}
/**
* Returns the scanner object associated with this object.
*
* @return Doctrine_Query_Scanner
*/
public function getScanner()
{
return $this->_scanner;
}
public function getPrinter()
{
return $this->_printer;
}
/**
* Parses a query string.
*
* @throws Doctrine_Query_Parser_Exception if errors were detected in the query string
*/
public function parse()
{
$this->lookahead = $this->_scanner->scan();
$this->getProduction('QueryLanguage')->execute();
$this->match(Doctrine_Query_Token::T_EOS);
if (count($this->_errors)) {
$msg = 'Query string parsing failed ('
. implode('; ', $this->_errors) . ').';
throw new Doctrine_Query_Parser_Exception($msg);
}
}
}
<?php
class Doctrine_Query_Parser_Exception extends Exception
{
}
<?php
class Doctrine_Query_Printer
{
protected $_indent = 0;
protected $_silent;
public function __construct($silent = false)
{
$this->_silent = $silent;
}
public function startProduction($name)
{
$this->println('(' . $name);
$this->_indent++;
}
public function endProduction()
{
$this->_indent--;
$this->println(')');
}
public function println($str)
{
if ( ! $this->_silent) {
echo str_repeat(' ', $this->_indent), $str, "\n";
}
}
}
<?php
/**
* An abstract base class that all query parser productions extend.
*/
abstract class Doctrine_Query_Production
{
/**
* a parser object
*
* @var Doctrine_Query_Parser
*/
protected $_parser;
/**
* Creates a new production object.
*
* @param Doctrine_Query_Parser $parser a parser object
*/
public function __construct(Doctrine_Query_Parser $parser)
{
$this->_parser = $parser;
}
protected function _isNextToken($token)
{
$la = $this->_parser->lookahead;
return ($la['type'] === $token || $la['value'] === $token);
}
/**
* Executes a production with specified name and parameters.
*
* @param string $name production name
* @param array $params an associative array containing parameter names and
* their values
* @return mixed
*/
public function __call($method, $args)
{
return $this->_parser->getProduction($method)->execute($args);
$this->_parser->getPrinter()->startProduction($name);
$retval = $this->_parser->getProduction($method)->execute($args);
$this->_parser->getPrinter()->endProduction();
return $retval;
}
/**
* Executes this production using the specified parameters.
*
* @param array $params an associative array containing parameter names and
* their values
* @return mixed
*/
abstract public function execute(array $params = array());
protected function _isSubquery()
{
$lookahead = $this->_parser->lookahead;
$next = $this->_parser->getScanner()->peek();
return $lookahead['value'] === '(' && $next['type'] === Doctrine_Query_Token::T_SELECT;
}
}
<?php
/**
* Atom = string | numeric | input_parameter
*/
class Doctrine_Query_Production_Atom extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
switch ($this->_parser->lookahead['type']) {
case Doctrine_Query_Token::T_STRING:
$this->_parser->match(Doctrine_Query_Token::T_STRING);
break;
case Doctrine_Query_Token::T_NUMERIC:
$this->_parser->match(Doctrine_Query_Token::T_NUMERIC);
break;
case Doctrine_Query_Token::T_INPUT_PARAMETER:
$this->_parser->match(Doctrine_Query_Token::T_INPUT_PARAMETER);
break;
default:
$this->_parser->syntaxError();
}
}
}
<?php
/**
* BetweenExpression = ["NOT"] "BETWEEN" Expression "AND" Expression
*/
class Doctrine_Query_Production_BetweenExpression extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
if ($this->_isNextToken(Doctrine_Query_Token::T_NOT)) {
$this->_parser->match(Doctrine_Query_Token::T_NOT);
}
$this->_parser->match(Doctrine_Query_Token::T_BETWEEN);
$this->Expression();
$this->_parser->match(Doctrine_Query_Token::T_AND);
$this->Expression();
}
}
<?php
/**
* ComparisonExpression = ComparisonOperator ( QuantifiedExpression | Expression | "(" Subselect ")" )
*/
class Doctrine_Query_Production_ComparisonExpression extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->ComparisonOperator();
if ($this->_isSubquery()) {
$this->_parser->match('(');
$this->Subselect();
$this->_parser->match(')');
} else {
switch ($this->_parser->lookahead['type']) {
case Doctrine_Query_Token::T_ALL:
case Doctrine_Query_Token::T_SOME:
case Doctrine_Query_Token::T_NONE:
$this->QuantifiedExpression();
break;
default:
$this->Expression();
}
}
}
}
<?php
/**
* ComparisonOperator = "=" | "<" | "<=" | "<>" | ">" | ">="
*/
class Doctrine_Query_Production_ComparisonOperator extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
switch ($this->_parser->lookahead['value']) {
case '=':
$this->_parser->match('=');
break;
case '<':
$this->_parser->match('<');
if ($this->_isNextToken('=')) {
$this->_parser->match('=');
} elseif ($this->_isNextToken('>')) {
$this->_parser->match('>');
}
break;
case '>':
$this->_parser->match('>');
if ($this->_isNextToken('=')) {
$this->_parser->match('=');
}
break;
default:
$this->_parser->syntaxError();
}
}
}
<?php
/**
* ConditionalExpression = ConditionalTerm {"OR" ConditionalTerm}
*/
class Doctrine_Query_Production_ConditionalExpression extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->ConditionalTerm();
while ($this->_isNextToken(Doctrine_Query_Token::T_OR)) {
$this->_parser->match(Doctrine_Query_Token::T_OR);
$this->ConditionalTerm();
}
}
}
<?php
/**
* ConditionalFactor = ["NOT"] ConditionalPrimary
*/
class Doctrine_Query_Production_ConditionalFactor extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
if ($this->_isNextToken(Doctrine_Query_Token::T_NOT)) {
$this->_parser->match(Doctrine_Query_Token::T_NOT);
}
$this->ConditionalPrimary();
}
}
<?php
/**
* ConditionalPrimary = SimpleConditionalExpression | "(" ConditionalExpression ")"
*/
class Doctrine_Query_Production_ConditionalPrimary extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
if ($this->_isNextToken('(')) {
$this->_parser->match('(');
$this->ConditionalExpression();
$this->_parser->match(')');
} else {
$this->SimpleConditionalExpression();
}
}
}
<?php
/**
* ConditionalTerm = ConditionalFactor {"AND" ConditionalFactor}
*/
class Doctrine_Query_Production_ConditionalTerm extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->ConditionalFactor();
while ($this->_isNextToken(Doctrine_Query_Token::T_AND)) {
$this->_parser->match(Doctrine_Query_Token::T_AND);
$this->ConditionalFactor();
}
}
}
<?php
/**
* DeleteClause = "DELETE" "FROM" RangeVariableDeclaration
*/
class Doctrine_Query_Production_DeleteClause extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->_parser->match(Doctrine_Query_Token::T_DELETE);
$this->_parser->match(Doctrine_Query_Token::T_FROM);
$this->RangeVariableDeclaration();
}
}
<?php
/**
* DeleteStatement = DeleteClause [WhereClause] [OrderByClause] [LimitClause]
*/
class Doctrine_Query_Production_DeleteStatement extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->DeleteClause();
if ($this->_isNextToken(Doctrine_Query_Token::T_WHERE)) {
$this->WhereClause();
}
if ($this->_isNextToken(Doctrine_Query_Token::T_ORDER)) {
$this->OrderByClause();
}
if ($this->_isNextToken(Doctrine_Query_Token::T_LIMIT)) {
$this->LimitClause();
}
}
}
<?php
/**
* Expression = Term {("+" | "-") Term}
*/
class Doctrine_Query_Production_Expression extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->Term();
while ($this->_isNextToken('+') || $this->_isNextToken('-')) {
if ($this->_isNextToken('+')) {
$this->_parser->match('+');
} else{
$this->_parser->match('-');
}
$this->Term();
}
}
}
<?php
/**
* Factor = [("+" | "-")] Primary
*/
class Doctrine_Query_Production_Factor extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
if ($this->_isNextToken('+')) {
$this->_parser->match('+');
} elseif ($this->_isNextToken('-')) {
$this->_parser->match('-');
}
$this->Primary();
}
}
<?php
/**
* FromClause = "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}
*/
class Doctrine_Query_Production_FromClause extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->_parser->match(Doctrine_Query_Token::T_FROM);
$this->IdentificationVariableDeclaration();
while ($this->_isNextToken(',')) {
$this->_parser->match(',');
$this->IdentificationVariableDeclaration();
}
}
}
<?php
/**
* GroupByClause = "GROUP" "BY" GroupByItem {"," GroupByItem}
*/
class Doctrine_Query_Production_GroupByClause extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->_parser->match(Doctrine_Query_Token::T_GROUP);
$this->_parser->match(Doctrine_Query_Token::T_BY);
$this->GroupByItem();
while ($this->_isNextToken(',')) {
$this->_parser->match(',');
$this->GroupByItem();
}
}
}
<?php
/**
* OrderByItem = PathExpression
*/
class Doctrine_Query_Production_GroupByItem extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->PathExpression();
}
}
<?php
/**
* HavingClause = "HAVING" ConditionalExpression
*/
class Doctrine_Query_Production_HavingClause extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->_parser->match(Doctrine_Query_Token::T_HAVING);
$this->ConditionalExpression();
}
}
<?php
/**
* IdentificationVariableDeclaration = RangeVariableDeclaration {Join}
*/
class Doctrine_Query_Production_IdentificationVariableDeclaration extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->RangeVariableDeclaration();
while ($this->_isNextToken(Doctrine_Query_Token::T_LEFT) ||
$this->_isNextToken(Doctrine_Query_Token::T_INNER) ||
$this->_isNextToken(Doctrine_Query_Token::T_JOIN)) {
$this->Join();
}
}
}
<?php
/**
* Join = ["LEFT" ["OUTER"] | "INNER"] "JOIN" PathExpression "AS" identifier
*/
class Doctrine_Query_Production_Join extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
if ($this->_isNextToken(Doctrine_Query_Token::T_LEFT)) {
$this->_parser->match(Doctrine_Query_Token::T_LEFT);
if ($this->_isNextToken(Doctrine_Query_Token::T_OUTER)) {
$this->_parser->match(Doctrine_Query_Token::T_OUTER);
}
} elseif ($this->_isNextToken(Doctrine_Query_Token::T_INNER)) {
$this->_parser->match(Doctrine_Query_Token::T_INNER);
}
$this->_parser->match(Doctrine_Query_Token::T_JOIN);
$this->PathExpression();
$this->_parser->match(Doctrine_Query_Token::T_AS);
$this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER);
}
}
<?php
/**
* OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem}
*/
class Doctrine_Query_Production_OrderByClause extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->_parser->match(Doctrine_Query_Token::T_ORDER);
$this->_parser->match(Doctrine_Query_Token::T_BY);
$this->OrderByItem();
while ($this->_isNextToken(',')) {
$this->_parser->match(',');
$this->OrderByItem();
}
}
}
<?php
/**
* OrderByItem = PathExpression ["ASC" | "DESC"]
*/
class Doctrine_Query_Production_OrderByItem extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->PathExpression();
if ($this->_isNextToken(Doctrine_Query_Token::T_ASC)) {
$this->_parser->match(Doctrine_Query_Token::T_ASC);
} elseif ($this->_isNextToken(Doctrine_Query_Token::T_DESC)) {
$this->_parser->match(Doctrine_Query_Token::T_DESC);
}
}
}
<?php
/**
* PathExpression = identifier { "." identifier }
*/
class Doctrine_Query_Production_PathExpression extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER);
while ($this->_isNextToken('.')) {
$this->_parser->match('.');
$this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER);
}
}
}
<?php
/**
* Primary = PathExpression | Atom | "(" Expression ")" | Function |
* AggregateExpression
*/
class Doctrine_Query_Production_Primary extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
switch ($this->_parser->lookahead['type']) {
case Doctrine_Query_Token::T_IDENTIFIER:
// @todo: custom functions
$this->PathExpression();
break;
case Doctrine_Query_Token::T_STRING:
case Doctrine_Query_Token::T_NUMERIC:
case Doctrine_Query_Token::T_INPUT_PARAMETER:
$this->Atom();
break;
case Doctrine_Query_Token::T_LENGTH:
case Doctrine_Query_Token::T_LOCATE:
case Doctrine_Query_Token::T_ABS:
case Doctrine_Query_Token::T_SQRT:
case Doctrine_Query_Token::T_MOD:
case Doctrine_Query_Token::T_SIZE:
case Doctrine_Query_Token::T_CURRENT_DATE:
case Doctrine_Query_Token::T_CURRENT_TIMESTAMP:
case Doctrine_Query_Token::T_CURRENT_TIME:
case Doctrine_Query_Token::T_SUBSTRING:
case Doctrine_Query_Token::T_CONCAT:
case Doctrine_Query_Token::T_TRIM:
case Doctrine_Query_Token::T_LOWER:
case Doctrine_Query_Token::T_UPPER:
$this->Function();
break;
case Doctrine_Query_Token::T_AVG:
case Doctrine_Query_Token::T_MAX:
case Doctrine_Query_Token::T_MIN:
case Doctrine_Query_Token::T_SUM:
case Doctrine_Query_Token::T_MOD:
case Doctrine_Query_Token::T_SIZE:
$this->AggregateExpression();
break;
case Doctrine_Query_Token::T_NONE:
if ($this->_isNextToken('(')) {
$this->_parser->match('(');
$this->Expression();
$this->_parser->match(')');
break;
}
default:
$this->_parser->syntaxError();
}
}
}
<?php
/**
* QuantifiedExpression = ("ALL" | "ANY" | "SOME") "(" Subselect ")"
*/
class Doctrine_Query_Production_QuantifiedExpression extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
switch ($this->_parser->lookahead['type']) {
case Doctrine_Query_Token::T_ALL:
$this->_parser->match(Doctrine_Query_Token::T_ALL);
break;
case Doctrine_Query_Token::T_ANY:
$this->_parser->match(Doctrine_Query_Token::T_ANY);
break;
case Doctrine_Query_Token::T_SOME:
$this->_parser->match(Doctrine_Query_Token::T_SOME);
break;
default:
$this->syntaxError();
}
$this->_parser->match('(');
$this->Subselect();
$this->_parser->match(')');
}
}
<?php
/**
* QueryLanguage = SelectStatement | UpdateStatement | DeleteStatement
*/
class Doctrine_Query_Production_QueryLanguage extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
switch ($this->_parser->lookahead['type']) {
case Doctrine_Query_Token::T_SELECT:
case Doctrine_Query_Token::T_FROM:
$this->SelectStatement();
break;
case Doctrine_Query_Token::T_UPDATE:
$this->UpdateStatement();
break;
case Doctrine_Query_Token::T_DELETE:
$this->DeleteStatement();
break;
default:
$this->_parser->syntaxError();
}
}
}
<?php
/**
* RangeVariableDeclaration = PathExpression [["AS" ] identifier]
*/
class Doctrine_Query_Production_RangeVariableDeclaration extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->PathExpression();
if ($this->_isNextToken(Doctrine_Query_Token::T_AS)) {
$this->_parser->match(Doctrine_Query_Token::T_AS);
$this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER);
} elseif ($this->_isNextToken(Doctrine_Query_Token::T_IDENTIFIER)) {
$this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER);
}
}
}
<?php
/**
* SelectClause = "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
*/
class Doctrine_Query_Production_SelectClause extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->_parser->match(Doctrine_Query_Token::T_SELECT);
if ($this->_isNextToken(Doctrine_Query_Token::T_DISTINCT)) {
$this->_parser->match(Doctrine_Query_Token::T_DISTINCT);
}
$this->SelectExpression();
while ($this->_isNextToken(',')) {
$this->_parser->match(',');
$this->SelectExpression();
}
}
}
<?php
/**
* SelectExpression = (Expression | "(" Subselect ")" ) [["AS"] identifier]
*/
class Doctrine_Query_Production_SelectExpression extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
if ($this->_isSubquery()) {
$this->_parser->match('(');
$this->Subselect();
$this->_parser->match(')');
} else {
$this->Expression();
}
if ($this->_isNextToken(Doctrine_Query_Token::T_AS)) {
$this->_parser->match(Doctrine_Query_Token::T_AS);
$this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER);
} elseif ($this->_isNextToken(Doctrine_Query_Token::T_IDENTIFIER)) {
$this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER);
}
}
}
<?php
/**
* SelectStatement = [SelectClause] FromClause [WhereClause] [GroupByClause]
* [HavingClause] [OrderByClause] [LimitClause]
*/
class Doctrine_Query_Production_SelectStatement extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
if ($this->_isNextToken(Doctrine_Query_Token::T_SELECT)) {
$this->SelectClause();
}
$this->FromClause();
if ($this->_isNextToken(Doctrine_Query_Token::T_WHERE)) {
$this->WhereClause();
}
if ($this->_isNextToken(Doctrine_Query_Token::T_GROUP)) {
$this->GroupByClause();
}
if ($this->_isNextToken(Doctrine_Query_Token::T_HAVING)) {
$this->HavingClause();
}
if ($this->_isNextToken(Doctrine_Query_Token::T_ORDER)) {
$this->OrderByClause();
}
if ($this->_isNextToken(Doctrine_Query_Token::T_LIMIT)) {
$this->LimitClause();
}
}
}
<?php
/**
* SimpleConditionalExpression =
* Expression (ComparisonExpression | BetweenExpression | LikeExpression |
* InExpression | NullComparisonExpression | QuantifiedExpression) |
* ExistsExpression
*/
class Doctrine_Query_Production_SimpleConditionalExpression extends Doctrine_Query_Production
{
protected function _getExpressionType() {
if ($this->_isNextToken(Doctrine_Query_Token::T_NOT)) {
$token = $this->_parser->getScanner()->peek();
$this->_parser->getScanner()->resetPeek();
} else {
$token = $this->_parser->lookahead;
}
return $token['type'];
}
public function execute(array $params = array())
{
if ($this->_getExpressionType() === Doctrine_Query_Token::T_EXISTS) {
$this->ExistsExpression();
} else {
$this->Expression();
switch ($this->_getExpressionType()) {
case Doctrine_Query_Token::T_BETWEEN:
$this->BetweenExpression();
break;
case Doctrine_Query_Token::T_LIKE:
$this->LikeExpression();
break;
case Doctrine_Query_Token::T_IN:
$this->InExpression();
break;
case Doctrine_Query_Token::T_IS:
$this->NullComparisonExpression();
break;
case Doctrine_Query_Token::T_NONE:
$this->ComparisonExpression();
break;
default:
$this->_parser->syntaxError();
}
}
}
}
<?php
/**
* Term = Factor {("*" | "/") Factor}
*/
class Doctrine_Query_Production_Term extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->Factor();
while ($this->_isNextToken('*') || $this->_isNextToken('/')) {
if ($this->_isNextToken('*')) {
$this->_parser->match('*');
} else {
$this->_parser->match('/');
}
$this->Factor();
}
}
}
<?php
/**
* UpdateClause = "UPDATE" RangeVariableDeclaration "SET" UpdateItem {"," UpdateItem}
*/
class Doctrine_Query_Production_UpdateClause extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->_parser->match(Doctrine_Query_Token::T_UPDATE);
$this->RangeVariableDeclaration();
$this->_parser->match(Doctrine_Query_Token::T_SET);
$this->RangeVariableDeclaration();
while ($this->_isNextToken(',')) {
$this->_parser->match(',');
$this->RangeVariableDeclaration();
}
}
}
<?php
/**
* UpdateStatement = UpdateClause [WhereClause] [OrderByClause] [LimitClause]
*/
class Doctrine_Query_Production_UpdateStatement extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->UpdateClause();
if ($this->_isNextToken(Doctrine_Query_Token::T_WHERE)) {
$this->WhereClause();
}
if ($this->_isNextToken(Doctrine_Query_Token::T_ORDER)) {
$this->OrderByClause();
}
if ($this->_isNextToken(Doctrine_Query_Token::T_LIMIT)) {
$this->LimitClause();
}
}
}
<?php
/**
* WhereClause = "WHERE" ConditionalExpression
*/
class Doctrine_Query_Production_WhereClause extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->_parser->match(Doctrine_Query_Token::T_WHERE);
$this->ConditionalExpression();
}
}
<?php
class Doctrine_Query_Scanner
{
/**
* The query string
*
* @var string
*/
protected $_input;
/**
* The length of the query string
*
* @var string
*/
protected $_length;
/**
* Array of tokens already peeked
*
* @var array
*/
protected $_tokens = array();
/**
*
* @var int
*/
protected $_peekPosition = 0;
protected $_position = 0;
protected $_line = 1;
protected $_column = 1;
protected static $_regex = array(
'identifier' => '/^[a-z][a-z0-9_]*/i',
'numeric' => '/^[+-]?([0-9]+([\.][0-9]+)?)(e[+-]?[0-9]+)?/i',
'string' => "/^'([^']|'')*'/",
'input_parameter' => '/^\?|:[a-z]+/'
);
/**
* Creates a new query scanner object.
*
* @param string $input a query string
*/
public function __construct($input)
{
$this->_input = $input;
$this->_length = strlen($input);
}
/**
* Checks if an identifier is a keyword and returns its correct type.
*
* @param string $identifier identifier name
* @return int token type
*/
public function _checkLiteral($identifier)
{
$name = 'Doctrine_Query_Token::T_' . strtoupper($identifier);
if (defined($name)) {
$type = constant($name);
if ($type > 100) {
return $type;
}
}
return Doctrine_Query_Token::T_IDENTIFIER;
}
/**
* Returns the next token in the input string.
*
* The returned token is an associative array containing the following keys:
* 'type' : type of the token; @see Doctrine_Query_Token::T_* constants
* 'value' : string value of the token in the input string
* 'position' : start position of the token in the input string
* 'line' :
* 'column' :
*
* @return array the next token
*/
protected function _nextToken()
{
// ignore whitespace
while ($this->_position < $this->_length
&& ctype_space($this->_input[$this->_position])) {
if ($this->_input[$this->_position] === "\n") {
$this->_line++;
$this->_column = 1;
} else {
$this->_column++;
}
$this->_position++;
}
if ($this->_position < $this->_length) {
$subject = substr($this->_input, $this->_position);
if (preg_match(self::$_regex['identifier'], $subject, $matches)) {
$value = $matches[0];
$type = $this->_checkLiteral($value);
} elseif (preg_match(self::$_regex['numeric'], $subject, $matches)) {
$value = $matches[0];
$type = Doctrine_Query_Token::T_NUMERIC;
} elseif (preg_match(self::$_regex['string'], $subject, $matches)) {
$value = $matches[0];
$type = Doctrine_Query_Token::T_STRING;
} elseif (preg_match(self::$_regex['input_parameter'], $subject, $matches)) {
$value = $matches[0];
$type = Doctrine_Query_Token::T_INPUT_PARAMETER;
} else {
$value = $subject[0];
$type = Doctrine_Query_Token::T_NONE;
}
} else {
$value = '';
$type = Doctrine_Query_Token::T_EOS;
}
$token = array(
'type' => $type,
'value' => $value,
'position' => $this->_position,
'line' => $this->_line,
'column' => $this->_column
);
$increment = strlen($value);
$this->_position += $increment;
$this->_column += $increment;
return $token;
}
/**
* Returns the next token without removing it from the input string.
*
* @return array the next token
*/
public function peek()
{
if ($this->_peekPosition >= count($this->_tokens)) {
$this->_tokens[] = $this->_nextToken();
}
return $this->_tokens[$this->_peekPosition++];
}
public function resetPeek()
{
$this->_peekPosition = 0;
}
/**
* Returns the next token in the input string.
*
* @return array the next token
*/
public function scan()
{
if (count($this->_tokens) > 0) {
$this->resetPeek();
return array_shift($this->_tokens);
} else {
return $this->_nextToken();
}
}
}
<?php
class Doctrine_Query_Token
{
const T_EOS = 0;
const T_NONE = 1;
const T_IDENTIFIER = 2;
const T_NUMERIC = 3;
const T_STRING = 4;
const T_INPUT_PARAMETER = 5;
const T_ALL = 101;
const T_AND = 102;
const T_ANY = 103;
const T_AS = 104;
const T_ASC = 105;
const T_AVG = 106;
const T_BETWEEN = 107;
const T_BY = 108;
const T_COUNT = 109;
const T_DELETE = 100;
const T_DESC = 111;
const T_DISTINCT = 112;
const T_ESCAPE = 113;
const T_EXISTS = 114;
const T_FROM = 115;
const T_GROUP = 116;
const T_HAVING = 117;
const T_IN = 118;
const T_INNER = 119;
const T_IS = 120;
const T_JOIN = 121;
const T_LEFT = 122;
const T_LIKE = 123;
const T_LIMIT = 124;
const T_MAX = 125;
const T_MIN = 126;
const T_NOT = 127;
const T_NULL = 128;
const T_OFFSET = 129;
const T_OR = 130;
const T_ORDER = 131;
const T_SELECT = 132;
const T_SET = 133;
const T_SOME = 134;
const T_SUM = 135;
const T_UPDATE = 136;
const T_WHERE = 137;
const T_LENGTH = 138;
const T_LOCATE = 139;
const T_ABS = 140;
const T_SQRT = 141;
const T_MOD = 142;
const T_SIZE = 143;
const T_CURRENT_DATE = 144;
const T_CURRENT_TIMESTAMP = 145;
const T_CURRENT_TIME = 146;
const T_SUBSTRING = 147;
const T_CONCAT = 148;
const T_TRIM = 149;
const T_LOWER = 150;
const T_UPPER = 151;
}
QueryLanguage = SelectStatement | UpdateStatement | DeleteStatement
SelectStatement = [SelectClause] FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] [LimitClause]
UpdateStatement = UpdateClause [WhereClause] [OrderByClause] [LimitClause]
DeleteStatement = DeleteClause [WhereClause] [OrderByClause] [LimitClause]
Subselect = SimpleSelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] [LimitClause] [OffsetClause]
SelectClause = "SELECT" ["ALL" | "DISTINCT"] SelectExpression {"," SelectExpression}
SimpleSelectClause = "SELECT" ["ALL" | "DISTINCT"] SelectExpression
DeleteClause = "DELETE" "FROM" RangeVariableDeclaration
WhereClause = "WHERE" ConditionalExpression
FromClause = "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}
HavingClause = "HAVING" ConditionalExpression
GroupByClause = "GROUP" "BY" GroupByItem {"," GroupByItem}
OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem}
LimitClause = "LIMIT" Expression ["OFFSET" Expression]
UpdateClause = "UPDATE" RangeVariableDeclaration "SET" UpdateItem {"," UpdateItem}
OrderByItem = PathExpression ["ASC" | "DESC"]
GroupByItem = PathExpression
UpdateItem = PathExpression "=" (Expression | "NULL")
IdentificationVariableDeclaration = RangeVariableDeclaration {Join}
RangeVariableDeclaration = PathExpression [["AS" ] identifier<identification-variable>]
Join = ["LEFT" | "INNER"] "JOIN" PathExpression "AS" identifier
ConditionalExpression = ConditionalTerm {"OR" ConditionalTerm}
ConditionalTerm = ConditionalFactor {"AND" ConditionalFactor}
ConditionalFactor = ["NOT"] ConditionalPrimary
ConditionalPrimary = SimpleConditionalExpression | "(" ConditionalExpression ")"
SimpleConditionalExpression
= Expression (ComparisonExpression | BetweenExpression | LikeExpression
| InExpression | NullComparisonExpression) | ExistsExpression
Atom = string-literal | numeric-constant | input-parameter
Expression = Expression {("+" | "-" | "*" | "/") Expression}
Expression = ("+" | "-") Expression
Expression = "(" Expression ")"
Expression = PathExpression | Atom | | Function | AggregateExpression
SelectExpression = (Expression | "(" Subselect ")" ) [["AS"] identifier]
PathExpression = identifier {"." identifier}
AggregateExpression = ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] Expression ")"
| "COUNT" "(" ["DISTINCT"] (Expression | "*") ")"
QuantifiedExpression = ("ALL" | "ANY" | "SOME") "(" Subselect ")"
BetweenExpression = ["NOT"] "BETWEEN" Expression "AND" Expression
ComparisonExpression = ComparisonOperator ( QuantifiedExpression | Expression | "(" Subselect ")" )
InExpression = ["NOT"] "IN" "(" (Atom {"," Atom} | Subselect) ")"
LikeExpression = ["NOT"] "LIKE" Expression ["ESCAPE" escape_character]
NullComparisonExpression = "IS" ["NOT"] "NULL"
ExistsExpression = ["NOT"] "EXISTS" "(" Subselect ")"
Function =
"CURRENT_DATE" |
"CURRENT_TIME" |
"CURRENT_TIMESTAMP" |
"LENGTH" "(" Expression ")" |
"LOCATE" "(" Expression "," Expression ["," Expression] ")" |
"ABS" "(" Expression ")" |
"SQRT" "(" Expression ")" |
"MOD" "(" Expression "," Expression ")" |
"SIZE" "(" Expression ")" |
"CONCAT" "(" Expression "," Expression ")" |
"SUBSTRING" "(" Expression "," Expression "," "Expression" ")" |
"TRIM" "(" [[TrimSpecification] [trim_character] "FROM"] string_primary ")" |
"LOWER" "(" string_primary ")" |
"UPPER" "(" string_primary ")" |
identifier "(" [Expression {"," Expression}]")" // Custom function
TrimSpecification = "LEADING" | "TRAILING" | "BOTH"
<?php
require_once 'Doctrine.php';
spl_autoload_register(array('Doctrine', 'autoload'));
$n = 1000;
$start = microtime(true);
for ($i = 0; $i < $n; $i++) {
/* $parser = new Doctrine_Query_Parser('SELECT u.name, u.age FROM User u WHERE u.id = ?');
$parser->parse();*/
$scanner = new Doctrine_Query_Scanner('SELECT u.name, u.age FROM User u WHERE u.id = ?');
do {
$token = $scanner->scan();
} while ($token['type'] !== Doctrine_Query_Token::T_EOS);
}
$end = microtime(true);
printf("Parsed %d queries: %.3f ms per query\n", $n, ($end - $start) / $n * 1000);
<?php
require_once 'PHPUnit/Framework.php';
require_once '../Doctrine.php';
spl_autoload_register(array('Doctrine', 'autoload'));
class ScannerTest extends PHPUnit_Framework_TestCase
{
public function testScannerRecognizesIdentifierWithLengthOfOneCharacter()
{
$scanner = new Doctrine_Query_Scanner('u');
$token = $scanner->scan();
$this->assertEquals(Doctrine_Query_Token::T_IDENTIFIER, $token->getType());
$this->assertEquals('u', $token->getValue());
}
public function testScannerRecognizesIdentifierConsistingOfLetters()
{
$scanner = new Doctrine_Query_Scanner('someIdentifier');
$token = $scanner->scan();
$this->assertEquals(Doctrine_Query_Token::T_IDENTIFIER, $token->getType());
$this->assertEquals('someIdentifier', $token->getValue());
}
public function testScannerRecognizesIdentifierIncludingDigits()
{
$scanner = new Doctrine_Query_Scanner('s0m31d3nt1f13r');
$token = $scanner->scan();
$this->assertEquals(Doctrine_Query_Token::T_IDENTIFIER, $token->getType());
$this->assertEquals('s0m31d3nt1f13r', $token->getValue());
}
public function testScannerRecognizesIdentifierIncludingUnderscore()
{
$scanner = new Doctrine_Query_Scanner('some_identifier');
$token = $scanner->scan();
$this->assertEquals(Doctrine_Query_Token::T_IDENTIFIER, $token->getType());
$this->assertEquals('some_identifier', $token->getValue());
}
public function testScannerRecognizesDecimalInteger()
{
$scanner = new Doctrine_Query_Scanner('1234');
$token = $scanner->scan();
$this->assertEquals(Doctrine_Query_Token::T_NUMERIC, $token->getType());
$this->assertEquals(1234, $token->getValue());
}
public function testScannerRecognizesNegativeDecimalInteger()
{
$scanner = new Doctrine_Query_Scanner('-123');
$token = $scanner->scan();
$this->assertEquals(Doctrine_Query_Token::T_NUMERIC, $token->getType());
$this->assertEquals(-123, $token->getValue());
}
public function testScannerRecognizesFloat()
{
$scanner = new Doctrine_Query_Scanner('1.234');
$token = $scanner->scan();
$this->assertEquals(Doctrine_Query_Token::T_NUMERIC, $token->getType());
$this->assertEquals(1.234, $token->getValue());
}
public function testScannerRecognizesFloatWithExponent()
{
$scanner = new Doctrine_Query_Scanner('1.2e3');
$token = $scanner->scan();
$this->assertEquals(Doctrine_Query_Token::T_NUMERIC, $token->getType());
$this->assertEquals(1.2e3, $token->getValue());
}
public function testScannerRecognizesFloatWithNegativeExponent()
{
$scanner = new Doctrine_Query_Scanner('7E-10');
$token = $scanner->scan();
$this->assertEquals(Doctrine_Query_Token::T_NUMERIC, $token->getType());
$this->assertEquals(7E-10, $token->getValue());
}
public function testScannerRecognizesStringContainingWhitespace()
{
$scanner = new Doctrine_Query_Scanner("'This is a string.'");
$token = $scanner->scan();
$this->assertEquals(Doctrine_Query_Token::T_STRING, $token->getType());
$this->assertEquals("'This is a string.'", $token->getValue());
}
public function testScannerRecognizesStringContainingSingleQuotes()
{
$scanner = new Doctrine_Query_Scanner("'abc''defg'''");
$token = $scanner->scan();
$this->assertEquals(Doctrine_Query_Token::T_STRING, $token->getType());
$this->assertEquals("'abc''defg'''", $token->getValue());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment