1
0
mirror of synced 2024-12-13 22:56:04 +03:00
doctrine2/lib/Doctrine/ORM/Query/Parser.php

2144 lines
68 KiB
PHP
Raw Normal View History

<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Query;
use Doctrine\Common\DoctrineException;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\AST;
use Doctrine\ORM\Query\Exec;
/**
* An LL(*) parser for the context-free grammar of the Doctrine Query Language.
* Parses a DQL query, reports any errors in it, and generates an AST.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
* @author Roman Borschel <roman@code-factory.org>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://www.doctrine-project.org
* @since 2.0
* @version $Revision$
*/
class Parser
{
/** Maps registered string function names to class names. */
private static $_STRING_FUNCTIONS = array(
'concat' => 'Doctrine\ORM\Query\AST\Functions\ConcatFunction',
'substring' => 'Doctrine\ORM\Query\AST\Functions\SubstringFunction',
'trim' => 'Doctrine\ORM\Query\AST\Functions\TrimFunction',
'lower' => 'Doctrine\ORM\Query\AST\Functions\LowerFunction',
'upper' => 'Doctrine\ORM\Query\AST\Functions\UpperFunction'
2009-06-14 21:34:28 +04:00
);
/** Maps registered numeric function names to class names. */
private static $_NUMERIC_FUNCTIONS = array(
'length' => 'Doctrine\ORM\Query\AST\Functions\LengthFunction',
'locate' => 'Doctrine\ORM\Query\AST\Functions\LocateFunction',
'abs' => 'Doctrine\ORM\Query\AST\Functions\AbsFunction',
'sqrt' => 'Doctrine\ORM\Query\AST\Functions\SqrtFunction',
'mod' => 'Doctrine\ORM\Query\AST\Functions\ModFunction',
'size' => 'Doctrine\ORM\Query\AST\Functions\SizeFunction'
);
/** Maps registered datetime function names to class names. */
private static $_DATETIME_FUNCTIONS = array(
'current_date' => 'Doctrine\ORM\Query\AST\Functions\CurrentDateFunction',
'current_time' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimeFunction',
'current_timestamp' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimestampFunction'
);
/**
* The minimum number of tokens read after last detected error before
* another error can be reported.
*
* @var int
*/
//const MIN_ERROR_DISTANCE = 2;
/**
* Path expressions that were encountered during parsing of SelectExpressions
* and still need to be validated.
*
* @var array
*/
private $_deferredPathExpressionStacks = array();
/**
2009-06-14 21:34:28 +04:00
* The lexer.
*
* @var Doctrine\ORM\Query\Lexer
*/
private $_lexer;
/**
2009-06-14 21:34:28 +04:00
* The parser result.
*
* @var Doctrine\ORM\Query\ParserResult
*/
private $_parserResult;
2009-06-14 21:34:28 +04:00
/**
* The EntityManager.
*
* @var EnityManager
*/
private $_em;
/**
* The Query to parse.
*
* @var Query
*/
private $_query;
/**
2009-06-14 21:34:28 +04:00
* Map of declared query components in the parsed query.
*
* @var array
*/
private $_queryComponents = array();
2009-06-14 21:34:28 +04:00
private $_sqlTreeWalker;
/**
* Creates a new query parser object.
*
* @param Query $query The Query to parse.
*/
public function __construct(Query $query)
{
$this->_query = $query;
$this->_em = $query->getEntityManager();
$this->_lexer = new Lexer($query->getDql());
$this->_parserResult = new ParserResult;
}
/**
* 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
* @return bool True, if tokens match; false otherwise.
*/
public function match($token)
{
if (is_string($token)) {
$isMatch = ($this->_lexer->lookahead['value'] === $token);
} else {
$isMatch = ($this->_lexer->lookahead['type'] === $token);
}
if ( ! $isMatch) {
$this->syntaxError($this->_lexer->getLiteral($token));
}
$this->_lexer->moveNext();
}
/**
2009-06-14 21:34:28 +04:00
* Free this parser enabling it to be reused
*
* @param boolean $deep Whether to clean peek and reset errors
* @param integer $position Position to reset
*/
public function free($deep = false, $position = 0)
{
// WARNING! Use this method with care. It resets the scanner!
$this->_lexer->resetPosition($position);
// Deep = true cleans peek and also any previously defined errors
if ($deep) {
$this->_lexer->resetPeek();
//$this->_errors = array();
}
$this->_lexer->token = null;
$this->_lexer->lookahead = null;
//$this->_errorDistance = self::MIN_ERROR_DISTANCE;
}
/**
* Parses a query string.
2009-06-14 21:34:28 +04:00
*
* @return ParserResult
*/
public function parse()
{
// Parse & build AST
2009-06-14 21:34:28 +04:00
$AST = $this->QueryLanguage();
// Check for end of string
if ($this->_lexer->lookahead !== null) {
//var_dump($this->_lexer->lookahead);
$this->syntaxError('end of string');
}
// Create SqlWalker who creates the SQL from the AST
2009-06-14 21:34:28 +04:00
$sqlWalker = $this->_sqlTreeWalker ?: new SqlWalker($this->_query, $this->_parserResult, $this->_queryComponents);
// Assign an SQL executor to the parser result
$this->_parserResult->setSqlExecutor(Exec\AbstractExecutor::create($AST, $sqlWalker));
return $this->_parserResult;
}
/**
* Gets the lexer used by the parser.
*
* @return Doctrine\ORM\Query\Lexer
*/
public function getLexer()
{
return $this->_lexer;
}
/**
* Gets the ParserResult that is being filled with information during parsing.
*
* @return Doctrine\ORM\Query\ParserResult
*/
public function getParserResult()
{
return $this->_parserResult;
}
/**
* Generates a new syntax error.
*
* @param string $expected Optional expected string.
* @param array $token Optional token.
*/
public function syntaxError($expected = '', $token = null)
{
if ($token === null) {
$token = $this->_lexer->lookahead;
}
$message = 'line 0, col ' . (isset($token['position']) ? $token['position'] : '-1') . ': Error: ';
if ($expected !== '') {
$message .= "Expected '$expected', got ";
} else {
$message .= 'Unexpected ';
}
if ($this->_lexer->lookahead === null) {
$message .= 'end of string.';
} else {
$message .= "'{$this->_lexer->lookahead['value']}'";
}
throw DoctrineException::updateMe($message);
}
/**
* Generates a new semantical error.
*
* @param string $message Optional message.
* @param array $token Optional token.
*/
public function semanticalError($message = '', $token = null)
{
if ($token === null) {
$token = $this->_lexer->token;
}
2009-07-16 03:20:11 +04:00
//TODO: Include $token in $message
throw DoctrineException::updateMe($message);
}
/**
* Logs new error entry.
*
* @param string $message Message to log.
* @param array $token Token that it was processing.
*/
/*protected function _logError($message = '', $token)
2009-06-14 21:34:28 +04:00
{
2009-07-16 03:20:11 +04:00
if ($this->_errorDistance >= self::MIN_ERROR_DISTANCE) {
$message = 'line 0, col ' . $token['position'] . ': ' . $message;
$this->_errors[] = $message;
}
2009-06-14 21:34:28 +04:00
2009-07-16 03:20:11 +04:00
$this->_errorDistance = 0;
2009-06-14 21:34:28 +04:00
}*/
/**
* Gets the EntityManager used by the parser.
*
* @return EntityManager
*/
public function getEntityManager()
{
return $this->_em;
}
/**
* Checks if the next-next (after lookahead) token starts a function.
*
* @return boolean
*/
private function _isFunction()
{
$next = $this->_lexer->glimpse();
return $next['value'] === '(';
}
/**
* Checks whether the next 2 tokens start a subselect.
*
* @return boolean TRUE if the next 2 tokens start a subselect, FALSE otherwise.
*/
private function _isSubselect()
{
$la = $this->_lexer->lookahead;
$next = $this->_lexer->glimpse();
2009-07-16 03:20:11 +04:00
return ($la['value'] === '(' && $next['type'] === Lexer::T_SELECT);
}
/**
* QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
*/
2009-06-14 21:34:28 +04:00
public function QueryLanguage()
{
$this->_lexer->moveNext();
2009-07-16 03:20:11 +04:00
switch ($this->_lexer->lookahead['type']) {
case Lexer::T_SELECT:
2009-06-14 21:34:28 +04:00
return $this->SelectStatement();
2009-07-16 03:20:11 +04:00
case Lexer::T_UPDATE:
2009-06-14 21:34:28 +04:00
return $this->UpdateStatement();
2009-07-16 03:20:11 +04:00
case Lexer::T_DELETE:
2009-06-14 21:34:28 +04:00
return $this->DeleteStatement();
2009-07-16 03:20:11 +04:00
default:
$this->syntaxError('SELECT, UPDATE or DELETE');
break;
}
}
/**
* SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
*/
2009-06-14 21:34:28 +04:00
public function SelectStatement()
{
$this->_beginDeferredPathExpressionStack();
2009-06-14 21:34:28 +04:00
$selectClause = $this->SelectClause();
$fromClause = $this->FromClause();
$this->_processDeferredPathExpressionStack();
2009-07-16 03:20:11 +04:00
$whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
? $this->WhereClause() : null;
2009-07-16 03:20:11 +04:00
$groupByClause = $this->_lexer->isNextToken(Lexer::T_GROUP)
? $this->GroupByClause() : null;
2009-07-16 03:20:11 +04:00
$havingClause = $this->_lexer->isNextToken(Lexer::T_HAVING)
? $this->HavingClause() : null;
2009-07-16 03:20:11 +04:00
$orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER)
? $this->OrderByClause() : null;
return new AST\SelectStatement(
2009-07-16 03:20:11 +04:00
$selectClause, $fromClause, $whereClause, $groupByClause, $havingClause, $orderByClause
);
}
/**
* Begins a new stack of deferred path expressions.
*/
private function _beginDeferredPathExpressionStack()
{
$this->_deferredPathExpressionStacks[] = array();
}
/**
* Processes the topmost stack of deferred path expressions.
* These will be validated to make sure they are indeed
* valid <tt>StateFieldPathExpression</tt>s and additional information
* is attached to their AST nodes.
*/
private function _processDeferredPathExpressionStack()
{
$exprStack = array_pop($this->_deferredPathExpressionStacks);
$qComps = $this->_queryComponents;
2009-07-16 03:20:11 +04:00
foreach ($exprStack as $expr) {
2009-06-14 21:34:28 +04:00
switch ($expr->getType()) {
case AST\PathExpression::TYPE_STATE_FIELD:
$this->_validateStateFieldPathExpression($expr);
2009-07-16 03:20:11 +04:00
break;
2009-06-14 21:34:28 +04:00
case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
$this->_validateSingleValuedAssociationPathExpression($expr);
2009-07-16 03:20:11 +04:00
break;
2009-06-14 21:34:28 +04:00
case AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION:
$this->_validateCollectionValuedAssociationPathExpression($expr);
2009-07-16 03:20:11 +04:00
break;
2009-06-14 21:34:28 +04:00
default:
$this->semanticalError('Encountered invalid PathExpression.');
2009-07-16 03:20:11 +04:00
break;
}
}
}
/**
* UpdateStatement ::= UpdateClause [WhereClause]
*/
2009-06-14 21:34:28 +04:00
public function UpdateStatement()
{
2009-06-14 21:34:28 +04:00
$updateStatement = new AST\UpdateStatement($this->UpdateClause());
$updateStatement->setWhereClause(
2009-07-16 03:20:11 +04:00
$this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null
2009-06-14 21:34:28 +04:00
);
2009-07-16 03:20:11 +04:00
return $updateStatement;
}
/**
* UpdateClause ::= "UPDATE" AbstractSchemaName [["AS"] AliasIdentificationVariable] "SET" UpdateItem {"," UpdateItem}*
*/
2009-06-14 21:34:28 +04:00
public function UpdateClause()
{
$this->match(Lexer::T_UPDATE);
2009-06-14 21:34:28 +04:00
$abstractSchemaName = $this->AbstractSchemaName();
$aliasIdentificationVariable = null;
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_AS);
}
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
$this->match(Lexer::T_IDENTIFIER);
$aliasIdentificationVariable = $this->_lexer->token['value'];
} else {
$aliasIdentificationVariable = $abstractSchemaName;
}
2009-07-16 03:20:11 +04:00
$this->match(Lexer::T_SET);
$updateItems = array();
2009-06-14 21:34:28 +04:00
$updateItems[] = $this->UpdateItem();
2009-07-16 03:20:11 +04:00
while ($this->_lexer->isNextToken(',')) {
$this->match(',');
2009-06-14 21:34:28 +04:00
$updateItems[] = $this->UpdateItem();
}
$classMetadata = $this->_em->getClassMetadata($abstractSchemaName);
2009-07-16 03:20:11 +04:00
// Building queryComponent
$queryComponent = array(
2009-07-16 03:20:11 +04:00
'metadata' => $classMetadata,
'parent' => null,
'relation' => null,
'map' => null
);
$this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
$updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems);
$updateClause->setAliasIdentificationVariable($aliasIdentificationVariable);
return $updateClause;
}
/**
* UpdateItem ::= [IdentificationVariable "."] {StateField | SingleValuedAssociationField} "=" NewValue
*/
2009-06-14 21:34:28 +04:00
public function UpdateItem()
{
$peek = $this->_lexer->glimpse();
$identVariable = null;
2009-07-16 03:20:11 +04:00
if ($peek['value'] == '.') {
$this->match(Lexer::T_IDENTIFIER);
$identVariable = $this->_lexer->token['value'];
$this->match('.');
} else {
throw QueryException::missingAliasQualifier();
}
2009-07-16 03:20:11 +04:00
$this->match(Lexer::T_IDENTIFIER);
$field = $this->_lexer->token['value'];
$this->match('=');
2009-06-14 21:34:28 +04:00
$newValue = $this->NewValue();
$updateItem = new AST\UpdateItem($field, $newValue);
$updateItem->setIdentificationVariable($identVariable);
return $updateItem;
}
/**
* NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
* EnumPrimary | SimpleEntityExpression | "NULL"
* @todo Implementation still incomplete.
*/
2009-06-14 21:34:28 +04:00
public function NewValue()
{
if ($this->_lexer->isNextToken(Lexer::T_NULL)) {
$this->match(Lexer::T_NULL);
return null;
} else if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
$this->match(Lexer::T_INPUT_PARAMETER);
return new AST\InputParameter($this->_lexer->token['value']);
} else if ($this->_lexer->isNextToken(Lexer::T_STRING)) {
//TODO: Can be StringPrimary or EnumPrimary
2009-06-14 21:34:28 +04:00
return $this->StringPrimary();
} else {
$this->syntaxError('Not yet implemented-1.');
}
}
/**
* DeleteStatement ::= DeleteClause [WhereClause]
*/
2009-06-14 21:34:28 +04:00
public function DeleteStatement()
{
2009-06-14 21:34:28 +04:00
$deleteStatement = new AST\DeleteStatement($this->DeleteClause());
$deleteStatement->setWhereClause(
2009-07-16 03:20:11 +04:00
$this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null
2009-06-14 21:34:28 +04:00
);
2009-07-16 03:20:11 +04:00
return $deleteStatement;
}
/**
* DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName [["AS"] AliasIdentificationVariable]
*/
2009-06-14 21:34:28 +04:00
public function DeleteClause()
{
$this->match(Lexer::T_DELETE);
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_FROM)) {
$this->match(Lexer::T_FROM);
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
$deleteClause = new AST\DeleteClause($this->AbstractSchemaName());
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_AS);
}
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
$this->match(Lexer::T_IDENTIFIER);
$deleteClause->setAliasIdentificationVariable($this->_lexer->token['value']);
} else {
$deleteClause->setAliasIdentificationVariable($deleteClause->getAbstractSchemaName());
}
$classMetadata = $this->_em->getClassMetadata($deleteClause->getAbstractSchemaName());
$queryComponent = array(
2009-07-16 03:20:11 +04:00
'metadata' => $classMetadata
);
$this->_queryComponents[$deleteClause->getAliasIdentificationVariable()] = $queryComponent;
return $deleteClause;
}
/**
* SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
*/
2009-06-14 21:34:28 +04:00
public function SelectClause()
{
$isDistinct = false;
$this->match(Lexer::T_SELECT);
// Check for DISTINCT
if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
$this->match(Lexer::T_DISTINCT);
$isDistinct = true;
}
// Process SelectExpressions (1..N)
$selectExpressions = array();
2009-06-14 21:34:28 +04:00
$selectExpressions[] = $this->SelectExpression();
2009-07-16 03:20:11 +04:00
while ($this->_lexer->isNextToken(',')) {
$this->match(',');
2009-06-14 21:34:28 +04:00
$selectExpressions[] = $this->SelectExpression();
}
return new AST\SelectClause($selectExpressions, $isDistinct);
}
/**
* FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
*/
2009-06-14 21:34:28 +04:00
public function FromClause()
{
$this->match(Lexer::T_FROM);
$identificationVariableDeclarations = array();
2009-06-14 21:34:28 +04:00
$identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
while ($this->_lexer->isNextToken(',')) {
$this->match(',');
2009-06-14 21:34:28 +04:00
$identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
}
return new AST\FromClause($identificationVariableDeclarations);
}
/**
* SelectExpression ::=
* IdentificationVariable | StateFieldPathExpression |
* (AggregateExpression | "(" Subselect ")") [["AS"] FieldAliasIdentificationVariable] |
* Function
*/
2009-06-14 21:34:28 +04:00
public function SelectExpression()
{
$expression = null;
$fieldIdentificationVariable = null;
$peek = $this->_lexer->glimpse();
2009-07-16 03:20:11 +04:00
// First we recognize for an IdentificationVariable (DQL class alias)
if ($peek['value'] != '.' && $peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) {
2009-06-14 21:34:28 +04:00
$expression = $this->IdentificationVariable();
} else if (($isFunction = $this->_isFunction()) !== false || $this->_isSubselect()) {
if ($isFunction) {
2009-06-14 21:34:28 +04:00
if ($this->isAggregateFunction($this->_lexer->lookahead['type'])) {
$expression = $this->AggregateExpression();
} else {
$expression = $this->_Function();
}
} else {
$this->match('(');
2009-06-14 21:34:28 +04:00
$expression = $this->Subselect();
$this->match(')');
}
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_AS);
}
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
$this->match(Lexer::T_IDENTIFIER);
$fieldIdentificationVariable = $this->_lexer->token['value'];
}
} else {
//TODO: If hydration mode is OBJECT throw an exception ("partial object dangerous...")
// unless the doctrine.forcePartialLoad query hint is set
2009-06-14 21:34:28 +04:00
$expression = $this->StateFieldPathExpression();
}
2009-07-16 03:20:11 +04:00
return new AST\SelectExpression($expression, $fieldIdentificationVariable);
}
/**
* IdentificationVariable ::= identifier
*/
2009-06-14 21:34:28 +04:00
public function IdentificationVariable()
{
$this->match(Lexer::T_IDENTIFIER);
2009-07-16 03:20:11 +04:00
return $this->_lexer->token['value'];
}
/**
* IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}*
*/
2009-06-14 21:34:28 +04:00
public function IdentificationVariableDeclaration()
{
2009-06-14 21:34:28 +04:00
$rangeVariableDeclaration = $this->RangeVariableDeclaration();
$indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
$joinVariableDeclarations = array();
2009-07-16 03:20:11 +04:00
while (
2009-07-16 03:20:11 +04:00
$this->_lexer->isNextToken(Lexer::T_LEFT) ||
$this->_lexer->isNextToken(Lexer::T_INNER) ||
$this->_lexer->isNextToken(Lexer::T_JOIN)
) {
2009-06-14 21:34:28 +04:00
$joinVariableDeclarations[] = $this->JoinVariableDeclaration();
}
return new AST\IdentificationVariableDeclaration(
2009-07-16 03:20:11 +04:00
$rangeVariableDeclaration, $indexBy, $joinVariableDeclarations
);
}
/**
* RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
*/
2009-06-14 21:34:28 +04:00
public function RangeVariableDeclaration()
{
2009-06-14 21:34:28 +04:00
$abstractSchemaName = $this->AbstractSchemaName();
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_AS);
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
$aliasIdentificationVariable = $this->AliasIdentificationVariable();
$classMetadata = $this->_em->getClassMetadata($abstractSchemaName);
// Building queryComponent
$queryComponent = array(
2009-07-16 03:20:11 +04:00
'metadata' => $classMetadata,
'parent' => null,
'relation' => null,
'map' => null
);
$this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
return new AST\RangeVariableDeclaration(
2009-07-16 03:20:11 +04:00
$classMetadata, $aliasIdentificationVariable
);
}
/**
* AbstractSchemaName ::= identifier
*/
2009-06-14 21:34:28 +04:00
public function AbstractSchemaName()
{
$this->match(Lexer::T_IDENTIFIER);
2009-07-16 03:20:11 +04:00
return $this->_lexer->token['value'];
}
/**
* AliasIdentificationVariable = identifier
*/
2009-06-14 21:34:28 +04:00
public function AliasIdentificationVariable()
{
$this->match(Lexer::T_IDENTIFIER);
2009-07-16 03:20:11 +04:00
return $this->_lexer->token['value'];
}
/**
* JoinVariableDeclaration ::= Join [IndexBy]
*/
2009-06-14 21:34:28 +04:00
public function JoinVariableDeclaration()
{
2009-06-14 21:34:28 +04:00
$join = $this->Join();
2009-07-16 03:20:11 +04:00
$indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX)
? $this->IndexBy() : null;
return new AST\JoinVariableDeclaration($join, $indexBy);
}
/**
* Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression
* ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression]
*/
2009-06-14 21:34:28 +04:00
public function Join()
{
// Check Join type
$joinType = AST\Join::JOIN_TYPE_INNER;
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_LEFT)) {
$this->match(Lexer::T_LEFT);
2009-07-16 03:20:11 +04:00
// Possible LEFT OUTER join
if ($this->_lexer->isNextToken(Lexer::T_OUTER)) {
$this->match(Lexer::T_OUTER);
$joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
} else {
$joinType = AST\Join::JOIN_TYPE_LEFT;
}
} else if ($this->_lexer->isNextToken(Lexer::T_INNER)) {
$this->match(Lexer::T_INNER);
}
$this->match(Lexer::T_JOIN);
2009-06-14 21:34:28 +04:00
$joinPathExpression = $this->JoinPathExpression();
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_AS);
}
2009-06-14 21:34:28 +04:00
$aliasIdentificationVariable = $this->AliasIdentificationVariable();
// Verify that the association exists.
$parentClass = $this->_queryComponents[$joinPathExpression->getIdentificationVariable()]['metadata'];
$assocField = $joinPathExpression->getAssociationField();
2009-07-16 03:20:11 +04:00
if ( ! $parentClass->hasAssociation($assocField)) {
2009-07-16 03:20:11 +04:00
$this->semanticalError(
"Class " . $parentClass->name . " has no association named '$assocField'."
);
}
2009-07-16 03:20:11 +04:00
$targetClassName = $parentClass->getAssociationMapping($assocField)->getTargetEntityName();
// Building queryComponent
$joinQueryComponent = array(
2009-07-16 03:20:11 +04:00
'metadata' => $this->_em->getClassMetadata($targetClassName),
'parent' => $joinPathExpression->getIdentificationVariable(),
'relation' => $parentClass->getAssociationMapping($assocField),
'map' => null
);
$this->_queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
// Create AST node
$join = new AST\Join($joinType, $joinPathExpression, $aliasIdentificationVariable);
// Check for ad-hoc Join conditions
if ($this->_lexer->isNextToken(Lexer::T_ON) || $this->_lexer->isNextToken(Lexer::T_WITH)) {
if ($this->_lexer->isNextToken(Lexer::T_ON)) {
$this->match(Lexer::T_ON);
$join->setWhereType(AST\Join::JOIN_WHERE_ON);
} else {
$this->match(Lexer::T_WITH);
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
$join->setConditionalExpression($this->ConditionalExpression());
}
return $join;
}
/**
* JoinPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
*/
2009-06-14 21:34:28 +04:00
public function JoinPathExpression()
{
2009-06-14 21:34:28 +04:00
$identificationVariable = $this->IdentificationVariable();
$this->match('.');
$this->match(Lexer::T_IDENTIFIER);
2009-07-16 03:20:11 +04:00
return new AST\JoinPathExpression(
2009-07-16 03:20:11 +04:00
$identificationVariable, $this->_lexer->token['value']
);
}
/**
* IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression
*/
2009-06-14 21:34:28 +04:00
public function IndexBy()
{
$this->match(Lexer::T_INDEX);
$this->match(Lexer::T_BY);
2009-06-14 21:34:28 +04:00
$pathExp = $this->SimpleStateFieldPathExpression();
2009-07-16 03:20:11 +04:00
// Add the INDEX BY info to the query component
2009-06-14 21:34:28 +04:00
$parts = $pathExp->getParts();
$this->_queryComponents[$pathExp->getIdentificationVariable()]['map'] = $parts[0];
2009-07-16 03:20:11 +04:00
return $pathExp;
}
2009-06-14 21:34:28 +04:00
/**
* StateFieldPathExpression ::= SimpleStateFieldPathExpression | SimpleStateFieldAssociationPathExpression
*/
public function StateFieldPathExpression()
{
$pathExpr = $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
if ( ! empty($this->_deferredPathExpressionStacks)) {
$exprStack = array_pop($this->_deferredPathExpressionStacks);
$exprStack[] = $pathExpr;
array_push($this->_deferredPathExpressionStacks, $exprStack);
return $pathExpr;
}
$this->_validateStateFieldPathExpression($pathExpr);
return $pathExpr;
}
/**
* SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField
*/
2009-06-14 21:34:28 +04:00
public function SimpleStateFieldPathExpression()
{
2009-06-14 21:34:28 +04:00
$pathExpr = $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
if (count($pathExpr->getParts()) > 1) {
$this->syntaxError('SimpleStateFieldPathExpression');
}
if ( ! empty($this->_deferredPathExpressionStacks)) {
$exprStack = array_pop($this->_deferredPathExpressionStacks);
$exprStack[] = $pathExpr;
array_push($this->_deferredPathExpressionStacks, $exprStack);
return $pathExpr;
}
$this->_validateStateFieldPathExpression($pathExpr);
return $pathExpr;
}
/**
2009-06-14 21:34:28 +04:00
* SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField
*/
2009-06-14 21:34:28 +04:00
public function SingleValuedAssociationPathExpression()
{
2009-06-14 21:34:28 +04:00
$pathExpr = $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
if ( ! empty($this->_deferredPathExpressionStacks)) {
$exprStack = array_pop($this->_deferredPathExpressionStacks);
2009-06-14 21:34:28 +04:00
$exprStack[] = $pathExpr;
array_push($this->_deferredPathExpressionStacks, $exprStack);
2009-06-14 21:34:28 +04:00
return $pathExpr;
}
2009-06-14 21:34:28 +04:00
$this->_validateSingleValuedAssociationPathExpression($pathExpr);
2009-06-14 21:34:28 +04:00
return $pathExpr;
}
/**
* CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField
*/
public function CollectionValuedPathExpression()
{
$pathExpr = $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
if ( ! empty($this->_deferredPathExpressionStacks)) {
$exprStack = array_pop($this->_deferredPathExpressionStacks);
$exprStack[] = $pathExpr;
array_push($this->_deferredPathExpressionStacks, $exprStack);
return $pathExpr;
}
2009-06-14 21:34:28 +04:00
$this->_validateCollectionValuedPathExpression($pathExpr);
return $pathExpr;
}
/**
* Validates that the given <tt>PathExpression</tt> is a semantically correct
* CollectionValuedPathExpression as specified in the grammar.
*
* @param PathExpression $pathExpression
*/
private function _validateCollectionValuedPathExpression(AST\PathExpression $pathExpression)
{
$class = $this->_queryComponents[$pathExpression->getIdentificationVariable()]['metadata'];
$collectionValuedField = null;
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
foreach ($pathExpression->getParts() as $field) {
if ($collectionValuedField !== null) {
$this->semanticalError('Can not navigate through collection-valued field named ' . $collectionValuedField);
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
if ( ! isset($class->associationMappings[$field])) {
$this->semanticalError('Class ' . $class->name . ' has no association field named ' . $lastItem);
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
if ($class->associationMappings[$field]->isOneToOne()) {
$class = $this->_em->getClassMetadata($class->associationMappings[$field]->targetEntityName);
} else {
$collectionValuedField = $field;
}
}
2009-06-14 21:34:28 +04:00
if ($collectionValuedField === null) {
$this->syntaxError('SingleValuedAssociationField or CollectionValuedAssociationField');
}
}
2009-06-14 21:34:28 +04:00
/**
* Validates that the given <tt>PathExpression</tt> is a semantically correct
* SingleValuedPathExpression as specified in the grammar.
*
* @param PathExpression $pathExpression
*/
private function _validateSingleValuedAssociationPathExpression(AST\PathExpression $pathExpression)
{
$class = $this->_queryComponents[$pathExpression->getIdentificationVariable()]['metadata'];
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
foreach ($pathExpression->getParts() as $field) {
if ( ! isset($class->associationMappings[$field])) {
$this->semanticalError('Class ' . $class->name . ' has no association field named ' . $field);
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
if ($class->associationMappings[$field]->isOneToOne()) {
$class = $this->_em->getClassMetadata($class->associationMappings[$field]->targetEntityName);
} else {
$this->syntaxError('SingleValuedAssociationField');
}
}
2009-06-14 21:34:28 +04:00
}
/**
* Validates that the given <tt>PathExpression</tt> is a semantically correct
* StateFieldPathExpression as specified in the grammar.
*
* @param PathExpression $pathExpression
*/
private function _validateStateFieldPathExpression(AST\PathExpression $pathExpression)
{
$class = $this->_queryComponents[$pathExpression->getIdentificationVariable()]['metadata'];
$stateFieldSeen = false;
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
foreach ($pathExpression->getParts() as $field) {
if ($stateFieldSeen) {
2009-06-14 21:34:28 +04:00
$this->semanticalError('Cannot navigate through state field.');
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
if (isset($class->associationMappings[$field]) && $class->associationMappings[$field]->isOneToOne()) {
$class = $this->_em->getClassMetadata($class->associationMappings[$field]->targetEntityName);
} else if (isset($class->fieldMappings[$field])) {
$stateFieldSeen = true;
} else {
2009-06-14 21:34:28 +04:00
$this->semanticalError('SingleValuedAssociationField or StateField expected.');
}
}
2009-06-14 21:34:28 +04:00
if ( ! $stateFieldSeen) {
$this->semanticalError('Invalid StateFieldPathExpression. Must end in a state field.');
}
}
2009-06-14 21:34:28 +04:00
/**
* Parses an arbitrary path expression without any semantic validation.
*
* PathExpression ::= IdentificationVariable "." {identifier "."}* identifier
*
2009-07-16 03:20:11 +04:00
* @todo Refactor. We should never use break in a loop! This should use a do { ... } while (...) instead
2009-06-14 21:34:28 +04:00
* @return PathExpression
*/
public function PathExpression($type)
{
$this->match(Lexer::T_IDENTIFIER);
$identificationVariable = $this->_lexer->token['value'];
$this->match('.');
$parts = array();
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
while ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
$this->match(Lexer::T_IDENTIFIER);
$parts[] = $this->_lexer->token['value'];
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
if ($this->_lexer->isNextToken('.')) {
$this->match('.');
} else {
break;
}
}
2009-06-14 21:34:28 +04:00
return new AST\PathExpression($type, $identificationVariable, $parts);
}
/**
* EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
* SimpleEntityExpression ::= IdentificationVariable | InputParameter
*/
public function EntityExpression()
{
}
/**
* NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL"
2009-07-16 03:20:11 +04:00
*
* @todo Implementation incomplete for SingleValuedPathExpression.
*/
2009-06-14 21:34:28 +04:00
public function NullComparisonExpression()
{
if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
$this->match(Lexer::T_INPUT_PARAMETER);
$expr = new AST\InputParameter($this->_lexer->token['value']);
} else {
//TODO: Support SingleValuedAssociationPathExpression
2009-06-14 21:34:28 +04:00
$expr = $this->StateFieldPathExpression();
}
2009-07-16 03:20:11 +04:00
$nullCompExpr = new AST\NullComparisonExpression($expr);
$this->match(Lexer::T_IS);
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
$this->match(Lexer::T_NOT);
$nullCompExpr->setNot(true);
}
2009-07-16 03:20:11 +04:00
$this->match(Lexer::T_NULL);
2009-06-14 21:34:28 +04:00
return $nullCompExpr;
}
/**
* AggregateExpression ::=
* ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" |
* "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedAssociationPathExpression | StateFieldPathExpression) ")"
2009-07-16 03:20:11 +04:00
*
* @todo Implementation incomplete. Support for SingleValuedAssociationPathExpression.
*/
2009-06-14 21:34:28 +04:00
public function AggregateExpression()
{
$isDistinct = false;
$functionName = '';
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_COUNT)) {
$this->match(Lexer::T_COUNT);
$functionName = $this->_lexer->token['value'];
$this->match('(');
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
$this->match(Lexer::T_DISTINCT);
$isDistinct = true;
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
//TODO: Support SingleValuedAssociationPathExpression
$pathExp = $this->StateFieldPathExpression();
$this->match(')');
} else {
if ($this->_lexer->isNextToken(Lexer::T_AVG)) {
$this->match(Lexer::T_AVG);
} else if ($this->_lexer->isNextToken(Lexer::T_MAX)) {
$this->match(Lexer::T_MAX);
} else if ($this->_lexer->isNextToken(Lexer::T_MIN)) {
$this->match(Lexer::T_MIN);
} else if ($this->_lexer->isNextToken(Lexer::T_SUM)) {
$this->match(Lexer::T_SUM);
} else {
$this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
}
2009-07-16 03:20:11 +04:00
$functionName = $this->_lexer->token['value'];
$this->match('(');
2009-06-14 21:34:28 +04:00
$pathExp = $this->StateFieldPathExpression();
$this->match(')');
}
2009-06-14 21:34:28 +04:00
return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
}
/**
* GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
* GroupByItem ::= IdentificationVariable | SingleValuedPathExpression
* @todo Implementation incomplete for GroupByItem.
*/
2009-06-14 21:34:28 +04:00
public function GroupByClause()
{
$this->match(Lexer::T_GROUP);
$this->match(Lexer::T_BY);
2009-07-16 03:20:11 +04:00
$groupByItems = array();
2009-06-14 21:34:28 +04:00
$groupByItems[] = $this->StateFieldPathExpression();
2009-07-16 03:20:11 +04:00
while ($this->_lexer->isNextToken(',')) {
$this->match(',');
2009-06-14 21:34:28 +04:00
$groupByItems[] = $this->StateFieldPathExpression();
}
2009-07-16 03:20:11 +04:00
return new AST\GroupByClause($groupByItems);
}
/**
* HavingClause ::= "HAVING" ConditionalExpression
*/
2009-06-14 21:34:28 +04:00
public function HavingClause()
{
$this->match(Lexer::T_HAVING);
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
return new AST\HavingClause($this->ConditionalExpression());
}
/**
* OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
*/
2009-06-14 21:34:28 +04:00
public function OrderByClause()
{
$this->match(Lexer::T_ORDER);
$this->match(Lexer::T_BY);
2009-07-16 03:20:11 +04:00
$orderByItems = array();
2009-06-14 21:34:28 +04:00
$orderByItems[] = $this->OrderByItem();
2009-07-16 03:20:11 +04:00
while ($this->_lexer->isNextToken(',')) {
$this->match(',');
2009-06-14 21:34:28 +04:00
$orderByItems[] = $this->OrderByItem();
}
2009-07-16 03:20:11 +04:00
return new AST\OrderByClause($orderByItems);
}
/**
* OrderByItem ::= ResultVariable | StateFieldPathExpression ["ASC" | "DESC"]
2009-07-16 03:20:11 +04:00
*
* @todo Implementation incomplete for OrderByItem.
*/
2009-06-14 21:34:28 +04:00
public function OrderByItem()
{
2009-06-14 21:34:28 +04:00
$item = new AST\OrderByItem($this->StateFieldPathExpression());
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_ASC)) {
$this->match(Lexer::T_ASC);
$item->setAsc(true);
} else if ($this->_lexer->isNextToken(Lexer::T_DESC)) {
$this->match(Lexer::T_DESC);
$item->setDesc(true);
} else {
$item->setDesc(true);
}
2009-07-16 03:20:11 +04:00
return $item;
}
/**
* WhereClause ::= "WHERE" ConditionalExpression
*/
2009-06-14 21:34:28 +04:00
public function WhereClause()
{
$this->match(Lexer::T_WHERE);
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
return new AST\WhereClause($this->ConditionalExpression());
}
/**
* ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
*/
2009-06-14 21:34:28 +04:00
public function ConditionalExpression()
{
$conditionalTerms = array();
2009-06-14 21:34:28 +04:00
$conditionalTerms[] = $this->ConditionalTerm();
2009-07-16 03:20:11 +04:00
while ($this->_lexer->isNextToken(Lexer::T_OR)) {
$this->match(Lexer::T_OR);
2009-06-14 21:34:28 +04:00
$conditionalTerms[] = $this->ConditionalTerm();
}
2009-07-16 03:20:11 +04:00
return new AST\ConditionalExpression($conditionalTerms);
}
/**
* ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
*/
2009-06-14 21:34:28 +04:00
public function ConditionalTerm()
{
$conditionalFactors = array();
2009-06-14 21:34:28 +04:00
$conditionalFactors[] = $this->ConditionalFactor();
2009-07-16 03:20:11 +04:00
while ($this->_lexer->isNextToken(Lexer::T_AND)) {
$this->match(Lexer::T_AND);
2009-06-14 21:34:28 +04:00
$conditionalFactors[] = $this->ConditionalFactor();
}
2009-07-16 03:20:11 +04:00
return new AST\ConditionalTerm($conditionalFactors);
}
/**
* ConditionalFactor ::= ["NOT"] ConditionalPrimary
*/
2009-06-14 21:34:28 +04:00
public function ConditionalFactor()
{
$not = false;
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
$this->match(Lexer::T_NOT);
$not = true;
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
return new AST\ConditionalFactor($this->ConditionalPrimary(), $not);
}
/**
* ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
* @todo Implementation incomplete: Recognition of SimpleConditionalExpression is incomplete.
*/
2009-06-14 21:34:28 +04:00
public function ConditionalPrimary()
{
$condPrimary = new AST\ConditionalPrimary;
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken('(')) {
// Peek beyond the matching closing paranthesis ')'
$numUnmatched = 1;
$peek = $this->_lexer->peek();
2009-07-16 03:20:11 +04:00
while ($numUnmatched > 0) {
if ($peek['value'] == ')') {
--$numUnmatched;
} else if ($peek['value'] == '(') {
++$numUnmatched;
}
2009-07-16 03:20:11 +04:00
$peek = $this->_lexer->peek();
}
2009-07-16 03:20:11 +04:00
$this->_lexer->resetPeek();
//TODO: This is not complete, what about LIKE/BETWEEN/...etc?
$comparisonOps = array("=", "<", "<=", "<>", ">", ">=", "!=");
if (in_array($peek['value'], $comparisonOps)) {
2009-06-14 21:34:28 +04:00
$condPrimary->setSimpleConditionalExpression($this->SimpleConditionalExpression());
} else {
$this->match('(');
2009-06-14 21:34:28 +04:00
$conditionalExpression = $this->ConditionalExpression();
$this->match(')');
$condPrimary->setConditionalExpression($conditionalExpression);
}
} else {
2009-06-14 21:34:28 +04:00
$condPrimary->setSimpleConditionalExpression($this->SimpleConditionalExpression());
}
2009-06-14 21:34:28 +04:00
return $condPrimary;
}
/**
* SimpleConditionalExpression ::=
* ComparisonExpression | BetweenExpression | LikeExpression |
* InExpression | NullComparisonExpression | ExistsExpression |
* EmptyCollectionComparisonExpression | CollectionMemberExpression
*/
2009-06-14 21:34:28 +04:00
public function SimpleConditionalExpression()
{
if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
$token = $this->_lexer->glimpse();
} else {
$token = $this->_lexer->lookahead;
}
2009-07-16 03:20:11 +04:00
if ($token['type'] === Lexer::T_EXISTS) {
2009-06-14 21:34:28 +04:00
return $this->ExistsExpression();
}
2009-06-14 21:34:28 +04:00
$pathExprOrInputParam = false;
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER) {
// Peek beyond the PathExpression
2009-06-14 21:34:28 +04:00
$pathExprOrInputParam = true;
$peek = $this->_lexer->peek();
2009-07-16 03:20:11 +04:00
while ($peek['value'] === '.') {
$this->_lexer->peek();
$peek = $this->_lexer->peek();
}
// Also peek beyond a NOT if there is one
if ($peek['type'] === Lexer::T_NOT) {
$peek = $this->_lexer->peek();
}
$this->_lexer->resetPeek();
$token = $peek;
}
2009-06-14 21:34:28 +04:00
if ($pathExprOrInputParam) {
switch ($token['type']) {
2009-06-14 21:34:28 +04:00
case Lexer::T_NONE:
return $this->ComparisonExpression();
2009-07-16 03:20:11 +04:00
case Lexer::T_BETWEEN:
2009-06-14 21:34:28 +04:00
return $this->BetweenExpression();
2009-07-16 03:20:11 +04:00
case Lexer::T_LIKE:
2009-06-14 21:34:28 +04:00
return $this->LikeExpression();
2009-07-16 03:20:11 +04:00
case Lexer::T_IN:
2009-06-14 21:34:28 +04:00
return $this->InExpression();
2009-07-16 03:20:11 +04:00
case Lexer::T_IS:
2009-06-14 21:34:28 +04:00
return $this->NullComparisonExpression();
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
case Lexer::T_MEMBER:
return $this->CollectionMemberExpression();
2009-07-16 03:20:11 +04:00
default:
$this->syntaxError();
}
} else {
2009-06-14 21:34:28 +04:00
return $this->ComparisonExpression();
}
}
2009-06-14 21:34:28 +04:00
/**
2009-06-14 21:34:28 +04:00
* CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
*
2009-06-14 21:34:28 +04:00
* EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
* SimpleEntityExpression ::= IdentificationVariable | InputParameter
*
* @return AST\CollectionMemberExpression
* @todo Support SingleValuedAssociationPathExpression and IdentificationVariable
*/
2009-06-14 21:34:28 +04:00
public function CollectionMemberExpression()
{
2009-06-14 21:34:28 +04:00
$isNot = false;
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
if ($this->_lexer->lookahead['type'] == Lexer::T_INPUT_PARAMETER) {
$this->match($this->_lexer->lookahead['value']);
$entityExpr = new AST\InputParameter($this->_lexer->token['value']);
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
$isNot = true;
$this->match(Lexer::T_NOT);
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
$this->match(Lexer::T_MEMBER);
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
if ($this->_lexer->isNextToken(Lexer::T_OF)) {
$this->match(Lexer::T_OF);
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
$collValuedPathExpr = $this->CollectionValuedPathExpression();
} else {
2009-06-14 21:34:28 +04:00
throw QueryException::notImplemented();
}
return new AST\CollectionMemberExpression($entityExpr, $collValuedPathExpr, $isNot);
}
/**
* ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
*
* @return AST\ComparisonExpression
* @todo Semantical checks whether $leftExpr $operator and $rightExpr are compatible.
*/
public function ComparisonExpression()
{
$peek = $this->_lexer->glimpse();
$leftExpr = $this->ArithmeticExpression();
$operator = $this->ComparisonOperator();
if ($this->_isNextAllAnySome()) {
$rightExpr = $this->QuantifiedExpression();
} else {
$rightExpr = $this->ArithmeticExpression();
}
return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
}
2009-06-14 21:34:28 +04:00
/**
* Checks whether the current lookahead token of the lexer has the type
* T_ALL, T_ANY or T_SOME.
2009-06-14 21:34:28 +04:00
*
* @return boolean
*/
private function _isNextAllAnySome()
{
return $this->_lexer->lookahead['type'] === Lexer::T_ALL ||
2009-07-16 03:20:11 +04:00
$this->_lexer->lookahead['type'] === Lexer::T_ANY ||
$this->_lexer->lookahead['type'] === Lexer::T_SOME;
}
/**
* Function ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
*/
public function _Function()
{
$funcName = $this->_lexer->lookahead['value'];
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
if ($this->isStringFunction($funcName)) {
return $this->FunctionsReturningStrings();
} else if ($this->isNumericFunction($funcName)) {
return $this->FunctionsReturningNumerics();
} else if ($this->isDatetimeFunction($funcName)) {
return $this->FunctionsReturningDatetime();
}
2009-07-16 03:20:11 +04:00
$this->syntaxError('Known function.');
}
/**
* Checks whether the function with the given name is a string function
* (a function that returns strings).
*/
2009-06-14 21:34:28 +04:00
public function isStringFunction($funcName)
{
return isset(self::$_STRING_FUNCTIONS[strtolower($funcName)]);
}
/**
* Checks whether the function with the given name is a numeric function
* (a function that returns numerics).
*/
2009-06-14 21:34:28 +04:00
public function isNumericFunction($funcName)
{
return isset(self::$_NUMERIC_FUNCTIONS[strtolower($funcName)]);
}
/**
* Checks whether the function with the given name is a datetime function
* (a function that returns date/time values).
*/
2009-06-14 21:34:28 +04:00
public function isDatetimeFunction($funcName)
{
return isset(self::$_DATETIME_FUNCTIONS[strtolower($funcName)]);
}
/**
* Peeks beyond the specified token and returns the first token after that one.
*/
private function _peekBeyond($token)
{
$peek = $this->_lexer->peek();
2009-07-16 03:20:11 +04:00
while ($peek['value'] != $token) {
$peek = $this->_lexer->peek();
}
2009-07-16 03:20:11 +04:00
$peek = $this->_lexer->peek();
$this->_lexer->resetPeek();
2009-06-14 21:34:28 +04:00
return $peek;
}
/**
* Checks whether the given token is a comparison operator.
*/
2009-06-14 21:34:28 +04:00
public function isComparisonOperator($token)
{
$value = $token['value'];
2009-07-16 03:20:11 +04:00
return $value == '=' || $value == '<' ||
$value == '<=' || $value == '<>' ||
$value == '>' || $value == '>=' ||
$value == '!=';
}
/**
* ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
*/
2009-06-14 21:34:28 +04:00
public function ArithmeticExpression()
{
$expr = new AST\ArithmeticExpression;
2009-07-16 03:20:11 +04:00
if ($this->_lexer->lookahead['value'] === '(') {
$peek = $this->_lexer->glimpse();
2009-07-16 03:20:11 +04:00
if ($peek['type'] === Lexer::T_SELECT) {
$this->match('(');
2009-06-14 21:34:28 +04:00
$expr->setSubselect($this->Subselect());
$this->match(')');
2009-07-16 03:20:11 +04:00
return $expr;
}
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
$expr->setSimpleArithmeticExpression($this->SimpleArithmeticExpression());
2009-07-16 03:20:11 +04:00
return $expr;
}
/**
* SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
*/
2009-06-14 21:34:28 +04:00
public function SimpleArithmeticExpression()
{
$terms = array();
2009-06-14 21:34:28 +04:00
$terms[] = $this->ArithmeticTerm();
2009-07-16 03:20:11 +04:00
while ($this->_lexer->lookahead['value'] == '+' || $this->_lexer->lookahead['value'] == '-') {
if ($this->_lexer->lookahead['value'] == '+') {
$this->match('+');
} else {
$this->match('-');
}
2009-07-16 03:20:11 +04:00
$terms[] = $this->_lexer->token['value'];
2009-06-14 21:34:28 +04:00
$terms[] = $this->ArithmeticTerm();
}
2009-07-16 03:20:11 +04:00
return new AST\SimpleArithmeticExpression($terms);
}
/**
* ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
*/
2009-06-14 21:34:28 +04:00
public function ArithmeticTerm()
{
$factors = array();
2009-06-14 21:34:28 +04:00
$factors[] = $this->ArithmeticFactor();
2009-07-16 03:20:11 +04:00
while ($this->_lexer->lookahead['value'] == '*' || $this->_lexer->lookahead['value'] == '/') {
if ($this->_lexer->lookahead['value'] == '*') {
$this->match('*');
} else {
$this->match('/');
}
2009-07-16 03:20:11 +04:00
$factors[] = $this->_lexer->token['value'];
2009-06-14 21:34:28 +04:00
$factors[] = $this->ArithmeticFactor();
}
2009-07-16 03:20:11 +04:00
return new AST\ArithmeticTerm($factors);
}
/**
* ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
*/
2009-06-14 21:34:28 +04:00
public function ArithmeticFactor()
{
$pSign = $nSign = false;
2009-07-16 03:20:11 +04:00
if ($this->_lexer->lookahead['value'] == '+') {
$this->match('+');
$pSign = true;
} else if ($this->_lexer->lookahead['value'] == '-') {
$this->match('-');
$nSign = true;
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
return new AST\ArithmeticFactor($this->ArithmeticPrimary(), $pSign, $nSign);
}
/**
* InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (Literal {"," Literal}* | Subselect) ")"
*/
2009-06-14 21:34:28 +04:00
public function InExpression()
{
2009-06-14 21:34:28 +04:00
$inExpression = new AST\InExpression($this->StateFieldPathExpression());
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
$this->match(Lexer::T_NOT);
$inExpression->setNot(true);
}
2009-07-16 03:20:11 +04:00
$this->match(Lexer::T_IN);
$this->match('(');
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_SELECT)) {
2009-06-14 21:34:28 +04:00
$inExpression->setSubselect($this->Subselect());
} else {
$literals = array();
2009-06-14 21:34:28 +04:00
$literals[] = $this->Literal();
2009-07-16 03:20:11 +04:00
while ($this->_lexer->isNextToken(',')) {
$this->match(',');
2009-06-14 21:34:28 +04:00
$literals[] = $this->Literal();
}
2009-07-16 03:20:11 +04:00
$inExpression->setLiterals($literals);
}
2009-07-16 03:20:11 +04:00
$this->match(')');
return $inExpression;
}
/**
* ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
*/
2009-06-14 21:34:28 +04:00
public function ExistsExpression()
{
$not = false;
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
$this->match(Lexer::T_NOT);
$not = true;
}
2009-07-16 03:20:11 +04:00
$this->match(Lexer::T_EXISTS);
$this->match('(');
2009-06-14 21:34:28 +04:00
$existsExpression = new AST\ExistsExpression($this->Subselect());
$this->match(')');
$existsExpression->setNot($not);
2009-06-14 21:34:28 +04:00
return $existsExpression;
}
/**
* QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
*/
2009-06-14 21:34:28 +04:00
public function QuantifiedExpression()
{
$all = $any = $some = false;
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_ALL)) {
$this->match(Lexer::T_ALL);
$all = true;
} else if ($this->_lexer->isNextToken(Lexer::T_ANY)) {
$this->match(Lexer::T_ANY);
$any = true;
} else if ($this->_lexer->isNextToken(Lexer::T_SOME)) {
$this->match(Lexer::T_SOME);
$some = true;
} else {
$this->syntaxError('ALL, ANY or SOME');
}
2009-07-16 03:20:11 +04:00
$this->match('(');
2009-06-14 21:34:28 +04:00
$qExpr = new AST\QuantifiedExpression($this->Subselect());
$this->match(')');
2009-07-16 03:20:11 +04:00
$qExpr->setAll($all);
$qExpr->setAny($any);
$qExpr->setSome($some);
2009-06-14 21:34:28 +04:00
return $qExpr;
}
/**
* Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
*/
2009-06-14 21:34:28 +04:00
public function Subselect()
{
$this->_beginDeferredPathExpressionStack();
2009-06-14 21:34:28 +04:00
$subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
$this->_processDeferredPathExpressionStack();
$subselect->setWhereClause(
2009-07-16 03:20:11 +04:00
$this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null
2009-06-14 21:34:28 +04:00
);
$subselect->setGroupByClause(
2009-07-16 03:20:11 +04:00
$this->_lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null
2009-06-14 21:34:28 +04:00
);
$subselect->setHavingClause(
2009-07-16 03:20:11 +04:00
$this->_lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null
2009-06-14 21:34:28 +04:00
);
$subselect->setOrderByClause(
2009-07-16 03:20:11 +04:00
$this->_lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null
2009-06-14 21:34:28 +04:00
);
return $subselect;
}
/**
* SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
*/
2009-06-14 21:34:28 +04:00
public function SimpleSelectClause()
{
$distinct = false;
$this->match(Lexer::T_SELECT);
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
$this->match(Lexer::T_DISTINCT);
$distinct = true;
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
$simpleSelectClause = new AST\SimpleSelectClause($this->SimpleSelectExpression());
$simpleSelectClause->setDistinct($distinct);
2009-06-14 21:34:28 +04:00
return $simpleSelectClause;
}
/**
* SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
*/
2009-06-14 21:34:28 +04:00
public function SubselectFromClause()
{
$this->match(Lexer::T_FROM);
$identificationVariables = array();
2009-06-14 21:34:28 +04:00
$identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
2009-07-16 03:20:11 +04:00
while ($this->_lexer->isNextToken(',')) {
$this->match(',');
2009-06-14 21:34:28 +04:00
$identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
}
2009-06-14 21:34:28 +04:00
return new AST\SubselectFromClause($identificationVariables);
}
/**
* SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
*/
2009-06-14 21:34:28 +04:00
public function SubselectIdentificationVariableDeclaration()
{
$peek = $this->_lexer->glimpse();
2009-07-16 03:20:11 +04:00
if ($peek['value'] == '.') {
$subselectIdentificationVarDecl = new AST\SubselectIdentificationVariableDeclaration;
2009-06-14 21:34:28 +04:00
$subselectIdentificationVarDecl->setAssociationPathExpression($this->AssociationPathExpression());
$this->match(Lexer::T_AS);
$this->match(Lexer::T_IDENTIFIER);
$subselectIdentificationVarDecl->setAliasIdentificationVariable($this->_lexer->token['value']);
2009-07-16 03:20:11 +04:00
return $subselectIdentificationVarDecl;
}
2009-07-16 03:20:11 +04:00
return $this->IdentificationVariableDeclaration();
}
/**
* SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable | (AggregateExpression [["AS"] FieldAliasIdentificationVariable])
*/
2009-06-14 21:34:28 +04:00
public function SimpleSelectExpression()
{
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
// SingleValuedPathExpression | IdentificationVariable
$peek = $this->_lexer->glimpse();
2009-07-16 03:20:11 +04:00
if ($peek['value'] == '.') {
2009-06-14 21:34:28 +04:00
return new AST\SimpleSelectExpression($this->StateFieldPathExpression());
}
2009-07-16 03:20:11 +04:00
$this->match($this->_lexer->lookahead['value']);
return new AST\SimpleSelectExpression($this->_lexer->token['value']);
} else {
2009-06-14 21:34:28 +04:00
$expr = new AST\SimpleSelectExpression($this->AggregateExpression());
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_AS);
}
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
$this->match(Lexer::T_IDENTIFIER);
$expr->setFieldIdentificationVariable($this->_lexer->token['value']);
}
2009-07-16 03:20:11 +04:00
return $expr;
}
}
/**
* Literal ::= string | char | integer | float | boolean | InputParameter
2009-06-14 21:34:28 +04:00
*
* @todo Rip out InputParameter. Thats not a literal.
*/
2009-06-14 21:34:28 +04:00
public function Literal()
{
switch ($this->_lexer->lookahead['type']) {
case Lexer::T_INPUT_PARAMETER:
$this->match($this->_lexer->lookahead['value']);
2009-07-16 03:20:11 +04:00
return new AST\InputParameter($this->_lexer->token['value']);
2009-07-16 03:20:11 +04:00
case Lexer::T_STRING:
case Lexer::T_INTEGER:
case Lexer::T_FLOAT:
$this->match($this->_lexer->lookahead['value']);
2009-07-16 03:20:11 +04:00
return $this->_lexer->token['value'];
2009-07-16 03:20:11 +04:00
default:
$this->syntaxError('Literal');
}
}
/**
* BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
*/
2009-06-14 21:34:28 +04:00
public function BetweenExpression()
{
$not = false;
2009-06-14 21:34:28 +04:00
$arithExpr1 = $this->ArithmeticExpression();
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
$this->match(Lexer::T_NOT);
$not = true;
}
2009-07-16 03:20:11 +04:00
$this->match(Lexer::T_BETWEEN);
2009-06-14 21:34:28 +04:00
$arithExpr2 = $this->ArithmeticExpression();
$this->match(Lexer::T_AND);
2009-06-14 21:34:28 +04:00
$arithExpr3 = $this->ArithmeticExpression();
$betweenExpr = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3);
$betweenExpr->setNot($not);
return $betweenExpr;
}
/**
2009-06-14 21:34:28 +04:00
* ArithmeticPrimary ::= StateFieldPathExpression | Literal | "(" SimpleArithmeticExpression ")"
* | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
* | FunctionsReturningDatetime | IdentificationVariable | SingleValuedAssociationPathExpression
*
* @todo IdentificationVariable | SingleValuedAssociationPathExpression
*/
2009-06-14 21:34:28 +04:00
public function ArithmeticPrimary()
{
if ($this->_lexer->lookahead['value'] === '(') {
$this->match('(');
2009-06-14 21:34:28 +04:00
$expr = $this->SimpleArithmeticExpression();
$this->match(')');
2009-07-16 03:20:11 +04:00
return $expr;
}
switch ($this->_lexer->lookahead['type']) {
case Lexer::T_IDENTIFIER:
$peek = $this->_lexer->glimpse();
2009-07-16 03:20:11 +04:00
if ($peek['value'] == '(') {
2009-06-14 21:34:28 +04:00
return $this->_Function();
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
if ($peek['value'] == '.') {
//TODO: SingleValuedAssociationPathExpression
return $this->StateFieldPathExpression();
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
$identificationVariable = $this->_lexer->lookahead['value'];
$this->match($identificationVariable);
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
return $identificationVariable;
2009-07-16 03:20:11 +04:00
case Lexer::T_INPUT_PARAMETER:
$this->match($this->_lexer->lookahead['value']);
2009-07-16 03:20:11 +04:00
return new AST\InputParameter($this->_lexer->token['value']);
2009-07-16 03:20:11 +04:00
case Lexer::T_STRING:
case Lexer::T_INTEGER:
case Lexer::T_FLOAT:
$this->match($this->_lexer->lookahead['value']);
2009-07-16 03:20:11 +04:00
return $this->_lexer->token['value'];
2009-07-16 03:20:11 +04:00
default:
$peek = $this->_lexer->glimpse();
2009-07-16 03:20:11 +04:00
if ($peek['value'] == '(') {
2009-06-14 21:34:28 +04:00
if ($this->isAggregateFunction($this->_lexer->lookahead['type'])) {
return $this->AggregateExpression();
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
return $this->FunctionsReturningStrings();
} else {
$this->syntaxError();
}
}
}
/**
* FunctionsReturningStrings ::=
* "CONCAT" "(" StringPrimary "," StringPrimary ")" |
* "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
* "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
* "LOWER" "(" StringPrimary ")" |
* "UPPER" "(" StringPrimary ")"
*/
2009-06-14 21:34:28 +04:00
public function FunctionsReturningStrings()
{
$funcNameLower = strtolower($this->_lexer->lookahead['value']);
$funcClass = self::$_STRING_FUNCTIONS[$funcNameLower];
$function = new $funcClass($funcNameLower);
$function->parse($this);
2009-07-16 03:20:11 +04:00
return $function;
}
/**
* FunctionsReturningNumerics ::=
* "LENGTH" "(" StringPrimary ")" |
* "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
* "ABS" "(" SimpleArithmeticExpression ")" |
* "SQRT" "(" SimpleArithmeticExpression ")" |
* "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
* "SIZE" "(" CollectionValuedPathExpression ")"
*/
2009-06-14 21:34:28 +04:00
public function FunctionsReturningNumerics()
{
$funcNameLower = strtolower($this->_lexer->lookahead['value']);
$funcClass = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
$function = new $funcClass($funcNameLower);
$function->parse($this);
2009-07-16 03:20:11 +04:00
return $function;
}
/**
* FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP"
*/
2009-06-14 21:34:28 +04:00
public function FunctionsReturningDatetime()
{
$funcNameLower = strtolower($this->_lexer->lookahead['value']);
$funcClass = self::$_DATETIME_FUNCTIONS[$funcNameLower];
$function = new $funcClass($funcNameLower);
$function->parse($this);
2009-07-16 03:20:11 +04:00
return $function;
}
/**
* Checks whether the given token type indicates an aggregate function.
*/
2009-06-14 21:34:28 +04:00
public function isAggregateFunction($tokenType)
{
return $tokenType == Lexer::T_AVG || $tokenType == Lexer::T_MIN ||
2009-07-16 03:20:11 +04:00
$tokenType == Lexer::T_MAX || $tokenType == Lexer::T_SUM ||
$tokenType == Lexer::T_COUNT;
}
/**
* ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
*/
2009-06-14 21:34:28 +04:00
public function ComparisonOperator()
{
switch ($this->_lexer->lookahead['value']) {
case '=':
$this->match('=');
2009-07-16 03:20:11 +04:00
return '=';
2009-07-16 03:20:11 +04:00
case '<':
$this->match('<');
$operator = '<';
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken('=')) {
$this->match('=');
$operator .= '=';
} else if ($this->_lexer->isNextToken('>')) {
$this->match('>');
$operator .= '>';
}
2009-07-16 03:20:11 +04:00
return $operator;
2009-07-16 03:20:11 +04:00
case '>':
$this->match('>');
$operator = '>';
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken('=')) {
$this->match('=');
$operator .= '=';
}
2009-07-16 03:20:11 +04:00
return $operator;
2009-07-16 03:20:11 +04:00
case '!':
$this->match('!');
$this->match('=');
2009-07-16 03:20:11 +04:00
return '<>';
2009-07-16 03:20:11 +04:00
default:
$this->syntaxError('=, <, <=, <>, >, >=, !=');
}
}
/**
* LikeExpression ::= StringExpression ["NOT"] "LIKE" (string | input_parameter) ["ESCAPE" char]
*/
2009-06-14 21:34:28 +04:00
public function LikeExpression()
{
2009-06-14 21:34:28 +04:00
$stringExpr = $this->StringExpression();
$isNot = false;
2009-07-16 03:20:11 +04:00
if ($this->_lexer->lookahead['type'] === Lexer::T_NOT) {
$this->match(Lexer::T_NOT);
$isNot = true;
}
2009-07-16 03:20:11 +04:00
$this->match(Lexer::T_LIKE);
2009-07-16 03:20:11 +04:00
if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
$this->match(Lexer::T_INPUT_PARAMETER);
$stringPattern = new AST\InputParameter($this->_lexer->token['value']);
} else {
$this->match(Lexer::T_STRING);
$stringPattern = $this->_lexer->token['value'];
}
2009-07-16 03:20:11 +04:00
$escapeChar = null;
2009-07-16 03:20:11 +04:00
if ($this->_lexer->lookahead['type'] === Lexer::T_ESCAPE) {
$this->match(Lexer::T_ESCAPE);
$this->match(Lexer::T_STRING);
$escapeChar = $this->_lexer->token['value'];
}
2009-06-14 21:34:28 +04:00
return new AST\LikeExpression($stringExpr, $stringPattern, $isNot, $escapeChar);
}
/**
* StringExpression ::= StringPrimary | "(" Subselect ")"
*/
2009-06-14 21:34:28 +04:00
public function StringExpression()
{
if ($this->_lexer->lookahead['value'] === '(') {
$peek = $this->_lexer->glimpse();
2009-07-16 03:20:11 +04:00
if ($peek['type'] === Lexer::T_SELECT) {
$this->match('(');
2009-06-14 21:34:28 +04:00
$expr = $this->Subselect();
$this->match(')');
2009-07-16 03:20:11 +04:00
return $expr;
}
}
2009-07-16 03:20:11 +04:00
2009-06-14 21:34:28 +04:00
return $this->StringPrimary();
}
/**
* StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression
*/
2009-06-14 21:34:28 +04:00
public function StringPrimary()
{
if ($this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) {
$peek = $this->_lexer->glimpse();
2009-07-16 03:20:11 +04:00
if ($peek['value'] == '.') {
2009-06-14 21:34:28 +04:00
return $this->StateFieldPathExpression();
} else if ($peek['value'] == '(') {
2009-06-14 21:34:28 +04:00
return $this->FunctionsReturningStrings();
} else {
$this->syntaxError("'.' or '('");
}
} else if ($this->_lexer->lookahead['type'] === Lexer::T_STRING) {
$this->match(Lexer::T_STRING);
2009-07-16 03:20:11 +04:00
return $this->_lexer->token['value'];
} else if ($this->_lexer->lookahead['type'] === Lexer::T_INPUT_PARAMETER) {
$this->match(Lexer::T_INPUT_PARAMETER);
2009-07-16 03:20:11 +04:00
return new AST\InputParameter($this->_lexer->token['value']);
2009-06-14 21:34:28 +04:00
} else if ($this->isAggregateFunction($this->_lexer->lookahead['type'])) {
return $this->AggregateExpression();
}
2009-07-16 03:20:11 +04:00
$this->syntaxError('StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression');
}
2009-06-14 21:34:28 +04:00
/**
* Sets the list of custom tree walkers.
*
* @param array $treeWalkers
*/
public function setSqlTreeWalker($treeWalker)
{
$this->_treeWalker = $treeWalker;
}
/**
* Registers a custom function that returns strings.
*
* @param string $name The function name.
* @param string $class The class name of the function implementation.
*/
public static function registerStringFunction($name, $class)
{
self::$_STRING_FUNCTIONS[$name] = $class;
}
/**
* Registers a custom function that returns numerics.
*
* @param string $name The function name.
* @param string $class The class name of the function implementation.
*/
public static function registerNumericFunction($name, $class)
{
self::$_NUMERIC_FUNCTIONS[$name] = $class;
}
/**
* Registers a custom function that returns date/time values.
*
* @param string $name The function name.
* @param string $class The class name of the function implementation.
*/
public static function registerDatetimeFunction($name, $class)
{
self::$_DATETIME_FUNCTIONS[$name] = $class;
}
}