[2.0] Parser work.
This commit is contained in:
parent
98076e0b0d
commit
618c1281e4
@ -194,10 +194,10 @@ abstract class AbstractHydrator
|
|||||||
if ($this->_isIgnoredName($key)) continue;
|
if ($this->_isIgnoredName($key)) continue;
|
||||||
|
|
||||||
// Cache general information like the column name <-> field name mapping
|
// Cache general information like the column name <-> field name mapping
|
||||||
$e = explode(\Doctrine\ORM\Query\ParserRule::SQLALIAS_SEPARATOR, $key);
|
$e = explode(\Doctrine\ORM\Query\SqlWalker::SQLALIAS_SEPARATOR, $key);
|
||||||
$columnName = array_pop($e);
|
$columnName = array_pop($e);
|
||||||
$cache[$key]['dqlAlias'] = $this->_tableAliases[
|
$cache[$key]['dqlAlias'] = $this->_tableAliases[
|
||||||
implode(\Doctrine\ORM\Query\ParserRule::SQLALIAS_SEPARATOR, $e)
|
implode(\Doctrine\ORM\Query\SqlWalker::SQLALIAS_SEPARATOR, $e)
|
||||||
];
|
];
|
||||||
$classMetadata = $this->_queryComponents[$cache[$key]['dqlAlias']]['metadata'];
|
$classMetadata = $this->_queryComponents[$cache[$key]['dqlAlias']]['metadata'];
|
||||||
// check whether it's an aggregate value or a regular field
|
// check whether it's an aggregate value or a regular field
|
||||||
@ -264,10 +264,10 @@ abstract class AbstractHydrator
|
|||||||
if ($this->_isIgnoredName($key)) continue;
|
if ($this->_isIgnoredName($key)) continue;
|
||||||
|
|
||||||
// cache general information like the column name <-> field name mapping
|
// cache general information like the column name <-> field name mapping
|
||||||
$e = explode(\Doctrine\ORM\Query\ParserRule::SQLALIAS_SEPARATOR, $key);
|
$e = explode(\Doctrine\ORM\Query\SqlWalker::SQLALIAS_SEPARATOR, $key);
|
||||||
$columnName = array_pop($e);
|
$columnName = array_pop($e);
|
||||||
$cache[$key]['dqlAlias'] = $this->_tableAliases[
|
$cache[$key]['dqlAlias'] = $this->_tableAliases[
|
||||||
implode(\Doctrine\ORM\Query\ParserRule::SQLALIAS_SEPARATOR, $e)
|
implode(\Doctrine\ORM\Query\SqlWalker::SQLALIAS_SEPARATOR, $e)
|
||||||
];
|
];
|
||||||
$classMetadata = $this->_queryComponents[$cache[$key]['dqlAlias']]['metadata'];
|
$classMetadata = $this->_queryComponents[$cache[$key]['dqlAlias']]['metadata'];
|
||||||
// check whether it's an aggregate value or a regular field
|
// check whether it's an aggregate value or a regular field
|
||||||
|
@ -19,6 +19,10 @@ class InputParameter extends Node
|
|||||||
|
|
||||||
public function __construct($value)
|
public function __construct($value)
|
||||||
{
|
{
|
||||||
|
if (strlen($value) == 1) {
|
||||||
|
throw new \InvalidArgumentException("Invalid parameter format.");
|
||||||
|
}
|
||||||
|
|
||||||
$param = substr($value, 1);
|
$param = substr($value, 1);
|
||||||
$this->_isNamed = ! is_numeric($param);
|
$this->_isNamed = ! is_numeric($param);
|
||||||
if ($this->_isNamed) {
|
if ($this->_isNamed) {
|
||||||
|
@ -62,6 +62,8 @@ abstract class AbstractResult
|
|||||||
*/
|
*/
|
||||||
protected $_enumParams;
|
protected $_enumParams;
|
||||||
|
|
||||||
|
protected $_defaultQueryComponentAlias;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cannot be called directly, factory methods handle this job.
|
* Cannot be called directly, factory methods handle this job.
|
||||||
*
|
*
|
||||||
@ -110,6 +112,24 @@ abstract class AbstractResult
|
|||||||
return $this->_queryComponents;
|
return $this->_queryComponents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function getDefaultQueryComponentAlias()
|
||||||
|
{
|
||||||
|
return $this->_defaultQueryComponentAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param <type> $alias
|
||||||
|
*/
|
||||||
|
public function setDefaultQueryComponentAlias($alias)
|
||||||
|
{
|
||||||
|
$this->_defaultQueryComponentAlias = $alias;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the declaration for given component alias.
|
* Get the declaration for given component alias.
|
||||||
*
|
*
|
||||||
|
@ -222,7 +222,7 @@ class Lexer
|
|||||||
'[a-z_][a-z0-9_\\\]*',
|
'[a-z_][a-z0-9_\\\]*',
|
||||||
'(?:[0-9]+(?:[,\.][0-9]+)*)(?:e[+-]?[0-9]+)?',
|
'(?:[0-9]+(?:[,\.][0-9]+)*)(?:e[+-]?[0-9]+)?',
|
||||||
"'(?:[^']|'')*'",
|
"'(?:[^']|'')*'",
|
||||||
'\?[0-9]+|:[a-z][a-z0-9_]+'
|
'\?[1-9]+|:[a-z][a-z0-9_]+'
|
||||||
);
|
);
|
||||||
$regex = '/(' . implode(')|(', $patterns) . ')|\s+|(.)/i';
|
$regex = '/(' . implode(')|(', $patterns) . ')|\s+|(.)/i';
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,8 @@ use Doctrine\ORM\Query\Exec;
|
|||||||
*/
|
*/
|
||||||
class Parser
|
class Parser
|
||||||
{
|
{
|
||||||
|
const SCALAR_QUERYCOMPONENT_ALIAS = 'dctrn';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The minimum number of tokens read after last detected error before
|
* The minimum number of tokens read after last detected error before
|
||||||
* another error can be reported.
|
* another error can be reported.
|
||||||
@ -86,12 +88,10 @@ class Parser
|
|||||||
$this->_em = $query->getEntityManager();
|
$this->_em = $query->getEntityManager();
|
||||||
$this->_lexer = new Lexer($query->getDql());
|
$this->_lexer = new Lexer($query->getDql());
|
||||||
|
|
||||||
$defaultQueryComponent = ParserRule::DEFAULT_QUERYCOMPONENT;
|
|
||||||
|
|
||||||
$this->_parserResult = new ParserResult(
|
$this->_parserResult = new ParserResult(
|
||||||
'',
|
'',
|
||||||
array( // queryComponent
|
array( // queryComponent
|
||||||
$defaultQueryComponent => array(
|
self::SCALAR_QUERYCOMPONENT_ALIAS => array(
|
||||||
'metadata' => null,
|
'metadata' => null,
|
||||||
'parent' => null,
|
'parent' => null,
|
||||||
'relation' => null,
|
'relation' => null,
|
||||||
@ -100,7 +100,7 @@ class Parser
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
array( // tableAliasMap
|
array( // tableAliasMap
|
||||||
$defaultQueryComponent => $defaultQueryComponent,
|
self::SCALAR_QUERYCOMPONENT_ALIAS => self::SCALAR_QUERYCOMPONENT_ALIAS,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -215,7 +215,6 @@ class Parser
|
|||||||
$token = $this->_lexer->lookahead;
|
$token = $this->_lexer->lookahead;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Formatting message
|
|
||||||
$message = 'line 0, col ' . (isset($token['position']) ? $token['position'] : '-1') . ': Error: ';
|
$message = 'line 0, col ' . (isset($token['position']) ? $token['position'] : '-1') . ': Error: ';
|
||||||
|
|
||||||
if ($expected !== '') {
|
if ($expected !== '') {
|
||||||
@ -274,20 +273,6 @@ class Parser
|
|||||||
return $this->_em;
|
return $this->_em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the piece of DQL string given the token position
|
|
||||||
*
|
|
||||||
* @param array $token Token that it was processing.
|
|
||||||
* @return string Piece of DQL string.
|
|
||||||
*/
|
|
||||||
/*public function getQueryPiece($token, $previousChars = 10, $nextChars = 10)
|
|
||||||
{
|
|
||||||
$start = max(0, $token['position'] - $previousChars);
|
|
||||||
$end = max($token['position'] + $nextChars, strlen($this->_input));
|
|
||||||
|
|
||||||
return substr($this->_input, $start, $end);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the next-next (after lookahead) token start a function.
|
* Checks if the next-next (after lookahead) token start a function.
|
||||||
*
|
*
|
||||||
@ -296,7 +281,7 @@ class Parser
|
|||||||
private function _isFunction()
|
private function _isFunction()
|
||||||
{
|
{
|
||||||
$next = $this->_lexer->glimpse();
|
$next = $this->_lexer->glimpse();
|
||||||
return ($next['value'] === '(');
|
return $next['value'] === '(';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -360,7 +345,7 @@ class Parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Begins a new stack of deferred path expressions.
|
||||||
*/
|
*/
|
||||||
private function _beginDeferredPathExpressionStack()
|
private function _beginDeferredPathExpressionStack()
|
||||||
{
|
{
|
||||||
@ -368,8 +353,8 @@ class Parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes pending path expressions that were encountered while parsing
|
* Processes the topmost stack of deferred path expressions.
|
||||||
* select expressions. These will be validated to make sure they are indeed
|
* These will be validated to make sure they are indeed
|
||||||
* valid <tt>StateFieldPathExpression</tt>s and additional information
|
* valid <tt>StateFieldPathExpression</tt>s and additional information
|
||||||
* is attached to their AST nodes.
|
* is attached to their AST nodes.
|
||||||
*/
|
*/
|
||||||
@ -451,6 +436,8 @@ class Parser
|
|||||||
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
|
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
|
||||||
$this->match(Lexer::T_IDENTIFIER);
|
$this->match(Lexer::T_IDENTIFIER);
|
||||||
$aliasIdentificationVariable = $this->_lexer->token['value'];
|
$aliasIdentificationVariable = $this->_lexer->token['value'];
|
||||||
|
} else {
|
||||||
|
$aliasIdentificationVariable = $abstractSchemaName;
|
||||||
}
|
}
|
||||||
$this->match(Lexer::T_SET);
|
$this->match(Lexer::T_SET);
|
||||||
$updateItems = array();
|
$updateItems = array();
|
||||||
@ -460,7 +447,6 @@ class Parser
|
|||||||
$updateItems[] = $this->_UpdateItem();
|
$updateItems[] = $this->_UpdateItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($aliasIdentificationVariable) {
|
|
||||||
$classMetadata = $this->_em->getClassMetadata($abstractSchemaName);
|
$classMetadata = $this->_em->getClassMetadata($abstractSchemaName);
|
||||||
// Building queryComponent
|
// Building queryComponent
|
||||||
$queryComponent = array(
|
$queryComponent = array(
|
||||||
@ -471,7 +457,7 @@ class Parser
|
|||||||
'scalar' => null,
|
'scalar' => null,
|
||||||
);
|
);
|
||||||
$this->_parserResult->setQueryComponent($aliasIdentificationVariable, $queryComponent);
|
$this->_parserResult->setQueryComponent($aliasIdentificationVariable, $queryComponent);
|
||||||
}
|
$this->_parserResult->setDefaultQueryComponentAlias($aliasIdentificationVariable);
|
||||||
|
|
||||||
$updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems);
|
$updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems);
|
||||||
$updateClause->setAliasIdentificationVariable($aliasIdentificationVariable);
|
$updateClause->setAliasIdentificationVariable($aliasIdentificationVariable);
|
||||||
@ -490,6 +476,8 @@ class Parser
|
|||||||
$this->match(Lexer::T_IDENTIFIER);
|
$this->match(Lexer::T_IDENTIFIER);
|
||||||
$identVariable = $this->_lexer->token['value'];
|
$identVariable = $this->_lexer->token['value'];
|
||||||
$this->match('.');
|
$this->match('.');
|
||||||
|
} else {
|
||||||
|
$identVariable = $this->_parserResult->getDefaultQueryComponentAlias();
|
||||||
}
|
}
|
||||||
$this->match(Lexer::T_IDENTIFIER);
|
$this->match(Lexer::T_IDENTIFIER);
|
||||||
$field = $this->_lexer->token['value'];
|
$field = $this->_lexer->token['value'];
|
||||||
@ -551,9 +539,11 @@ class Parser
|
|||||||
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
|
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
|
||||||
$this->match(Lexer::T_IDENTIFIER);
|
$this->match(Lexer::T_IDENTIFIER);
|
||||||
$deleteClause->setAliasIdentificationVariable($this->_lexer->token['value']);
|
$deleteClause->setAliasIdentificationVariable($this->_lexer->token['value']);
|
||||||
|
} else {
|
||||||
|
$deleteClause->setAliasIdentificationVariable($deleteClause->getAbstractSchemaName());
|
||||||
|
}
|
||||||
|
|
||||||
$classMetadata = $this->_em->getClassMetadata($deleteClause->getAbstractSchemaName());
|
$classMetadata = $this->_em->getClassMetadata($deleteClause->getAbstractSchemaName());
|
||||||
// Building queryComponent
|
|
||||||
$queryComponent = array(
|
$queryComponent = array(
|
||||||
'metadata' => $classMetadata,
|
'metadata' => $classMetadata,
|
||||||
'parent' => null,
|
'parent' => null,
|
||||||
@ -561,8 +551,10 @@ class Parser
|
|||||||
'map' => null,
|
'map' => null,
|
||||||
'scalar' => null,
|
'scalar' => null,
|
||||||
);
|
);
|
||||||
$this->_parserResult->setQueryComponent($this->_lexer->token['value'], $queryComponent);
|
$this->_parserResult->setQueryComponent($deleteClause->getAliasIdentificationVariable(),
|
||||||
}
|
$queryComponent);
|
||||||
|
$this->_parserResult->setDefaultQueryComponentAlias($deleteClause->getAliasIdentificationVariable());
|
||||||
|
|
||||||
return $deleteClause;
|
return $deleteClause;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,6 +591,9 @@ class Parser
|
|||||||
$this->match(Lexer::T_FROM);
|
$this->match(Lexer::T_FROM);
|
||||||
$identificationVariableDeclarations = array();
|
$identificationVariableDeclarations = array();
|
||||||
$identificationVariableDeclarations[] = $this->_IdentificationVariableDeclaration();
|
$identificationVariableDeclarations[] = $this->_IdentificationVariableDeclaration();
|
||||||
|
$this->_parserResult->setDefaultQueryComponentAlias(
|
||||||
|
$identificationVariableDeclarations[0]->getRangeVariableDeclaration()->getAbstractSchemaName()
|
||||||
|
);
|
||||||
while ($this->_lexer->isNextToken(',')) {
|
while ($this->_lexer->isNextToken(',')) {
|
||||||
$this->match(',');
|
$this->match(',');
|
||||||
$identificationVariableDeclarations[] = $this->_IdentificationVariableDeclaration();
|
$identificationVariableDeclarations[] = $this->_IdentificationVariableDeclaration();
|
||||||
@ -1345,6 +1340,7 @@ class Parser
|
|||||||
$this->match(',');
|
$this->match(',');
|
||||||
$literals[] = $this->_Literal();
|
$literals[] = $this->_Literal();
|
||||||
}
|
}
|
||||||
|
$inExpression->setLiterals($literals);
|
||||||
}
|
}
|
||||||
$this->match(')');
|
$this->match(')');
|
||||||
|
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
<?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.phpdoctrine.org>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Production variables holder
|
|
||||||
*
|
|
||||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
|
||||||
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
|
|
||||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
|
||||||
* @link http://www.phpdoctrine.org
|
|
||||||
* @since 2.0
|
|
||||||
* @version $Revision$
|
|
||||||
*/
|
|
||||||
class Doctrine_ORM_Query_ParserDataHolder
|
|
||||||
{
|
|
||||||
protected static $_instance;
|
|
||||||
protected $_data;
|
|
||||||
|
|
||||||
protected function __construct()
|
|
||||||
{
|
|
||||||
$this->free();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function create()
|
|
||||||
{
|
|
||||||
if ( ! isset(self::$_instance)) {
|
|
||||||
self::$_instance = new self;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::$_instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function free()
|
|
||||||
{
|
|
||||||
$this->_data = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function set($offset, $value)
|
|
||||||
{
|
|
||||||
$this->_data[$offset] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get($offset)
|
|
||||||
{
|
|
||||||
return isset($this->_data[$offset]) ? $this->_data[$offset] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function has($offset)
|
|
||||||
{
|
|
||||||
return isset($this->_data[$offset]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function remove($offset)
|
|
||||||
{
|
|
||||||
if ($this->has($offset)) {
|
|
||||||
$this->_data[$offset] = null;
|
|
||||||
unset($this->_data[$offset]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,7 +16,7 @@
|
|||||||
*
|
*
|
||||||
* This software consists of voluntary contributions made by many individuals
|
* This software consists of voluntary contributions made by many individuals
|
||||||
* and is licensed under the LGPL. For more information, see
|
* and is licensed under the LGPL. For more information, see
|
||||||
* <http://www.phpdoctrine.org>.
|
* <http://www.doctrine-project.org>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Doctrine\ORM\Query;
|
namespace Doctrine\ORM\Query;
|
||||||
@ -27,7 +27,7 @@ namespace Doctrine\ORM\Query;
|
|||||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||||
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
|
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
|
||||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||||
* @link http://www.phpdoctrine.org
|
* @link http://www.doctrine-project.org
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
* @version $Revision$
|
* @version $Revision$
|
||||||
*/
|
*/
|
||||||
|
@ -1,193 +0,0 @@
|
|||||||
<?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.phpdoctrine.org>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Doctrine\ORM\Query;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An abstract base class for the productions of the Doctrine Query Language
|
|
||||||
* context-free grammar.
|
|
||||||
*
|
|
||||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
|
||||||
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
|
|
||||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
|
||||||
* @link http://www.phpdoctrine.org
|
|
||||||
* @since 2.0
|
|
||||||
* @version $Revision$
|
|
||||||
*/
|
|
||||||
abstract class ParserRule
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @nodoc
|
|
||||||
*/
|
|
||||||
const SQLALIAS_SEPARATOR = '__';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @nodoc
|
|
||||||
*/
|
|
||||||
const DEFAULT_QUERYCOMPONENT = 'dctrn';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parser object
|
|
||||||
*
|
|
||||||
* @var Doctrine_ORM_Query_Parser
|
|
||||||
*/
|
|
||||||
protected $_parser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The EntityManager.
|
|
||||||
*
|
|
||||||
* @var EntityManager
|
|
||||||
*/
|
|
||||||
protected $_em;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Parser Data Holder.
|
|
||||||
*
|
|
||||||
* @var ParserDataHolder
|
|
||||||
*/
|
|
||||||
protected $_dataHolder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new production object.
|
|
||||||
*
|
|
||||||
* @param Doctrine_ORM_Query_Parser $parser a parser object
|
|
||||||
*/
|
|
||||||
public function __construct(Doctrine_ORM_Query_Parser $parser)
|
|
||||||
{
|
|
||||||
$this->_parser = $parser;
|
|
||||||
$this->_em = $this->_parser->getEntityManager();
|
|
||||||
$this->_dataHolder = Doctrine_ORM_Query_ParserDataHolder::create();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function _isNextToken($token)
|
|
||||||
{
|
|
||||||
$la = $this->_parser->lookahead;
|
|
||||||
return ($la['type'] === $token || $la['value'] === $token);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function _isFunction()
|
|
||||||
{
|
|
||||||
$la = $this->_parser->lookahead;
|
|
||||||
$next = $this->_parser->getScanner()->peek();
|
|
||||||
return ($la['type'] === Doctrine_ORM_Query_Token::T_IDENTIFIER && $next['value'] === '(');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function _isSubselect()
|
|
||||||
{
|
|
||||||
$la = $this->_parser->lookahead;
|
|
||||||
$next = $this->_parser->getScanner()->peek();
|
|
||||||
return ($la['value'] === '(' && $next['type'] === Doctrine_ORM_Query_Token::T_SELECT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the grammar rule using the specified parameters.
|
|
||||||
*
|
|
||||||
* @param string $RuleName BNF Grammar Rule name
|
|
||||||
* @param array $paramHolder Production parameter holder
|
|
||||||
* @return Doctrine_ORM_Query_AST The constructed subtree during parsing.
|
|
||||||
*/
|
|
||||||
public function parse($ruleName)
|
|
||||||
{
|
|
||||||
echo $ruleName . PHP_EOL;
|
|
||||||
return $this->_getGrammarRule($ruleName)->syntax();
|
|
||||||
|
|
||||||
// Syntax check
|
|
||||||
/*if ( ! $this->_dataHolder->has('syntaxCheck') || $this->_dataHolder->get('syntaxCheck') === true) {
|
|
||||||
//echo "Processing syntax checks of " . $RuleName . "...\n";
|
|
||||||
$ASTNode = $BNFGrammarRule->syntax();
|
|
||||||
if ($ASTNode !== null) {
|
|
||||||
//echo "Returning Grammar Rule class: " . (is_object($ASTNode) ? get_class($ASTNode) : $ASTNode) . "...\n";
|
|
||||||
return $ASTNode;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// Semantical check
|
|
||||||
/*if ( ! $this->_dataHolder->has('semanticalCheck') || $this->_dataHolder->get('semanticalCheck') === true) {
|
|
||||||
echo "Processing semantical checks of " . $RuleName . "...\n";
|
|
||||||
|
|
||||||
$return = $BNFGrammarRule->semantical();
|
|
||||||
|
|
||||||
if ($return !== null) {
|
|
||||||
echo "Returning Grammar Rule class: " . (is_object($return) ? get_class($return) : $return) . "...\n";
|
|
||||||
|
|
||||||
return $return;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return $BNFGrammarRule;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a grammar rule object with the given name.
|
|
||||||
*
|
|
||||||
* @param string $name grammar rule name
|
|
||||||
* @return Doctrine_ORM_Query_ParserRule
|
|
||||||
*/
|
|
||||||
protected function _getGrammarRule($name)
|
|
||||||
{
|
|
||||||
$class = 'Doctrine_ORM_Query_Parser_' . $name;
|
|
||||||
|
|
||||||
//echo $class . "\r\n";
|
|
||||||
//TODO: This expensive check is not necessary. Should be removed at the end.
|
|
||||||
// "new $class" will throw an error anyway if the class is not found.
|
|
||||||
if ( ! class_exists($class)) {
|
|
||||||
throw \Doctrine\Common\DoctrineException::updateMe(
|
|
||||||
"Unknown Grammar Rule '$name'. Could not find related compiler class."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new $class($this->_parser);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an AST node with the given name.
|
|
||||||
*
|
|
||||||
* @param string $AstName AST node name
|
|
||||||
* @return Doctrine_ORM_Query_AST
|
|
||||||
*/
|
|
||||||
public function AST($AstName)
|
|
||||||
{
|
|
||||||
$class = 'Doctrine_ORM_Query_AST_' . $AstName;
|
|
||||||
return new $class($this->_parser->getParserResult());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @nodoc
|
|
||||||
*/
|
|
||||||
abstract public function syntax();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @nodoc
|
|
||||||
*/
|
|
||||||
public function semantical()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getParser()
|
|
||||||
{
|
|
||||||
return $this->_parser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDataHolder()
|
|
||||||
{
|
|
||||||
return $this->_dataHolder;
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,7 +16,7 @@
|
|||||||
*
|
*
|
||||||
* This software consists of voluntary contributions made by many individuals
|
* This software consists of voluntary contributions made by many individuals
|
||||||
* and is licensed under the LGPL. For more information, see
|
* and is licensed under the LGPL. For more information, see
|
||||||
* <http://www.phpdoctrine.org>.
|
* <http://www.doctrine-project.org>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Doctrine\ORM\Query;
|
namespace Doctrine\ORM\Query;
|
||||||
@ -27,7 +27,7 @@ namespace Doctrine\ORM\Query;
|
|||||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||||
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
|
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
|
||||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||||
* @link http://www.phpdoctrine.org
|
* @link http://www.doctrine-project.org
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
* @version $Revision$
|
* @version $Revision$
|
||||||
*/
|
*/
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
namespace Doctrine\ORM\Query;
|
namespace Doctrine\ORM\Query;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Query\Parser;
|
||||||
use Doctrine\ORM\Query\AST;
|
use Doctrine\ORM\Query\AST;
|
||||||
use Doctrine\Common\DoctrineException;
|
use Doctrine\Common\DoctrineException;
|
||||||
|
|
||||||
@ -35,6 +36,8 @@ use Doctrine\Common\DoctrineException;
|
|||||||
*/
|
*/
|
||||||
class SqlWalker
|
class SqlWalker
|
||||||
{
|
{
|
||||||
|
const SQLALIAS_SEPARATOR = '__';
|
||||||
|
|
||||||
private $_tableAliasCounter = 0;
|
private $_tableAliasCounter = 0;
|
||||||
private $_parserResult;
|
private $_parserResult;
|
||||||
private $_em;
|
private $_em;
|
||||||
@ -50,7 +53,7 @@ class SqlWalker
|
|||||||
$this->_parserResult = $parserResult;
|
$this->_parserResult = $parserResult;
|
||||||
$sqlToDqlAliasMap = array();
|
$sqlToDqlAliasMap = array();
|
||||||
foreach ($parserResult->getQueryComponents() as $dqlAlias => $qComp) {
|
foreach ($parserResult->getQueryComponents() as $dqlAlias => $qComp) {
|
||||||
if ($dqlAlias != 'dctrn') {
|
if ($dqlAlias != Parser::SCALAR_QUERYCOMPONENT_ALIAS) {
|
||||||
$sqlAlias = $this->generateSqlTableAlias($qComp['metadata']->getTableName());
|
$sqlAlias = $this->generateSqlTableAlias($qComp['metadata']->getTableName());
|
||||||
$sqlToDqlAliasMap[$sqlAlias] = $dqlAlias;
|
$sqlToDqlAliasMap[$sqlAlias] = $dqlAlias;
|
||||||
}
|
}
|
||||||
@ -302,7 +305,9 @@ class SqlWalker
|
|||||||
|
|
||||||
public function walkUpdateStatement(AST\UpdateStatement $AST)
|
public function walkUpdateStatement(AST\UpdateStatement $AST)
|
||||||
{
|
{
|
||||||
|
$sql = $this->walkUpdateClause($AST->getUpdateClause());
|
||||||
|
$sql .= $AST->getWhereClause() ? $this->walkWhereClause($AST->getWhereClause()) : '';
|
||||||
|
return $sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function walkDeleteStatement(AST\DeleteStatement $AST)
|
public function walkDeleteStatement(AST\DeleteStatement $AST)
|
||||||
@ -323,6 +328,40 @@ class SqlWalker
|
|||||||
return $sql;
|
return $sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function walkUpdateClause($updateClause)
|
||||||
|
{
|
||||||
|
$sql = 'UPDATE ';
|
||||||
|
$class = $this->_em->getClassMetadata($updateClause->getAbstractSchemaName());
|
||||||
|
$sql .= $class->getTableName();
|
||||||
|
if ($updateClause->getAliasIdentificationVariable()) {
|
||||||
|
$sql .= ' ' . $this->_dqlToSqlAliasMap[$updateClause->getAliasIdentificationVariable()];
|
||||||
|
}
|
||||||
|
$sql .= ' SET ' . implode(', ', array_map(array($this, 'walkUpdateItem'),
|
||||||
|
$updateClause->getUpdateItems()));
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function walkUpdateItem($updateItem)
|
||||||
|
{
|
||||||
|
$sql = '';
|
||||||
|
$dqlAlias = $updateItem->getIdentificationVariable() ?
|
||||||
|
$updateItem->getIdentificationVariable() :
|
||||||
|
$this->_parserResult->getDefaultQueryComponentAlias();
|
||||||
|
$qComp = $this->_parserResult->getQueryComponent($dqlAlias);
|
||||||
|
|
||||||
|
$sql .= $this->_dqlToSqlAliasMap[$dqlAlias] . '.'
|
||||||
|
. $qComp['metadata']->getColumnName($updateItem->getField())
|
||||||
|
. ' = ';
|
||||||
|
|
||||||
|
$newValue = $updateItem->getNewValue();
|
||||||
|
if ($newValue instanceof AST\InputParameter) {
|
||||||
|
$sql .= $newValue->isNamed() ? ':' . $newValue->getName() : '?';
|
||||||
|
} // TODO: else if ...
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
public function walkWhereClause($whereClause)
|
public function walkWhereClause($whereClause)
|
||||||
{
|
{
|
||||||
$sql = ' WHERE ';
|
$sql = ' WHERE ';
|
||||||
@ -388,12 +427,21 @@ class SqlWalker
|
|||||||
if ($inExpr->getSubselect()) {
|
if ($inExpr->getSubselect()) {
|
||||||
$sql .= $this->walkSubselect($inExpr->getSubselect());
|
$sql .= $this->walkSubselect($inExpr->getSubselect());
|
||||||
} else {
|
} else {
|
||||||
//$sql .= implode(', ', array_map(array($this, 'walkLiteral'), $inExpr->getLiterals()));
|
$sql .= implode(', ', array_map(array($this, 'walkLiteral'), $inExpr->getLiterals()));
|
||||||
}
|
}
|
||||||
$sql .= ')';
|
$sql .= ')';
|
||||||
return $sql;
|
return $sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function walkLiteral($literal)
|
||||||
|
{
|
||||||
|
if ($literal instanceof AST\InputParameter) {
|
||||||
|
return ($literal->isNamed() ? ':' . $literal->getName() : '?');
|
||||||
|
} else {
|
||||||
|
return $literal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function walkBetweenExpression($betweenExpr)
|
public function walkBetweenExpression($betweenExpr)
|
||||||
{
|
{
|
||||||
$sql = $this->walkArithmeticExpression($betweenExpr->getBaseExpression());
|
$sql = $this->walkArithmeticExpression($betweenExpr->getBaseExpression());
|
||||||
@ -433,7 +481,7 @@ class SqlWalker
|
|||||||
$sql .= ' ' . $compExpr->getOperator() . ' ';
|
$sql .= ' ' . $compExpr->getOperator() . ' ';
|
||||||
if ($compExpr->getRightExpression() instanceof AST\ArithmeticExpression) {
|
if ($compExpr->getRightExpression() instanceof AST\ArithmeticExpression) {
|
||||||
$sql .= $this->walkArithmeticExpression($compExpr->getRightExpression());
|
$sql .= $this->walkArithmeticExpression($compExpr->getRightExpression());
|
||||||
}
|
} // else...
|
||||||
return $sql;
|
return $sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,9 +24,7 @@ class AllTests
|
|||||||
$suite->addTestSuite('Doctrine\Tests\ORM\Query\LanguageRecognitionTest');
|
$suite->addTestSuite('Doctrine\Tests\ORM\Query\LanguageRecognitionTest');
|
||||||
$suite->addTestSuite('Doctrine\Tests\ORM\Query\LexerTest');
|
$suite->addTestSuite('Doctrine\Tests\ORM\Query\LexerTest');
|
||||||
$suite->addTestSuite('Doctrine\Tests\ORM\Query\DeleteSqlGenerationTest');
|
$suite->addTestSuite('Doctrine\Tests\ORM\Query\DeleteSqlGenerationTest');
|
||||||
|
$suite->addTestSuite('Doctrine\Tests\ORM\Query\UpdateSqlGenerationTest');
|
||||||
/*
|
|
||||||
$suite->addTestSuite('Orm_Query_UpdateSqlGenerationTest');*/
|
|
||||||
|
|
||||||
return $suite;
|
return $suite;
|
||||||
}
|
}
|
||||||
|
@ -218,19 +218,17 @@ class DeleteSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
public function testWithExprAndIn()
|
public function testWithExprAndIn()
|
||||||
{
|
{
|
||||||
// "WHERE" Expression InExpression
|
|
||||||
$this->assertSqlGeneration(
|
$this->assertSqlGeneration(
|
||||||
'DELETE Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id IN ( ?, ?, ?, ? )',
|
'DELETE Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id IN ( ?1, ?2, ?3, ?4 )',
|
||||||
'DELETE FROM cms_users c0 WHERE c0.id IN (?, ?, ?, ?)'
|
'DELETE FROM cms_users c0 WHERE c0.id IN (?, ?, ?, ?)'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertSqlGeneration(
|
$this->assertSqlGeneration(
|
||||||
'DELETE Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id NOT IN ( ?, ? )',
|
'DELETE Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id NOT IN ( ?1, ?2 )',
|
||||||
'DELETE FROM cms_users c0 WHERE c0.id NOT IN (?, ?)'
|
'DELETE FROM cms_users c0 WHERE c0.id NOT IN (?, ?)'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
@ -181,13 +181,12 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
|
|||||||
|
|
||||||
public function testSubselectInSelectPart()
|
public function testSubselectInSelectPart()
|
||||||
{
|
{
|
||||||
// Semantical error: Unknown query component u (probably in subselect)
|
|
||||||
$this->assertValidDql("SELECT u.name, (SELECT COUNT(p.phonenumber) FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p WHERE p.phonenumber = 1234) pcount FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name = 'jon'");
|
$this->assertValidDql("SELECT u.name, (SELECT COUNT(p.phonenumber) FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p WHERE p.phonenumber = 1234) pcount FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name = 'jon'");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPositionalInputParameter()
|
public function testPositionalInputParameter()
|
||||||
{
|
{
|
||||||
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?');
|
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?1');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNamedInputParameter()
|
public function testNamedInputParameter()
|
||||||
|
@ -16,14 +16,16 @@
|
|||||||
*
|
*
|
||||||
* This software consists of voluntary contributions made by many individuals
|
* This software consists of voluntary contributions made by many individuals
|
||||||
* and is licensed under the LGPL. For more information, see
|
* and is licensed under the LGPL. For more information, see
|
||||||
* <http://www.phpdoctrine.org>.
|
* <http://www.doctrine-project.org>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
namespace Doctrine\Tests\ORM\Query;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../TestInit.php';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test case for testing the saving and referencing of query identifiers.
|
* Test case for testing the saving and referencing of query identifiers.
|
||||||
*
|
*
|
||||||
* @package Doctrine
|
|
||||||
* @subpackage Query
|
|
||||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||||
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
|
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
|
||||||
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
|
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
|
||||||
@ -35,34 +37,36 @@
|
|||||||
* testcases later since we'll have a lot of them and we might want to have special SQL
|
* testcases later since we'll have a lot of them and we might want to have special SQL
|
||||||
* generation tests for some dbms specific SQL syntaxes.
|
* generation tests for some dbms specific SQL syntaxes.
|
||||||
*/
|
*/
|
||||||
class Orm_Query_UpdateSqlGenerationTest extends Doctrine_OrmTestCase
|
class UpdateSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
|
||||||
{
|
{
|
||||||
|
private $_em;
|
||||||
|
|
||||||
|
protected function setUp() {
|
||||||
|
$this->_em = $this->_getTestEntityManager();
|
||||||
|
}
|
||||||
|
|
||||||
public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed)
|
public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$entityManager = $this->_em;
|
$query = $this->_em->createQuery($dqlToBeTested);
|
||||||
$query = $entityManager->createQuery($dqlToBeTested);
|
|
||||||
|
|
||||||
parent::assertEquals($sqlToBeConfirmed, $query->getSql());
|
parent::assertEquals($sqlToBeConfirmed, $query->getSql());
|
||||||
|
|
||||||
$query->free();
|
$query->free();
|
||||||
} catch (Doctrine_Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->fail($e->getMessage());
|
$this->fail($e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function testWithoutWhere()
|
public function testWithoutWhere()
|
||||||
{
|
{
|
||||||
// NO WhereClause
|
|
||||||
$this->assertSqlGeneration(
|
$this->assertSqlGeneration(
|
||||||
'UPDATE CmsUser u SET name = ?',
|
'UPDATE Doctrine\Tests\Models\CMS\CmsUser u SET u.name = ?1',
|
||||||
'UPDATE cms_user cu SET cu.name = ? WHERE 1 = 1'
|
'UPDATE cms_users c0 SET c0.name = ?'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertSqlGeneration(
|
$this->assertSqlGeneration(
|
||||||
'UPDATE CmsUser u SET name = ?, username = ?',
|
'UPDATE Doctrine\Tests\Models\CMS\CmsUser u SET u.name = ?1, u.username = ?2',
|
||||||
'UPDATE cms_user cu SET cu.name = ?, cu.username = ? WHERE 1 = 1'
|
'UPDATE cms_users c0 SET c0.name = ?, c0.username = ?'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user