added new dql parser to draft folder
This commit is contained in:
parent
efa434800c
commit
97b4eb3f17
156
draft/Doctrine/Query/Parser.php
Normal file
156
draft/Doctrine/Query/Parser.php
Normal file
@ -0,0 +1,156 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
4
draft/Doctrine/Query/Parser/Exception.php
Normal file
4
draft/Doctrine/Query/Parser/Exception.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php
|
||||
class Doctrine_Query_Parser_Exception extends Exception
|
||||
{
|
||||
}
|
30
draft/Doctrine/Query/Printer.php
Normal file
30
draft/Doctrine/Query/Printer.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?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";
|
||||
}
|
||||
}
|
||||
}
|
65
draft/Doctrine/Query/Production.php
Normal file
65
draft/Doctrine/Query/Production.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
}
|
23
draft/Doctrine/Query/Production/Atom.php
Normal file
23
draft/Doctrine/Query/Production/Atom.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
18
draft/Doctrine/Query/Production/BetweenExpression.php
Normal file
18
draft/Doctrine/Query/Production/BetweenExpression.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
27
draft/Doctrine/Query/Production/ComparisonExpression.php
Normal file
27
draft/Doctrine/Query/Production/ComparisonExpression.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
draft/Doctrine/Query/Production/ComparisonOperator.php
Normal file
31
draft/Doctrine/Query/Production/ComparisonOperator.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
16
draft/Doctrine/Query/Production/ConditionalExpression.php
Normal file
16
draft/Doctrine/Query/Production/ConditionalExpression.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
15
draft/Doctrine/Query/Production/ConditionalFactor.php
Normal file
15
draft/Doctrine/Query/Production/ConditionalFactor.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
17
draft/Doctrine/Query/Production/ConditionalPrimary.php
Normal file
17
draft/Doctrine/Query/Production/ConditionalPrimary.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
16
draft/Doctrine/Query/Production/ConditionalTerm.php
Normal file
16
draft/Doctrine/Query/Production/ConditionalTerm.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
13
draft/Doctrine/Query/Production/DeleteClause.php
Normal file
13
draft/Doctrine/Query/Production/DeleteClause.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
23
draft/Doctrine/Query/Production/DeleteStatement.php
Normal file
23
draft/Doctrine/Query/Production/DeleteStatement.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
20
draft/Doctrine/Query/Production/Expression.php
Normal file
20
draft/Doctrine/Query/Production/Expression.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
17
draft/Doctrine/Query/Production/Factor.php
Normal file
17
draft/Doctrine/Query/Production/Factor.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
18
draft/Doctrine/Query/Production/FromClause.php
Normal file
18
draft/Doctrine/Query/Production/FromClause.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
19
draft/Doctrine/Query/Production/GroupByClause.php
Normal file
19
draft/Doctrine/Query/Production/GroupByClause.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
11
draft/Doctrine/Query/Production/GroupByItem.php
Normal file
11
draft/Doctrine/Query/Production/GroupByItem.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* OrderByItem = PathExpression
|
||||
*/
|
||||
class Doctrine_Query_Production_GroupByItem extends Doctrine_Query_Production
|
||||
{
|
||||
public function execute(array $params = array())
|
||||
{
|
||||
$this->PathExpression();
|
||||
}
|
||||
}
|
13
draft/Doctrine/Query/Production/HavingClause.php
Normal file
13
draft/Doctrine/Query/Production/HavingClause.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
27
draft/Doctrine/Query/Production/Join.php
Normal file
27
draft/Doctrine/Query/Production/Join.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
19
draft/Doctrine/Query/Production/OrderByClause.php
Normal file
19
draft/Doctrine/Query/Production/OrderByClause.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
17
draft/Doctrine/Query/Production/OrderByItem.php
Normal file
17
draft/Doctrine/Query/Production/OrderByItem.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
16
draft/Doctrine/Query/Production/PathExpression.php
Normal file
16
draft/Doctrine/Query/Production/PathExpression.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
55
draft/Doctrine/Query/Production/Primary.php
Normal file
55
draft/Doctrine/Query/Production/Primary.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
27
draft/Doctrine/Query/Production/QuantifiedExpression.php
Normal file
27
draft/Doctrine/Query/Production/QuantifiedExpression.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?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(')');
|
||||
}
|
||||
}
|
24
draft/Doctrine/Query/Production/QueryLanguage.php
Normal file
24
draft/Doctrine/Query/Production/QueryLanguage.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
18
draft/Doctrine/Query/Production/RangeVariableDeclaration.php
Normal file
18
draft/Doctrine/Query/Production/RangeVariableDeclaration.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
22
draft/Doctrine/Query/Production/SelectClause.php
Normal file
22
draft/Doctrine/Query/Production/SelectClause.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
24
draft/Doctrine/Query/Production/SelectExpression.php
Normal file
24
draft/Doctrine/Query/Production/SelectExpression.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
36
draft/Doctrine/Query/Production/SelectStatement.php
Normal file
36
draft/Doctrine/Query/Production/SelectStatement.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
20
draft/Doctrine/Query/Production/Term.php
Normal file
20
draft/Doctrine/Query/Production/Term.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
19
draft/Doctrine/Query/Production/UpdateClause.php
Normal file
19
draft/Doctrine/Query/Production/UpdateClause.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
23
draft/Doctrine/Query/Production/UpdateStatement.php
Normal file
23
draft/Doctrine/Query/Production/UpdateStatement.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
13
draft/Doctrine/Query/Production/WhereClause.php
Normal file
13
draft/Doctrine/Query/Production/WhereClause.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
173
draft/Doctrine/Query/Scanner.php
Normal file
173
draft/Doctrine/Query/Scanner.php
Normal file
@ -0,0 +1,173 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
61
draft/Doctrine/Query/Token.php
Normal file
61
draft/Doctrine/Query/Token.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?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;
|
||||
}
|
75
draft/query-language.txt
Normal file
75
draft/query-language.txt
Normal file
@ -0,0 +1,75 @@
|
||||
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"
|
20
draft/test.php
Normal file
20
draft/test.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?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);
|
107
draft/tests/ScannerTest.php
Normal file
107
draft/tests/ScannerTest.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?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());
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user