From 3086835fe84c1658ce84cbe5c08e0bdc10a2a542 Mon Sep 17 00:00:00 2001 From: guilhermeblanco Date: Tue, 23 Sep 2008 02:47:11 +0000 Subject: [PATCH] Second part of commit, including the Doctrine/ORM/Query namespace. --- lib/Doctrine/ORM/Query/AST.php | 41 + .../ORM/Query/AST/AbstractSchemaName.php | 48 + .../Query/AST/AliasIdentificationVariable.php | 48 + .../ORM/Query/AST/DeleteStatement.php | 73 ++ .../Query/AST/FieldIdentificationVariable.php | 48 + lib/Doctrine/ORM/Query/AST/FromClause.php | 85 ++ .../ORM/Query/AST/IdentificationVariable.php | 60 + .../AST/IdentificationVariableDeclaration.php | 98 ++ lib/Doctrine/ORM/Query/AST/IndexBy.php | 48 + lib/Doctrine/ORM/Query/AST/Join.php | 203 ++++ .../ORM/Query/AST/JoinVariableDeclaration.php | 70 ++ .../Query/AST/RangeVariableDeclaration.php | 87 ++ lib/Doctrine/ORM/Query/AST/SelectClause.php | 91 ++ .../ORM/Query/AST/SelectStatement.php | 130 +++ .../AST/SimpleStateFieldPathExpression.php | 62 + .../ORM/Query/AST/UpdateStatetement.php | 73 ++ lib/Doctrine/ORM/Query/Abstract.php | 1014 +++++++++++++++++ lib/Doctrine/ORM/Query/AbstractResult.php | 276 +++++ lib/Doctrine/ORM/Query/CacheHandler.php | 150 +++ lib/Doctrine/ORM/Query/Exception.php | 37 + lib/Doctrine/ORM/Query/Parser.php | 378 ++++++ .../ORM/Query/Parser/AbstractSchemaName.php | 67 ++ .../Parser/AliasIdentificationVariable.php | 64 ++ .../ORM/Query/Parser/DeleteStatement.php | 51 + lib/Doctrine/ORM/Query/Parser/Exception.php | 35 + .../Parser/FieldIdentificationVariable.php | 59 + lib/Doctrine/ORM/Query/Parser/FromClause.php | 59 + .../Query/Parser/IdentificationVariable.php | 62 + .../IdentificationVariableDeclaration.php | 82 ++ lib/Doctrine/ORM/Query/Parser/IndexBy.php | 99 ++ lib/Doctrine/ORM/Query/Parser/Join.php | 91 ++ .../Query/Parser/JoinVariableDeclaration.php | 74 ++ .../ORM/Query/Parser/QueryLanguage.php | 57 + .../Query/Parser/RangeVariableDeclaration.php | 94 ++ .../ORM/Query/Parser/SelectClause.php | 74 ++ .../ORM/Query/Parser/SelectExpression.php | 80 ++ .../ORM/Query/Parser/SelectStatement.php | 81 ++ .../ORM/Query/Parser/SimpleStateField.php | 42 + .../Parser/SimpleStateFieldPathExpression.php | 77 ++ .../ORM/Query/Parser/UpdateStatement.php | 51 + lib/Doctrine/ORM/Query/ParserParamHolder.php | 87 ++ lib/Doctrine/ORM/Query/ParserResult.php | 186 +++ lib/Doctrine/ORM/Query/ParserResultDummy.php | 75 ++ lib/Doctrine/ORM/Query/ParserRule.php | 202 ++++ lib/Doctrine/ORM/Query/Printer.php | 93 ++ .../Query/Production/AggregateExpression.php | 118 ++ lib/Doctrine/ORM/Query/Production/Atom.php | 119 ++ .../Query/Production/BetweenExpression.php | 87 ++ .../Query/Production/ComparisonExpression.php | 94 ++ .../Query/Production/ComparisonOperator.php | 82 ++ .../Production/ConditionalExpression.php | 79 ++ .../Query/Production/ConditionalFactor.php | 70 ++ .../Query/Production/ConditionalPrimary.php | 108 ++ .../ORM/Query/Production/ConditionalTerm.php | 79 ++ .../ORM/Query/Production/DeleteClause.php | 73 ++ .../ORM/Query/Production/DeleteStatement.php | 85 ++ .../ORM/Query/Production/ExistsExpression.php | 61 + .../ORM/Query/Production/Expression.php | 96 ++ lib/Doctrine/ORM/Query/Production/Factor.php | 94 ++ .../FieldIdentificationVariable.php | 91 ++ .../ORM/Query/Production/FromClause.php | 81 ++ .../ORM/Query/Production/Function.php | 104 ++ .../ORM/Query/Production/GroupByClause.php | 89 ++ .../ORM/Query/Production/GroupByItem.php | 50 + .../ORM/Query/Production/HavingClause.php | 70 ++ .../Production/IdentificationVariable.php | 65 ++ .../IdentificationVariableDeclaration.php | 92 ++ .../ORM/Query/Production/InExpression.php | 109 ++ lib/Doctrine/ORM/Query/Production/IndexBy.php | 121 ++ lib/Doctrine/ORM/Query/Production/Join.php | 162 +++ .../Production/JoinVariableDeclaration.php | 70 ++ .../ORM/Query/Production/LikeExpression.php | 91 ++ .../ORM/Query/Production/LimitClause.php | 65 ++ .../Production/NullComparisonExpression.php | 65 ++ .../ORM/Query/Production/OffsetClause.php | 74 ++ .../ORM/Query/Production/OrderByClause.php | 88 ++ .../ORM/Query/Production/OrderByItem.php | 82 ++ .../ORM/Query/Production/PathExpression.php | 171 +++ .../PathExpressionEndingWithAsterisk.php | 169 +++ lib/Doctrine/ORM/Query/Production/Primary.php | 110 ++ .../Query/Production/QuantifiedExpression.php | 85 ++ .../ORM/Query/Production/QueryLanguage.php | 57 + .../Production/RangeVariableDeclaration.php | 260 +++++ .../ORM/Query/Production/SelectClause.php | 101 ++ .../ORM/Query/Production/SelectExpression.php | 212 ++++ .../ORM/Query/Production/SelectStatement.php | 133 +++ .../SimpleConditionalExpression.php | 122 ++ .../Query/Production/SimpleSelectClause.php | 91 ++ .../ORM/Query/Production/Subselect.php | 161 +++ lib/Doctrine/ORM/Query/Production/Term.php | 107 ++ .../ORM/Query/Production/UpdateClause.php | 101 ++ .../ORM/Query/Production/UpdateItem.php | 86 ++ .../ORM/Query/Production/UpdateStatement.php | 82 ++ .../Query/Production/VariableDeclaration.php | 151 +++ .../ORM/Query/Production/WhereClause.php | 59 + lib/Doctrine/ORM/Query/QueryResult.php | 44 + lib/Doctrine/ORM/Query/Scanner.php | 230 ++++ lib/Doctrine/ORM/Query/SqlBuilder.php | 65 ++ lib/Doctrine/ORM/Query/SqlBuilder/Mysql.php | 36 + lib/Doctrine/ORM/Query/SqlBuilder/Sqlite.php | 36 + .../ORM/Query/SqlExecutor/Abstract.php | 110 ++ .../Query/SqlExecutor/MultiTableDelete.php | 59 + .../Query/SqlExecutor/MultiTableUpdate.php | 54 + .../ORM/Query/SqlExecutor/SingleSelect.php | 43 + .../SqlExecutor/SingleTableDeleteUpdate.php | 45 + lib/Doctrine/ORM/Query/Token.php | 154 +++ 106 files changed, 11010 insertions(+) create mode 100644 lib/Doctrine/ORM/Query/AST.php create mode 100644 lib/Doctrine/ORM/Query/AST/AbstractSchemaName.php create mode 100644 lib/Doctrine/ORM/Query/AST/AliasIdentificationVariable.php create mode 100644 lib/Doctrine/ORM/Query/AST/DeleteStatement.php create mode 100644 lib/Doctrine/ORM/Query/AST/FieldIdentificationVariable.php create mode 100644 lib/Doctrine/ORM/Query/AST/FromClause.php create mode 100644 lib/Doctrine/ORM/Query/AST/IdentificationVariable.php create mode 100644 lib/Doctrine/ORM/Query/AST/IdentificationVariableDeclaration.php create mode 100644 lib/Doctrine/ORM/Query/AST/IndexBy.php create mode 100644 lib/Doctrine/ORM/Query/AST/Join.php create mode 100644 lib/Doctrine/ORM/Query/AST/JoinVariableDeclaration.php create mode 100644 lib/Doctrine/ORM/Query/AST/RangeVariableDeclaration.php create mode 100644 lib/Doctrine/ORM/Query/AST/SelectClause.php create mode 100644 lib/Doctrine/ORM/Query/AST/SelectStatement.php create mode 100644 lib/Doctrine/ORM/Query/AST/SimpleStateFieldPathExpression.php create mode 100644 lib/Doctrine/ORM/Query/AST/UpdateStatetement.php create mode 100644 lib/Doctrine/ORM/Query/Abstract.php create mode 100644 lib/Doctrine/ORM/Query/AbstractResult.php create mode 100644 lib/Doctrine/ORM/Query/CacheHandler.php create mode 100644 lib/Doctrine/ORM/Query/Exception.php create mode 100644 lib/Doctrine/ORM/Query/Parser.php create mode 100644 lib/Doctrine/ORM/Query/Parser/AbstractSchemaName.php create mode 100644 lib/Doctrine/ORM/Query/Parser/AliasIdentificationVariable.php create mode 100644 lib/Doctrine/ORM/Query/Parser/DeleteStatement.php create mode 100644 lib/Doctrine/ORM/Query/Parser/Exception.php create mode 100644 lib/Doctrine/ORM/Query/Parser/FieldIdentificationVariable.php create mode 100644 lib/Doctrine/ORM/Query/Parser/FromClause.php create mode 100644 lib/Doctrine/ORM/Query/Parser/IdentificationVariable.php create mode 100644 lib/Doctrine/ORM/Query/Parser/IdentificationVariableDeclaration.php create mode 100644 lib/Doctrine/ORM/Query/Parser/IndexBy.php create mode 100644 lib/Doctrine/ORM/Query/Parser/Join.php create mode 100644 lib/Doctrine/ORM/Query/Parser/JoinVariableDeclaration.php create mode 100644 lib/Doctrine/ORM/Query/Parser/QueryLanguage.php create mode 100644 lib/Doctrine/ORM/Query/Parser/RangeVariableDeclaration.php create mode 100644 lib/Doctrine/ORM/Query/Parser/SelectClause.php create mode 100644 lib/Doctrine/ORM/Query/Parser/SelectExpression.php create mode 100644 lib/Doctrine/ORM/Query/Parser/SelectStatement.php create mode 100644 lib/Doctrine/ORM/Query/Parser/SimpleStateField.php create mode 100644 lib/Doctrine/ORM/Query/Parser/SimpleStateFieldPathExpression.php create mode 100644 lib/Doctrine/ORM/Query/Parser/UpdateStatement.php create mode 100644 lib/Doctrine/ORM/Query/ParserParamHolder.php create mode 100644 lib/Doctrine/ORM/Query/ParserResult.php create mode 100644 lib/Doctrine/ORM/Query/ParserResultDummy.php create mode 100644 lib/Doctrine/ORM/Query/ParserRule.php create mode 100644 lib/Doctrine/ORM/Query/Printer.php create mode 100644 lib/Doctrine/ORM/Query/Production/AggregateExpression.php create mode 100644 lib/Doctrine/ORM/Query/Production/Atom.php create mode 100644 lib/Doctrine/ORM/Query/Production/BetweenExpression.php create mode 100644 lib/Doctrine/ORM/Query/Production/ComparisonExpression.php create mode 100644 lib/Doctrine/ORM/Query/Production/ComparisonOperator.php create mode 100644 lib/Doctrine/ORM/Query/Production/ConditionalExpression.php create mode 100644 lib/Doctrine/ORM/Query/Production/ConditionalFactor.php create mode 100644 lib/Doctrine/ORM/Query/Production/ConditionalPrimary.php create mode 100644 lib/Doctrine/ORM/Query/Production/ConditionalTerm.php create mode 100644 lib/Doctrine/ORM/Query/Production/DeleteClause.php create mode 100644 lib/Doctrine/ORM/Query/Production/DeleteStatement.php create mode 100644 lib/Doctrine/ORM/Query/Production/ExistsExpression.php create mode 100644 lib/Doctrine/ORM/Query/Production/Expression.php create mode 100644 lib/Doctrine/ORM/Query/Production/Factor.php create mode 100644 lib/Doctrine/ORM/Query/Production/FieldIdentificationVariable.php create mode 100644 lib/Doctrine/ORM/Query/Production/FromClause.php create mode 100644 lib/Doctrine/ORM/Query/Production/Function.php create mode 100644 lib/Doctrine/ORM/Query/Production/GroupByClause.php create mode 100644 lib/Doctrine/ORM/Query/Production/GroupByItem.php create mode 100644 lib/Doctrine/ORM/Query/Production/HavingClause.php create mode 100644 lib/Doctrine/ORM/Query/Production/IdentificationVariable.php create mode 100644 lib/Doctrine/ORM/Query/Production/IdentificationVariableDeclaration.php create mode 100644 lib/Doctrine/ORM/Query/Production/InExpression.php create mode 100644 lib/Doctrine/ORM/Query/Production/IndexBy.php create mode 100644 lib/Doctrine/ORM/Query/Production/Join.php create mode 100644 lib/Doctrine/ORM/Query/Production/JoinVariableDeclaration.php create mode 100644 lib/Doctrine/ORM/Query/Production/LikeExpression.php create mode 100644 lib/Doctrine/ORM/Query/Production/LimitClause.php create mode 100644 lib/Doctrine/ORM/Query/Production/NullComparisonExpression.php create mode 100644 lib/Doctrine/ORM/Query/Production/OffsetClause.php create mode 100644 lib/Doctrine/ORM/Query/Production/OrderByClause.php create mode 100644 lib/Doctrine/ORM/Query/Production/OrderByItem.php create mode 100644 lib/Doctrine/ORM/Query/Production/PathExpression.php create mode 100644 lib/Doctrine/ORM/Query/Production/PathExpressionEndingWithAsterisk.php create mode 100644 lib/Doctrine/ORM/Query/Production/Primary.php create mode 100644 lib/Doctrine/ORM/Query/Production/QuantifiedExpression.php create mode 100644 lib/Doctrine/ORM/Query/Production/QueryLanguage.php create mode 100644 lib/Doctrine/ORM/Query/Production/RangeVariableDeclaration.php create mode 100644 lib/Doctrine/ORM/Query/Production/SelectClause.php create mode 100644 lib/Doctrine/ORM/Query/Production/SelectExpression.php create mode 100644 lib/Doctrine/ORM/Query/Production/SelectStatement.php create mode 100644 lib/Doctrine/ORM/Query/Production/SimpleConditionalExpression.php create mode 100644 lib/Doctrine/ORM/Query/Production/SimpleSelectClause.php create mode 100644 lib/Doctrine/ORM/Query/Production/Subselect.php create mode 100644 lib/Doctrine/ORM/Query/Production/Term.php create mode 100644 lib/Doctrine/ORM/Query/Production/UpdateClause.php create mode 100644 lib/Doctrine/ORM/Query/Production/UpdateItem.php create mode 100644 lib/Doctrine/ORM/Query/Production/UpdateStatement.php create mode 100644 lib/Doctrine/ORM/Query/Production/VariableDeclaration.php create mode 100644 lib/Doctrine/ORM/Query/Production/WhereClause.php create mode 100644 lib/Doctrine/ORM/Query/QueryResult.php create mode 100644 lib/Doctrine/ORM/Query/Scanner.php create mode 100644 lib/Doctrine/ORM/Query/SqlBuilder.php create mode 100644 lib/Doctrine/ORM/Query/SqlBuilder/Mysql.php create mode 100644 lib/Doctrine/ORM/Query/SqlBuilder/Sqlite.php create mode 100644 lib/Doctrine/ORM/Query/SqlExecutor/Abstract.php create mode 100644 lib/Doctrine/ORM/Query/SqlExecutor/MultiTableDelete.php create mode 100644 lib/Doctrine/ORM/Query/SqlExecutor/MultiTableUpdate.php create mode 100644 lib/Doctrine/ORM/Query/SqlExecutor/SingleSelect.php create mode 100644 lib/Doctrine/ORM/Query/SqlExecutor/SingleTableDeleteUpdate.php create mode 100644 lib/Doctrine/ORM/Query/Token.php diff --git a/lib/Doctrine/ORM/Query/AST.php b/lib/Doctrine/ORM/Query/AST.php new file mode 100644 index 000000000..3616f1be2 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST.php @@ -0,0 +1,41 @@ +. + */ + +/** + * Abstract class of an AST node + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +abstract class Doctrine_ORM_Query_AST +{ + protected $_parserResult = null; + + + public function __construct(Doctrine_ORM_Query_ParserResult $parserResult) + { + $this->_parserResult = $parserResult; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/AbstractSchemaName.php b/lib/Doctrine/ORM/Query/AST/AbstractSchemaName.php new file mode 100644 index 000000000..569480b50 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/AbstractSchemaName.php @@ -0,0 +1,48 @@ +. + */ + +/** + * AbstractSchemaName ::= identifier + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_AST_AbstractSchemaName extends Doctrine_ORM_Query_AST +{ + protected $_componentName; + + + /* Setters */ + public function setComponentName($componentName) + { + $this->_componentName = $componentName; + } + + + /* Getters */ + public function getComponentName() + { + return $this->_componentName; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/AliasIdentificationVariable.php b/lib/Doctrine/ORM/Query/AST/AliasIdentificationVariable.php new file mode 100644 index 000000000..4db49e7d0 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/AliasIdentificationVariable.php @@ -0,0 +1,48 @@ +. + */ + +/** + * AliasIdentificationVariable ::= identifier + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_AST_AliasIdentificationVariable extends Doctrine_ORM_Query_AST +{ + protected $_componentAlias; + + + /* Setters */ + public function setComponentAlias($componentAlias) + { + $this->_componentAlias = $componentAlias; + } + + + /* Getters */ + public function getComponentAlias() + { + return $this->_componentAlias; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/DeleteStatement.php b/lib/Doctrine/ORM/Query/AST/DeleteStatement.php new file mode 100644 index 000000000..df3d4856c --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/DeleteStatement.php @@ -0,0 +1,73 @@ +. + */ + +/** + * DeleteStatement = DeleteClause [WhereClause] + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_AST_DeleteStatement extends Doctrine_ORM_Query_AST +{ + protected $_deleteClause; + + protected $_whereClause; + + + /* Setters */ + public function setDeleteClause($deleteClause) + { + $this->_deleteClause = $deleteClause; + } + + + public function setWhereClause($whereClause) + { + $this->_whereClause = $whereClause; + } + + + /* Getters */ + public function getDeleteClause() + { + return $this->_deleteClause; + } + + + public function getWhereClause() + { + return $this->_whereClause; + } + + + /* REMOVE ME LATER. COPIED METHODS FROM SPLIT OF PRODUCTION INTO "AST" AND "PARSER" */ + + public function buildSql() + { + // The 1=1 is needed to workaround the affected_rows in MySQL. + // Simple "DELETE FROM table_name" gives 0 affected rows. + return $this->_deleteClause->buildSql() . (($this->_whereClause !== null) + ? ' ' . $this->_whereClause->buildSql() : ' WHERE 1 = 1'); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/FieldIdentificationVariable.php b/lib/Doctrine/ORM/Query/AST/FieldIdentificationVariable.php new file mode 100644 index 000000000..5ba81a262 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/FieldIdentificationVariable.php @@ -0,0 +1,48 @@ +. + */ + +/** + * FieldIdentificationVariable ::= identifier + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_AST_FieldIdentificationVariable extends Doctrine_ORM_Query_AST +{ + protected $_fieldName; + + + /* Setters */ + public function setFieldName($fieldName) + { + $this->_fieldName = $fieldName; + } + + + /* Getters */ + public function getFieldName() + { + return $this->_fieldName; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/FromClause.php b/lib/Doctrine/ORM/Query/AST/FromClause.php new file mode 100644 index 000000000..42effd6dd --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/FromClause.php @@ -0,0 +1,85 @@ +. + */ + +/** + * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration} + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_AST_FromClause extends Doctrine_ORM_Query_AST +{ + protected $_identificationVariableDeclarations = array(); + + + /* Setters */ + public function addIdentificationVariableDeclaration($identificationVariableDeclaration) + { + $this->_identificationVariableDeclarations[] = $identificationVariableDeclaration; + } + + + public function setIdentificationVariableDeclarations($identificationVariableDeclarations, $append = false) + { + $this->_selectExpressions = ($append === true) + ? array_merge($this->_identificationVariableDeclarations, $identificationVariableDeclarations) + : $identificationVariableDeclarations; + } + + + /* Getters */ + public function getIdentificationVariableDeclarations() + { + return $this->_identificationVariableDeclarations; + } + + + /* REMOVE ME LATER. COPIED METHODS FROM SPLIT OF PRODUCTION INTO "AST" AND "PARSER" */ + + public function buildSql() + { + //echo "FromClause:\n"; + //for ($i = 0; $i < count($this->_identificationVariableDeclaration);$i++) { + // echo (($this->_identificationVariableDeclaration[$i] instanceof IdentificationVariableDeclaration) + // ? get_class($this->_identificationVariableDeclaration[$i]) + // : get_class($this->_identificationVariableDeclaration[$i])) . "\n"; + //} + + return 'FROM ' . implode(', ', $this->_mapIdentificationVariableDeclarations()); + } + + + protected function _mapIdentificationVariableDeclarations() + { + return array_map( + array(&$this, '_mapIdentificationVariableDeclaration'), $this->_identificationVariableDeclarations + ); + } + + + protected function _mapIdentificationVariableDeclaration($value) + { + return $value->buildSql(); + } +} diff --git a/lib/Doctrine/ORM/Query/AST/IdentificationVariable.php b/lib/Doctrine/ORM/Query/AST/IdentificationVariable.php new file mode 100644 index 000000000..801e9f3fd --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/IdentificationVariable.php @@ -0,0 +1,60 @@ +. + */ + +/** + * IdentificationVariable ::= identifier + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_AST_IdentificationVariable extends Doctrine_ORM_Query_AST +{ + protected $_componentAlias; + + + /* Setters */ + public function setComponentAlias($componentAlias) + { + $this->_componentAlias = $componentAlias; + } + + + /* Getters */ + public function getComponentAlias() + { + return $this->_componentAlias; + } + + + /* REMOVE ME LATER. COPIED METHODS FROM SPLIT OF PRODUCTION INTO "AST" AND "PARSER" */ + + public function buildSql() + { + $conn = $this->_parserResult->getEntityManager()->getConnection(); + + return $conn->quoteIdentifier( + $this->_parserResult->getTableAliasFromComponentAlias($this->_componentAlias) + ); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/IdentificationVariableDeclaration.php b/lib/Doctrine/ORM/Query/AST/IdentificationVariableDeclaration.php new file mode 100644 index 000000000..c53f5b012 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/IdentificationVariableDeclaration.php @@ -0,0 +1,98 @@ +. + */ + +/** + * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_AST_IdentificationVariableDeclaration extends Doctrine_ORM_Query_AST +{ + protected $_rangeVariableDeclaration = null; + + protected $_indexBy = null; + + protected $_joinVariableDeclarations = array(); + + + /* Setters */ + public function setRangeVariableDeclaration($rangeVariableDeclaration) + { + $this->_rangeVariableDeclaration = $rangeVariableDeclaration; + } + + + public function setIndexBy($indexBy) + { + $this->_indexBy = $indexBy; + } + + + public function addJoinVariableDeclaration($joinVariableDeclaration) + { + $this->_joinVariableDeclarations[] = $joinVariableDeclaration; + } + + + public function setJoinVariableDeclarations($joinVariableDeclarations, $append = false) + { + $this->_joinVariableDeclarations = ($append === true) + ? array_merge($this->_joinVariableDeclarations, $joinVariableDeclarations) + : $joinVariableDeclarations; + } + + + /* Getters */ + public function getRangeVariableDeclaration() + { + return $this->_rangeVariableDeclaration; + } + + + public function getIndexBy() + { + return $this->_indexBy; + } + + + public function getJoinVariableDeclarations() + { + return $this->_joinVariableDeclarations; + } + + + /* REMOVE ME LATER. COPIED METHODS FROM SPLIT OF PRODUCTION INTO "AST" AND "PARSER" */ + + public function buildSql() + { + $str = $this->_rangeVariableDeclaration->buildSql(); + + for ($i = 0, $l = count($this->_joinVariableDeclarations); $i < $l; $i++) { + $str .= ' ' . $this->_joinVariableDeclarations[$i]->buildSql(); + } + + return $str; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/IndexBy.php b/lib/Doctrine/ORM/Query/AST/IndexBy.php new file mode 100644 index 000000000..738dd8182 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/IndexBy.php @@ -0,0 +1,48 @@ +. + */ + +/** + * IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_AST_IndexBy extends Doctrine_ORM_Query_AST +{ + protected $_simpleStateFieldPathExpression = null; + + + /* Setters */ + public function setSimpleStateFieldPathExpression($simpleStateFieldPathExpression) + { + $this->_simpleStateFieldPathExpression = $simpleStateFieldPathExpression; + } + + + /* Getters */ + public function getSimpleStateFieldPathExpression() + { + return $this->_simpleStateFieldPathExpression; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/Join.php b/lib/Doctrine/ORM/Query/AST/Join.php new file mode 100644 index 000000000..8a8a70653 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/Join.php @@ -0,0 +1,203 @@ +. + */ + +/** + * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression + * ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression] + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_AST_Join extends Doctrine_ORM_Query_AST +{ + const JOIN_TYPE_LEFT = 1; + + const JOIN_TYPE_LEFTOUTER = 2; + + const JOIN_TYPE_INNER = 3; + + + const JOIN_WHERE_ON = 1; + + const JOIN_WHERE_WITH = 2; + + + protected $_joinType = self::JOIN_TYPE_INNER; + + protected $_joinAssociationPathExpression = null; + + protected $_aliasIdentificationVariable = null; + + protected $_whereType = self::JOIN_WHERE_WITH; + + protected $_conditionalExpression = null; + + + /* Setters */ + public function setJoinType($joinType) + { + $this->_joinType = $joinType; + } + + public function setJoinAssociationPathExpression($joinAssociationPathExpression) + { + $this->_joinAssociationPathExpression = $joinAssociationPathExpression; + } + + public function setAliasIdentificationVariable($aliasIdentificationVariable) + { + $this->_aliasIdentificationVariable = $aliasIdentificationVariable; + } + + public function setWhereType($whereType) + { + $this->_whereType = $whereType; + } + + public function setConditionalExpression($conditionalExpression) + { + $this->_conditionalExpression = $conditionalExpression; + } + + + /* Getters */ + public function getJoinType() + { + return $this->_joinType; + } + + public function getJoinAssociationPathExpression() + { + return $this->_joinAssociationPathExpression; + } + + public function getAliasIdentificationVariable() + { + return $this->_aliasIdentificationVariable; + } + + public function getWhereType() + { + return $this->_whereType; + } + + public function getConditionalExpression() + { + return $this->_conditionalExpression; + } + + + /* REMOVE ME LATER. COPIED METHODS FROM SPLIT OF PRODUCTION INTO "AST" AND "PARSER" */ + + public function buildSql() + { + $join = ''; + + switch ($this->_joinType) { + case self::JOIN_TYPE_LEFT: + $join .= 'LEFT'; + break; + + case self::JOIN_TYPE_LEFTOUTER: + $join .= 'LEFT OUTER'; + break; + + case self::JOIN_TYPE_INNER: + default: + $join .= 'INNER'; + break; + } + + $join .= ' JOIN ' . $this->_joinAssociationPathExpression->buildSql(); + $condition = isset($this->_conditionalExpression) + ? $this->_conditionalExpression->buildSql() : ''; + + switch ($this->whereType) { + case self::JOIN_WHERE_ON: + // Nothing to do here... =) + break; + + case self::JOIN_WHERE_WITH: + default: + + /* + + TODO: Refactor to support split!!! + + $parserResult = $this->_parser->getParserResult(); + + // Get the connection for the component + $conn = $this->_em->getConnection(); + + // We need to build the join conditions. Retrieving AssociationMapping + $queryComponent = $this->_rangeVariableDeclaration->getQueryComponent(); + $association = $queryComponent['relation']; + $joinColumns = array(); + + if ($association->isOneToMany() || $association->isOneToOne()) { + if ($association->isInverseSide()) { + // joinColumns are found on the other (owning) side + $targetClass = $this->_em->getClassMetadata($association->getTargetEntityName()); + $joinColumns = $targetClass->getAssociationMapping($association->getMappedByFieldName()) + ->getTargetToSourceKeyColumns(); + } else { + $joinColumns = $association->getSourceToTargetKeyColumns(); + } + } else { + //TODO: many-many + } + + $relationConditionExpression = ''; + + // We have an array('localColumn' => 'foreignColumn', ...) here + foreach ($joinColumns as $localColumn => $foreignColumn) { + // leftExpression = rightExpression + + // Defining leftExpression + $leftExpression = $conn->quoteIdentifier( + $parserResult->getTableAliasFromComponentAlias($queryComponent['parent']) . '.' . $localColumn + ); + + // Defining rightExpression + $rightExpression = $conn->quoteIdentifier( + $parserResult->getTableAliasFromComponentAlias( + $this->_rangeVariableDeclaration->getIdentificationVariable() + ) . '.' . $foreignColumn + ); + + // Building the relation + $relationConditionExpression .= (($relationConditionExpression != '') ? ' AND ' : '') + . $leftExpression . ' = ' . $rightExpression; + } + + $sql .= ' ON ' . $relationConditionExpression; + $sql .= empty($conditionExpression) ? '' : ' AND (' . $conditionExpression . ')'; + */ + + break; + } + + return $join . ' ON ' . $condition; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/JoinVariableDeclaration.php b/lib/Doctrine/ORM/Query/AST/JoinVariableDeclaration.php new file mode 100644 index 000000000..e3a5f14ef --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/JoinVariableDeclaration.php @@ -0,0 +1,70 @@ +. + */ + +/** + * JoinVariableDeclaration ::= Join [IndexBy] + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_AST_JoinVariableDeclaration extends Doctrine_ORM_Query_AST +{ + protected $_join = null; + + protected $_indexBy = null; + + + /* Setters */ + public function setJoin($join) + { + $this->_join = $join; + } + + + public function setIndexBy($indexBy) + { + $this->_indexBy = $indexBy; + } + + + /* Getters */ + public function getJoin() + { + return $this->_join; + } + + + public function getIndexBy() + { + return $this->_indexBy; + } + + + /* REMOVE ME LATER. COPIED METHODS FROM SPLIT OF PRODUCTION INTO "AST" AND "PARSER" */ + + public function buildSql() + { + return $this->_join->buildSql() . (isset($this->_indexby) ? $this->_indexby->buildSql() . ' ' : ''); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/RangeVariableDeclaration.php b/lib/Doctrine/ORM/Query/AST/RangeVariableDeclaration.php new file mode 100644 index 000000000..93cb63db8 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/RangeVariableDeclaration.php @@ -0,0 +1,87 @@ +. + */ + +/** + * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_AST_RangeVariableDeclaration extends Doctrine_ORM_Query_AST +{ + protected $_abstractSchemaName = null; + + protected $_aliasIdentificationVariable = null; + + + /* Setters */ + public function setAbstractSchemaName($abstractSchemaName) + { + $this->_abstractSchemaName = $abstractSchemaName; + } + + + public function setAliasIdentificationVariable($aliasIdentificationVariable) + { + $this->_aliasIdentificationVariable = $aliasIdentificationVariable; + } + + + /* Getters */ + public function getAbstractSchemaName() + { + return $this->_abstractSchemaName; + } + + + public function getAliasIdentificationVariable() + { + return $this->_aliasIdentificationVariable; + } + + + /* REMOVE ME LATER. COPIED METHODS FROM SPLIT OF PRODUCTION INTO "AST" AND "PARSER" */ + + public function buildSql() + { + // Retrieving connection + $conn = $this->_parserResult->getEntityManager()->getConnection(); + + // Component alias + $componentAlias = $this->_aliasIdentificationVariable->getComponentAlias(); + + // Retrieving required information + try { + $queryComponent = $this->_parserResult->getQueryComponent($componentAlias); + $classMetadata = $queryComponent['metadata']; + } catch (Doctrine_Exception $e) { + $this->_parser->semanticalError($e->getMessage()); + + return; + } + + return $conn->quoteIdentifier($classMetadata->getTableName()) . ' ' + . $conn->quoteIdentifier($this->_parserResult->getTableAliasFromComponentAlias($componentAlias)); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/SelectClause.php b/lib/Doctrine/ORM/Query/AST/SelectClause.php new file mode 100644 index 000000000..b38c7636f --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/SelectClause.php @@ -0,0 +1,91 @@ +. + */ + +/** + * SelectClause = "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_AST_SelectClause extends Doctrine_ORM_Query_AST +{ + protected $_isDistinct; + + protected $_selectExpressions = array(); + + + /* Setters */ + public function setIsDistinct($value) + { + $this->_isDistinct = $value; + } + + + public function addSelectExpression($expression) + { + $this->_selectExpressions[] = $expression; + } + + + public function setSelectExpressions($expressions, $append = false) + { + $this->_selectExpressions = ($append === true) + ? array_merge($this->_selectExpressions, $expressions) + : $expressions; + } + + + /* Getters */ + public function isDistinct() + { + return $this->_isDistinct; + } + + + public function getSelectExpressions() + { + return $this->_selectExpressions; + } + + + /* REMOVE ME LATER. COPIED METHODS FROM SPLIT OF PRODUCTION INTO "AST" AND "PARSER" */ + + public function buildSql() + { + return 'SELECT ' . (($this->_isDistinct) ? 'DISTINCT ' : '') + . implode(', ', $this->_mapSelectExpressions()); + } + + + protected function _mapSelectExpressions() + { + return array_map(array(&$this, '_mapSelectExpression'), $this->_selectExpressions); + } + + + protected function _mapSelectExpression($value) + { + return $value->buildSql(); + } +} diff --git a/lib/Doctrine/ORM/Query/AST/SelectStatement.php b/lib/Doctrine/ORM/Query/AST/SelectStatement.php new file mode 100644 index 000000000..3c9026f19 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/SelectStatement.php @@ -0,0 +1,130 @@ +. + */ + +/** + * SelectStatement = SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_AST_SelectStatement extends Doctrine_ORM_Query_AST +{ + protected $_selectClause; + + protected $_fromClause; + + protected $_whereClause; + + protected $_groupByClause; + + protected $_havingClause; + + protected $_orderByClause; + + + /* Setters */ + public function setSelectClause($selectClause) + { + $this->_selectClause = $selectClause; + } + + + public function setFromClause($fromClause) + { + $this->_fromClause = $fromClause; + } + + + public function setWhereClause($whereClause) + { + $this->_whereClause = $whereClause; + } + + + public function setGroupByClause($groupByClause) + { + $this->_groupByClause = $groupByClause; + } + + + public function setHavingClause($havingClause) + { + $this->_havingClause = $havingClause; + } + + + public function setOrderByClause($orderByClause) + { + $this->_orderByClause = $orderByClause; + } + + + /* Getters */ + public function getSelectClause() + { + return $this->_selectClause; + } + + + public function getFromClause() + { + return $this->_fromClause; + } + + + public function getWhereClause() + { + return $this->_whereClause; + } + + + public function getGroupByClause() + { + return $this->_groupByClause; + } + + + public function getHavingClause() + { + return $this->_havingClause; + } + + + public function getOrderByClause() + { + return $this->_orderByClause; + } + + + /* REMOVE ME LATER. COPIED METHODS FROM SPLIT OF PRODUCTION INTO "AST" AND "PARSER" */ + + public function buildSql() + { + return $this->_selectClause->buildSql() . ' ' . $this->_fromClause->buildSql() + . (($this->_whereClause !== null) ? ' ' . $this->_whereClause->buildSql() : ' WHERE 1 = 1') + . (($this->_groupByClause !== null) ? ' ' . $this->_groupByClause->buildSql() : '') + . (($this->_havingClause !== null) ? ' ' . $this->_havingClause->buildSql() : '') + . (($this->_orderByClause !== null) ? ' ' . $this->_orderByClause->buildSql() : ''); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/SimpleStateFieldPathExpression.php b/lib/Doctrine/ORM/Query/AST/SimpleStateFieldPathExpression.php new file mode 100644 index 000000000..6178ea454 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/SimpleStateFieldPathExpression.php @@ -0,0 +1,62 @@ +. + */ + +/** + * SimpleStateFieldPathExpression ::= IdentificationVariable "." SimpleStateField + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_AST_SimpleStateFieldPathExpression extends Doctrine_ORM_Query_AST +{ + protected $_identificationVariable = null; + + protected $_simpleStateField = null; + + + /* Setters */ + public function setIdentificationVariable($identificationVariable) + { + $this->_identificationVariable = $identificationVariable; + } + + + public function setSimpleStateField($simpleStateField) + { + $this->_simpleStateField = $simpleStateField; + } + + + /* Getters */ + public function getIdentificationVariable() + { + return $this->_identificationVariable; + } + + + public function getSimpleStateField() + { + return $this->_simpleStateField; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/AST/UpdateStatetement.php b/lib/Doctrine/ORM/Query/AST/UpdateStatetement.php new file mode 100644 index 000000000..995aaa216 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AST/UpdateStatetement.php @@ -0,0 +1,73 @@ +. + */ + +/** + * UpdateStatement = UpdateClause [WhereClause] + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_AST_UpdateStatement extends Doctrine_ORM_Query_AST +{ + protected $_updateClause; + + protected $_whereClause; + + + /* Setters */ + public function setUpdateClause($updateClause) + { + $this->_updateClause = $updateClause; + } + + + public function setWhereClause($whereClause) + { + $this->_whereClause = $whereClause; + } + + + /* Getters */ + public function getUpdateClause() + { + return $this->_updateClause; + } + + + public function getWhereClause() + { + return $this->_whereClause; + } + + + /* REMOVE ME LATER. COPIED METHODS FROM SPLIT OF PRODUCTION INTO "AST" AND "PARSER" */ + + public function buildSql() + { + // The 1=1 is needed to workaround the affected_rows in MySQL. + // Simple "UPDATE table_name SET column_name = value" gives 0 affected rows. + return $this->_updateClause->buildSql() . (($this->_whereClause !== null) + ? ' ' . $this->_whereClause->buildSql() : ' WHERE 1 = 1'); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Abstract.php b/lib/Doctrine/ORM/Query/Abstract.php new file mode 100644 index 000000000..a903ca59a --- /dev/null +++ b/lib/Doctrine/ORM/Query/Abstract.php @@ -0,0 +1,1014 @@ +. + */ + +/** + * Doctrine_ORM_Query_Abstract + * + * @package Doctrine + * @subpackage Query + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.phpdoctrine.com + * @since 1.0 + * @version $Revision: 1393 $ + * @author Guilherme Blanco + * @author Konsta Vesterinen + * @todo See {@link Doctrine_ORM_Query} + */ +abstract class Doctrine_ORM_Query_Abstract +{ + /** + * QUERY TYPE CONSTANTS + */ + + /** + * Constant for SELECT queries. + */ + const SELECT = 0; + + /** + * Constant for DELETE queries. + */ + const DELETE = 1; + + /** + * Constant for UPDATE queries. + */ + const UPDATE = 2; + + /** + * @todo [TODO] Remove these ones (INSERT and CREATE)? + */ + + /** + * Constant for INSERT queries. + */ + //const INSERT = 3; + + /** + * Constant for CREATE queries. + */ + //const CREATE = 4; + + + /** + * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts. + */ + const STATE_CLEAN = 1; + + /** + * A query object is in state DIRTY when it has DQL parts that have not yet been + * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart + * is called. + */ + const STATE_DIRTY = 2; + + /** + * @todo [TODO] Remove these ones (DIRECT and LOCKED)? + */ + + /** + * A query is in DIRECT state when ... ? + */ + //const STATE_DIRECT = 3; + + /** + * A query object is on LOCKED state when ... ? + */ + //const STATE_LOCKED = 4; + + + /** + * @var integer $type Query type. + * + * @see Doctrine_ORM_Query::* constants + */ + protected $_type = self::SELECT; + + /** + * @var integer $_state The current state of this query. + */ + protected $_state = self::STATE_CLEAN; + + /** + * @var array $params Parameters of this query. + * @see Doctrine_ORM_Query::free that initializes this property + */ + protected $_params = array(); + + /** + * @var array $_enumParams Array containing the keys of the parameters that should be enumerated. + * @see Doctrine_ORM_Query::free that initializes this property + */ + protected $_enumParams = array(); + + /** + * @var array $_dqlParts An array containing all DQL query parts. + * @see Doctrine_ORM_Query::free that initializes this property + */ + protected $_dqlParts = array(); + + /** + * @var string $_dql Cached DQL query. + */ + protected $_dql = null; + + + /** + * Frees the resources used by the query object. It especially breaks a + * cyclic reference between the query object and it's parsers. This enables + * PHP's current GC to reclaim the memory. + * This method can therefore be used to reduce memory usage when creating a lot + * of query objects during a request. + */ + public function free() + { + /** + * @todo [TODO] What about "forUpdate" support? Remove it? + */ + $this->_dqlParts = array( + 'select' => array(), + 'distinct' => false, + 'forUpdate' => false, + 'from' => array(), + 'join' => array(), + 'set' => array(), + 'where' => array(), + 'groupby' => array(), + 'having' => array(), + 'orderby' => array(), + 'limit' => array(), + 'offset' => array(), + ); + + $this->_params = array( + 'join' => array(), + 'set' => array(), + 'where' => array(), + 'having' => array() + ); + + $this->_enumParams = array(); + + $this->_dql = null; + $this->_state = self::STATE_CLEAN; + } + + + /** + * Defines a complete DQL + * + * @param string $dqlQuery DQL Query + */ + public function setDql($dqlQuery) + { + $this->free(); + + if ($dqlQuery !== null) { + $this->_dql = $dqlQuery; + + $this->_state = self::STATE_DIRTY; + } + } + + + /** + * Returns the DQL query that is represented by this query object. + * + * @return string DQL query + */ + public function getDql() + { + if ($this->_dql !== null) { + return $this->_dql; + } + + $dql = ''; + + switch ($this->_type) { + case self::DELETE: + $dql = $this->_getDqlForDelete(); + break; + + case self::UPDATE: + $dql = $this->_getDqlForUpdate(); + break; + + /** + * @todo [TODO] Remove these ones (INSERT and CREATE)? + */ + /* + case self::INSERT: + break; + + case self::CREATE: + break; + */ + + case self::SELECT: + default: + $dql = $this->_getDqlForSelect(); + break; + } + + return $dql; + } + + + /** + * Builds the DQL of DELETE + */ + protected function _getDqlForDelete() + { + /* + * BNF: + * + * DeleteStatement = DeleteClause [WhereClause] [OrderByClause] [LimitClause] [OffsetClause] + * DeleteClause = "DELETE" "FROM" RangeVariableDeclaration + * WhereClause = "WHERE" ConditionalExpression + * OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem} + * LimitClause = "LIMIT" integer + * OffsetClause = "OFFSET" integer + * + */ + return 'DELETE' + . $this->_getReducedDqlQueryPart('from', array('pre' => ' FROM ', 'separator' => ' ')) + . $this->_getReducedDqlQueryPart('where', array('pre' => ' WHERE ', 'separator' => ' ')) + . $this->_getReducedDqlQueryPart('orderby', array('pre' => ' ORDER BY ', 'separator' => ', ')) + . $this->_getReducedDqlQueryPart('limit', array('pre' => ' LIMIT ', 'separator' => ' ')) + . $this->_getReducedDqlQueryPart('offset', array('pre' => ' OFFSET ', 'separator' => ' ')); + } + + + /** + * Builds the DQL of UPDATE + */ + protected function _getDqlForUpdate() + { + /* + * BNF: + * + * UpdateStatement = UpdateClause [WhereClause] [OrderByClause] [LimitClause] [OffsetClause] + * UpdateClause = "UPDATE" RangeVariableDeclaration "SET" UpdateItem {"," UpdateItem} + * WhereClause = "WHERE" ConditionalExpression + * OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem} + * LimitClause = "LIMIT" integer + * OffsetClause = "OFFSET" integer + * + */ + return 'UPDATE' + . $this->_getReducedDqlQueryPart('from', array('pre' => ' FROM ', 'separator' => ' ')) + . $this->_getReducedDqlQueryPart('where', array('pre' => ' SET ', 'separator' => ', ')) + . $this->_getReducedDqlQueryPart('where', array('pre' => ' WHERE ', 'separator' => ' ')) + . $this->_getReducedDqlQueryPart('orderby', array('pre' => ' ORDER BY ', 'separator' => ', ')) + . $this->_getReducedDqlQueryPart('limit', array('pre' => ' LIMIT ', 'separator' => ' ')) + . $this->_getReducedDqlQueryPart('offset', array('pre' => ' OFFSET ', 'separator' => ' ')); + } + + + /** + * Builds the DQL of SELECT + */ + protected function _getDqlForSelect() + { + /* + * BNF: + * + * SelectStatement = [SelectClause] FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] [LimitClause] [OffsetClause] + * SelectClause = "SELECT" ["ALL" | "DISTINCT"] SelectExpression {"," SelectExpression} + * FromClause = "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration} + * WhereClause = "WHERE" ConditionalExpression + * GroupByClause = "GROUP" "BY" GroupByItem {"," GroupByItem} + * HavingClause = "HAVING" ConditionalExpression + * OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem} + * LimitClause = "LIMIT" integer + * OffsetClause = "OFFSET" integer + * + */ + /** + * @todo [TODO] What about "ALL" support? + */ + return 'SELECT' + . (($this->getDqlQueryPart('distinct') === true) ? ' DISTINCT' : '') + . $this->_getReducedDqlQueryPart('select', array('pre' => ' ', 'separator' => ', ', 'empty' => ' *')) + . $this->_getReducedDqlQueryPart('from', array('pre' => ' FROM ', 'separator' => ' ')) + . $this->_getReducedDqlQueryPart('where', array('pre' => ' WHERE ', 'separator' => ' ')) + . $this->_getReducedDqlQueryPart('groupby', array('pre' => ' GROUP BY ', 'separator' => ', ')) + . $this->_getReducedDqlQueryPart('having', array('pre' => ' HAVING ', 'separator' => ' ')) + . $this->_getReducedDqlQueryPart('orderby', array('pre' => ' ORDER BY ', 'separator' => ', ')) + . $this->_getReducedDqlQueryPart('limit', array('pre' => ' LIMIT ', 'separator' => ' ')) + . $this->_getReducedDqlQueryPart('offset', array('pre' => ' OFFSET ', 'separator' => ' ')); + } + + + /** + * @nodoc + */ + protected function _getReducedDqlQueryPart($queryPartName, $options = array()) + { + if (empty($this->_dqlParts[$queryPartName])) { + return (isset($options['empty']) ? $options['empty'] : ''); + } + + $str = (isset($options['pre']) ? $options['pre'] : ''); + $str .= implode($options['separator'], $this->getDqlQueryPart($queryPartName)); + $str .= (isset($options['post']) ? $options['post'] : ''); + + return $str; + } + + /** + * Returns the type of this query object + * By default the type is Doctrine_ORM_Query_Abstract::SELECT but if update() or delete() + * are being called the type is Doctrine_ORM_Query_Abstract::UPDATE and Doctrine_ORM_Query_Abstract::DELETE, + * respectively. + * + * @see Doctrine_ORM_Query_Abstract::SELECT + * @see Doctrine_ORM_Query_Abstract::UPDATE + * @see Doctrine_ORM_Query_Abstract::DELETE + * + * @return integer Return the query type + */ + public function getType() + { + return $this->_type; + } + + + /** + * Returns the state of this query object + * By default the type is Doctrine_ORM_Query_Abstract::STATE_CLEAN but if it appears any unprocessed DQL + * part, it is switched to Doctrine_ORM_Query_Abstract::STATE_DIRTY. + * + * @see Doctrine_ORM_Query_Abstract::STATE_CLEAN + * @see Doctrine_ORM_Query_Abstract::STATE_DIRTY + * + * @return integer Return the query state + */ + public function getState() + { + return $this->_state; + } + + + /** + * Adds fields to the SELECT part of the query + * + * @param string $select Query SELECT part + * @return Doctrine_ORM_Query + */ + public function select($select = '', $override = false) + { + if ($select === '') { + return $this; + } + + return $this->_addDqlQueryPart('select', $select, ! $override); + } + + + /** + * Makes the query SELECT DISTINCT. + * + * @param bool $flag Whether or not the SELECT is DISTINCT (default true). + * @return Doctrine_ORM_Query + */ + public function distinct($flag = true) + { + $this->_dqlParts['distinct'] = (bool) $flag; + return $this; + } + + + /** + * Makes the query SELECT FOR UPDATE. + * + * @param bool $flag Whether or not the SELECT is FOR UPDATE (default true). + * @return Doctrine_ORM_Query + * + * @todo [TODO] What about "forUpdate" support? Remove it? + */ + public function forUpdate($flag = true) + { + return $this->_addDqlQueryPart('forUpdate', (bool) $flag); + } + + + /** + * Sets the query type to DELETE + * + * @return Doctrine_ORM_Query + */ + public function delete() + { + $this->_type = self::DELETE; + return $this; + } + + + /** + * Sets the UPDATE part of the query + * + * @param string $update Query UPDATE part + * @return Doctrine_ORM_Query + */ + public function update($update) + { + $this->_type = self::UPDATE; + return $this->_addDqlQueryPart('from', $update); + } + + + /** + * Sets the SET part of the query + * + * @param mixed $key UPDATE keys. Accepts either a string (requiring then $value or $params to be defined) + * or an array of $key => $value pairs. + * @param string $value UPDATE key value. Optional argument, but required if $key is a string. + * @return Doctrine_ORM_Query + */ + public function set($key, $value = null, $params = null) + { + if (is_array($key)) { + foreach ($key as $k => $v) { + $this->set($k, '?', array($v)); + } + + return $this; + } else { + if ($params !== null) { + if (is_array($params)) { + $this->_params['set'] = array_merge($this->_params['set'], $params); + } else { + $this->_params['set'][] = $params; + } + } + + if ($value === null) { + throw new Doctrine_ORM_Query_Exception( 'Cannot try to set \''.$key.'\' without a value.' ); + } + + return $this->_addDqlQueryPart('set', $key . ' = ' . $value, true); + } + } + + /** + * Adds fields to the FROM part of the query + * + * @param string $from Query FROM part + * @return Doctrine_ORM_Query + */ + public function from($from, $override = false) + { + return $this->_addDqlQueryPart('from', $from, ! $override); + } + + + /** + * Appends an INNER JOIN to the FROM part of the query + * + * @param string $join Query INNER JOIN + * @param mixed $params Optional JOIN params (array of parameters or a simple scalar) + * @return Doctrine_ORM_Query + */ + public function innerJoin($join, $params = array()) + { + if (is_array($params)) { + $this->_params['join'] = array_merge($this->_params['join'], $params); + } else { + $this->_params['join'][] = $params; + } + + return $this->_addDqlQueryPart('from', 'INNER JOIN ' . $join, true); + } + + + /** + * Appends an INNER JOIN to the FROM part of the query + * + * @param string $join Query INNER JOIN + * @param mixed $params Optional JOIN params (array of parameters or a simple scalar) + * @return Doctrine_ORM_Query + */ + public function join($join, $params = array()) + { + return $this->innerJoin($join, $params); + } + + + /** + * Appends a LEFT JOIN to the FROM part of the query + * + * @param string $join Query LEFT JOIN + * @param mixed $params Optional JOIN params (array of parameters or a simple scalar) + * @return Doctrine_ORM_Query + */ + public function leftJoin($join, $params = array()) + { + if (is_array($params)) { + $this->_params['join'] = array_merge($this->_params['join'], $params); + } else { + $this->_params['join'][] = $params; + } + + return $this->_addDqlQueryPart('from', 'LEFT JOIN ' . $join, true); + } + + + /** + * Adds conditions to the WHERE part of the query + * + * @param string $where Query WHERE part + * @param mixed $params An array of parameters or a simple scalar + * @return Doctrine_ORM_Query + */ + public function where($where, $params = array(), $override = false) + { + if ($override) { + $this->_params['where'] = array(); + } + + if (is_array($params)) { + $this->_params['where'] = array_merge($this->_params['where'], $params); + } else { + $this->_params['where'][] = $params; + } + + return $this->_addDqlQueryPart('where', $where, ! $override); + } + + + /** + * Adds conditions to the WHERE part of the query + * + * @param string $where Query WHERE part + * @param mixed $params An array of parameters or a simple scalar + * @return Doctrine_ORM_Query + */ + public function andWhere($where, $params = array(), $override = false) + { + if (count($this->getDqlQueryPart('where')) > 0) { + $this->_addDqlQueryPart('where', 'AND', true); + } + + return $this->where($where, $params, $override); + } + + + /** + * Adds conditions to the WHERE part of the query + * + * @param string $where Query WHERE part + * @param mixed $params An array of parameters or a simple scalar + * @return Doctrine_ORM_Query + */ + public function orWhere($where, $params = array(), $override = false) + { + if (count($this->getDqlQueryPart('where')) > 0) { + $this->_addDqlQueryPart('where', 'OR', true); + } + + return $this->where($where, $params, $override); + } + + + /** + * Adds IN condition to the query WHERE part + * + * @param string $expr The operand of the IN + * @param mixed $params An array of parameters or a simple scalar + * @param boolean $not Whether or not to use NOT in front of IN + * @return Doctrine_ORM_Query + */ + public function whereIn($expr, $params = array(), $override = false, $not = false) + { + $params = (array) $params; + + // Must have at least one param, otherwise we'll get an empty IN () => invalid SQL + if ( ! count($params)) { + return $this; + } + + list($sqlPart, $params) = $this->_processWhereInParams($params); + + $where = $expr . ($not === true ? ' NOT' : '') . ' IN (' . $sqlPart . ')'; + + return $this->_returnWhereIn($where, $params, $override); + } + + + /** + * Adds NOT IN condition to the query WHERE part + * + * @param string $expr The operand of the NOT IN + * @param mixed $params An array of parameters or a simple scalar + * @return Doctrine_ORM_Query + */ + public function whereNotIn($expr, $params = array(), $override = false) + { + return $this->whereIn($expr, $params, $override, true); + } + + + /** + * Adds IN condition to the query WHERE part + * + * @param string $expr The operand of the IN + * @param mixed $params An array of parameters or a simple scalar + * @param boolean $not Whether or not to use NOT in front of IN + * @return Doctrine_ORM_Query + */ + public function andWhereIn($expr, $params = array(), $override = false) + { + if (count($this->getDqlQueryPart('where')) > 0) { + $this->_addDqlQueryPart('where', 'AND', true); + } + + return $this->whereIn($expr, $params, $override); + } + + + /** + * Adds NOT IN condition to the query WHERE part + * + * @param string $expr The operand of the NOT IN + * @param mixed $params An array of parameters or a simple scalar + * @return Doctrine_ORM_Query + */ + public function andWhereNotIn($expr, $params = array(), $override = false) + { + if (count($this->getDqlQueryPart('where')) > 0) { + $this->_addDqlQueryPart('where', 'AND', true); + } + + return $this->whereIn($expr, $params, $override, true); + } + + + /** + * Adds IN condition to the query WHERE part + * + * @param string $expr The operand of the IN + * @param mixed $params An array of parameters or a simple scalar + * @param boolean $not Whether or not to use NOT in front of IN + * @return Doctrine_ORM_Query + */ + public function orWhereIn($expr, $params = array(), $override = false) + { + if (count($this->getDqlQueryPart('where')) > 0) { + $this->_addDqlQueryPart('where', 'OR', true); + } + + return $this->whereIn($expr, $params, $override); + } + + + /** + * Adds NOT IN condition to the query WHERE part + * + * @param string $expr The operand of the NOT IN + * @param mixed $params An array of parameters or a simple scalar + * @return Doctrine_ORM_Query + */ + public function orWhereNotIn($expr, $params = array(), $override = false) + { + if (count($this->getDqlQueryPart('where')) > 0) { + $this->_addDqlQueryPart('where', 'OR', true); + } + + return $this->whereIn($expr, $params, $override, true); + } + + + /** + * Adds fields to the GROUP BY part of the query + * + * @param string $groupby Query GROUP BY part + * @return Doctrine_ORM_Query + */ + public function groupBy($groupby, $override = false) + { + return $this->_addDqlQueryPart('groupby', $groupby, ! $override); + } + + + /** + * Adds conditions to the HAVING part of the query + * + * @param string $having Query HAVING part + * @param mixed $params An array of parameters or a simple scalar + * @return Doctrine_ORM_Query + */ + public function having($having, $params = array(), $override = false) + { + if ($override) { + $this->_params['having'] = array(); + } + + if (is_array($params)) { + $this->_params['having'] = array_merge($this->_params['having'], $params); + } else { + $this->_params['having'][] = $params; + } + + return $this->_addDqlQueryPart('having', $having, true); + } + + + /** + * Adds conditions to the HAVING part of the query + * + * @param string $having Query HAVING part + * @param mixed $params An array of parameters or a simple scalar + * @return Doctrine_ORM_Query + */ + public function andHaving($having, $params = array(), $override = false) + { + if (count($this->getDqlQueryPart('having')) > 0) { + $this->_addDqlQueryPart('having', 'AND', true); + } + + return $this->having($having, $params, $override); + } + + + /** + * Adds conditions to the HAVING part of the query + * + * @param string $having Query HAVING part + * @param mixed $params An array of parameters or a simple scalar + * @return Doctrine_ORM_Query + */ + public function orHaving($having, $params = array(), $override = false) + { + if (count($this->getDqlQueryPart('having')) > 0) { + $this->_addDqlQueryPart('having', 'OR', true); + } + + return $this->having($having, $params, $override); + } + + + /** + * Adds fields to the ORDER BY part of the query + * + * @param string $orderby Query ORDER BY part + * @return Doctrine_ORM_Query + */ + public function orderBy($orderby, $override = false) + { + return $this->_addDqlQueryPart('orderby', $orderby, ! $override); + } + + + /** + * Sets the Query query limit + * + * @param integer $limit Limit to be used for limiting the query results + * @return Doctrine_ORM_Query + */ + public function limit($limit) + { + return $this->_addDqlQueryPart('limit', $limit); + } + + + /** + * Sets the Query query offset + * + * @param integer $offset Offset to be used for paginating the query + * @return Doctrine_ORM_Query + */ + public function offset($offset) + { + return $this->_addDqlQueryPart('offset', $offset); + } + + + /** + * Set enumerated parameters + * + * @param array $enumParams Enum parameters. + */ + protected function _setEnumParams($enumParams = array()) + { + $this->_enumParams = $enumParams; + } + + + /** + * Get all enumerated parameters + * + * @return array All enumerated parameters + */ + public function getEnumParams() + { + return $this->_enumParams; + } + + + /** + * Convert ENUM parameters to their integer equivalents + * + * @param $params Parameters to be converted + * @return array Converted parameters array + */ + public function convertEnums($params) + { + foreach ($this->_enumParams as $key => $values) { + if (isset($params[$key]) && ! empty($values)) { + $params[$key] = $values[0]->enumIndex($values[1], $params[$key]); + } + } + + return $params; + } + + + /** + * Get all defined parameters + * + * @return array Defined parameters + */ + public function getParams($params = array()) + { + return array_merge( + $this->_params['join'], + $this->_params['set'], + $this->_params['where'], + $this->_params['having'], + $params + ); + } + + + /** + * setParams + * + * @param array $params + */ + public function setParams(array $params = array()) { + $this->_params = $params; + } + + + /** + * Method to check if a arbitrary piece of DQL exists + * + * @param string $dql Arbitrary piece of DQL to check for + * @return boolean + */ + public function contains($dql) + { + return stripos($this->getDql(), $dql) === false ? false : true; + } + + + /** + * Retrieve a DQL part for internal purposes + * + * @param string $queryPartName The name of the query part. + * @return mixed Array related to query part or simple scalar + */ + public function getDqlQueryPart($queryPartName) + { + if ( ! isset($this->_dqlParts[$queryPartName])) { + throw new Doctrine_ORM_Query_Exception('Unknown DQL query part \'' . $queryPartName . '\''); + } + + return $this->_dqlParts[$queryPartName]; + } + + + /** + * Adds a DQL part to the internal parts collection. + * + * @param string $queryPartName The name of the query part. + * @param string $queryPart The actual query part to add. + * @param boolean $append Whether to append $queryPart to already existing + * parts under the same $queryPartName. Defaults to FALSE + * (previously added parts with the same name get overridden). + * @return Doctrine_ORM_Query + */ + protected function _addDqlQueryPart($queryPartName, $queryPart, $append = false) + { + if ($append) { + $this->_dqlParts[$queryPartName][] = $queryPart; + } else { + $this->_dqlParts[$queryPartName] = array($queryPart); + } + + $this->_state = Doctrine_ORM_Query::STATE_DIRTY; + return $this; + } + + + /** + * Processes the WHERE IN () parameters and return an indexed array containing + * the sqlPart to be placed in SQL statement and the new parameters (that will be + * bound in SQL execution) + * + * @param array $params Parameters to be processed + * @return array + */ + protected function _processWhereInParams($params = array()) + { + return array( + // [0] => sqlPart + implode(', ', array_map(array(&$this, '_processWhereInSqlPart'), $params)), + // [1] => params + array_filter($params, array(&$this, '_processWhereInParamItem')), + ); + } + + + /** + * @nodoc + */ + protected function _processWhereInSqlPart($value) + { + // [TODO] Add support to imbricated query (must deliver the hardest effort to Parser) + return ($value instanceof Doctrine_Expression) ? $value->getSql() : '?'; + } + + + /** + * @nodoc + */ + protected function _processWhereInParamItem($value) + { + // [TODO] Add support to imbricated query (must deliver the hardest effort to Parser) + return ( ! ($value instanceof Doctrine_Expression)); + } + + + /** + * Processes a WHERE IN () and build defined stuff to add in DQL + * + * @param string $where The WHERE clause to be added + * @param array $params WHERE clause parameters + * @param mixed $appender Where this clause may be not be appended, or appended + * (two possible values: AND or OR) + * @return Doctrine_ORM_Query + */ + protected function _returnWhereIn($where, $params = array(), $override = false) + { + // Parameters inclusion + $this->_params['where'] = $override ? $params : array_merge($this->_params['where'], $params); + + // WHERE clause definition + return $this->_addDqlQueryPart('where', $where, ! $override); + } + + + /** + * Gets the SQL query that corresponds to this query object. + * The returned SQL syntax depends on the connection driver that is used + * by this query object at the time of this method call. + * + * @return string SQL query + */ + abstract public function getSql(); + + /** + * Sets a query parameter. + * + * @param string|integer $key + * @param mixed $value + */ + public function setParameter($key, $value) + { + $this->_params[$key] = $value; + } + + /** + * Sets a collection of query parameters. + * + * @param array $params + */ + public function setParameters(array $params) + { + foreach ($params as $key => $value) { + $this->setParameter($key, $value); + } + } + +} diff --git a/lib/Doctrine/ORM/Query/AbstractResult.php b/lib/Doctrine/ORM/Query/AbstractResult.php new file mode 100644 index 000000000..dff5da695 --- /dev/null +++ b/lib/Doctrine/ORM/Query/AbstractResult.php @@ -0,0 +1,276 @@ +. + */ + +/** + * Doctrine_ORM_Query_AbstractResult + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.phpdoctrine.com + * @since 2.0 + * @version $Revision: 1393 $ + * @author Guilherme Blanco + * @author Konsta Vesterinen + */ +abstract class Doctrine_ORM_Query_AbstractResult +{ + /** + * @var mixed $_data The actual data to be stored. Can be an array, a string or an integer. + */ + protected $_data; + + /** + * @var array $_queryComponents + * + * Two dimensional array containing the map for query aliases. Main keys are component aliases. + * + * table Table object associated with given alias. + * relation Relation object owned by the parent. + * parent Alias of the parent. + * agg Aggregates of this component. + * map Name of the column / aggregate value this component is mapped to a collection. + */ + protected $_queryComponents; + + /** + * @var array Table alias map. Keys are SQL aliases and values DQL aliases. + */ + protected $_tableAliasMap; + + /** + * @var array Enum params. + */ + protected $_enumParams; + + + /** + * Cannot be called directly, factory methods handle this job. + * + * @param mixed $data Data to be stored. + * @param array $queryComponents Query components. + * @param array $tableAliasMap Table aliases. + * @param array $enumParams Enum params. + * @return Doctrine_ORM_Query_CacheHandler + */ + public function __construct($data = '', $queryComponents = array(), $tableAliasMap = array(), $enumParams = array()) + { + $this->_data = $data; + $this->_queryComponents = $queryComponents; + $this->_tableAliasMap = $tableAliasMap; + $this->_enumParams = $enumParams; + } + + + /** + * Defines the mapping components. + * + * @param array $queryComponents Query components. + */ + public function setQueryComponents(array $queryComponents) + { + $this->_queryComponents = $queryComponents; + } + + + /** + * Sets the declaration for given component alias. + * + * @param string $componentAlias The component alias to set the declaration to. + * @param string $queryComponent Alias declaration. + */ + public function setQueryComponent($componentAlias, array $queryComponent) + { + $this->_queryComponents[$componentAlias] = $queryComponent; + } + + + /** + * Gets the mapping components. + * + * @return array Query components. + */ + public function getQueryComponents() + { + return $this->_queryComponents; + } + + + /** + * Get the declaration for given component alias. + * + * @param string $componentAlias The component alias the retrieve the declaration from. + * @return array Alias declaration. + */ + public function getQueryComponent($componentAlias) + { + if ( ! array_key_exists($componentAlias, $this->_queryComponents)) { + throw new Doctrine_ORM_Query_Exception('Unknown query component ' . $componentAlias); + } + + return $this->_queryComponents[$componentAlias]; + } + + + /** + * Get the component alias for a given query component + * + * @param array $queryComponent The query component + * @param string Component alias + */ + public function getComponentAlias($queryComponent) + { + return array_search($queryComponent, $this->_queryComponents);; + } + + + /** + * Whether or not this object has a declaration for given component alias. + * + * @param string $componentAlias Component alias the retrieve the declaration from. + * @return boolean True if this object has given alias, otherwise false. + */ + public function hasQueryComponent($componentAlias) + { + return isset($this->_queryComponents[$componentAlias]); + } + + + /** + * Defines the table aliases. + * + * @param array $tableAliasMap Table aliases. + */ + public function setTableAliasMap(array $tableAliasMap) + { + $this->_tableAliasMap = $tableAliasMap; + } + + + /** + * Adds an SQL table alias and associates it a component alias + * + * @param string $tableAlias Table alias to be added. + * @param string $componentAlias Alias for the query component associated with given tableAlias. + */ + public function setTableAlias($tableAlias, $componentAlias) + { + $this->_tableAliasMap[$tableAlias] = $componentAlias; + } + + + /** + * Returns all table aliases. + * + * @return array Table aliases as an array. + */ + public function getTableAliasMap() + { + return $this->_tableAliasMap; + } + + + /** + * Get component alias associated with given table alias. + * + * @param string $tableAlias SQL table alias that identifies the component alias + * @return string Component alias + */ + public function getTableAlias($tableAlias) + { + if ( ! isset($this->_tableAliasMap[$tableAlias])) { + throw new Doctrine_ORM_Query_Exception('Unknown table alias ' . $tableAlias); + } + + return $this->_tableAliasMap[$tableAlias]; + } + + + /** + * Get table alias associated with given component alias. + * + * @param string $componentAlias Component alias that identifies the table alias + * @return string Component alias + */ + public function getTableAliasFromComponentAlias($componentAlias) + { + return array_search($componentAlias, $this->_tableAliasMap); + } + + + /** + * Whether or not this object has given tableAlias. + * + * @param string $tableAlias Table alias to be checked. + * @return boolean True if this object has given alias, otherwise false. + */ + public function hasTableAlias($tableAlias) + { + return (isset($this->_tableAliasMap[$tableAlias])); + } + + + /** + * Returns the enum parameters. + * + * @return mixed Enum parameters. + */ + public function getEnumParams() + { + return $this->_enumParams; + } + + + /** + * Sets input parameter as an enumerated parameter + * + * @param string $key The key of the input parameter + * @return Doctrine_ORM_Query_AbstractResult + */ + public function addEnumParam($key, $table = null, $column = null) + { + $array = (isset($table) || isset($column)) ? array($table, $column) : array(); + + if ($key === '?') { + $this->_enumParams[] = $array; + } else { + $this->_enumParams[$key] = $array; + } + + return $this; + } + + + /** + * Returns this object in serialized format, revertable using fromCached*. + * + * @return string Serialized cached item. + */ + public function toCachedForm() + { + return serialize(array( + $this->_data, + $this->getQueryComponents(), + $this->getTableAliasMap(), + $this->getEnumParams() + )); + } + +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/CacheHandler.php b/lib/Doctrine/ORM/Query/CacheHandler.php new file mode 100644 index 000000000..59b9af762 --- /dev/null +++ b/lib/Doctrine/ORM/Query/CacheHandler.php @@ -0,0 +1,150 @@ +. + */ + +/** + * Doctrine_ORM_Query_CacheHandler + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.phpdoctrine.com + * @since 1.0 + * @version $Revision: 1393 $ + * @author Guilherme Blanco + * @author Konsta Vesterinen + * + * @todo Re-document this class + */ +abstract class Doctrine_ORM_Query_CacheHandler +{ + /** + * Static factory method. Receives a Doctrine_ORM_Query object and generates + * the object after processing queryComponents. Table aliases are retrieved + * directly from Doctrine_ORM_Query_Parser. + * + * @param mixed $result Data to be stored. + * @param Doctrine_ORM_Query_ParserResult $parserResult Parser results that enables to have important data retrieved. + */ + public static function fromResultSet($result, $parserResult) + { + $queryComponents = array(); + + foreach ($parserResult->getQueryComponents() as $alias => $components) { + if ( ! isset($components['parent'])) { + $queryComponents[$alias][] = $components['mapper']->getComponentName(); + //$queryComponents[$alias][] = $components['mapper']->getComponentName(); + } else { + $queryComponents[$alias][] = $components['parent'] . '.' . $components['relation']->getAlias(); + } + + if (isset($components['agg'])) { + $queryComponents[$alias][] = $components['agg']; + } + + if (isset($components['map'])) { + $queryComponents[$alias][] = $components['map']; + } + } + + return new Doctrine_ORM_Query_QueryResult( + $result, + $queryComponents, + $parserResult->getTableAliasMap(), + $parserResult->getEnumParams() + ); + } + + + + /** + * Static factory method. Receives a Doctrine_ORM_Query object and a cached data. + * It handles the cache and generates the object after processing queryComponents. + * Table aliases are retrieved from cache. + * + * @param Doctrine_ORM_Query $query Doctrine_ORM_Query_Object related to this cache item. + * @param mixed $cached Cached data. + */ + public static function fromCachedResult($query, $cached = false) + { + $cached = unserialize($cached); + + return new Doctrine_ORM_Query_QueryResult( + $cached[0], + self::_getQueryComponents($cached[1]), + $cached[2], + $cached[3] + ); + } + + + /** + * Static factory method. Receives a Doctrine_ORM_Query object and a cached data. + * It handles the cache and generates the object after processing queryComponents. + * Table aliases are retrieved from cache. + * + * @param Doctrine_ORM_Query $query Doctrine_ORM_Query_Object related to this cache item. + * @param mixed $cached Cached data. + */ + public static function fromCachedQuery($query, $cached = false) + { + $cached = unserialize($cached); + + return new Doctrine_ORM_Query_ParserResult( + $cached[0], + self::_getQueryComponents($cached[1]), + $cached[2], + $cached[3] + ); + } + + + /** + * @nodoc + */ + protected static function _getQueryComponents($query, $cachedQueryComponents) + { + $queryComponents = array(); + + foreach ($cachedQueryComponents as $alias => $components) { + $e = explode('.', $components[0]); + + if (count($e) === 1) { + $queryComponents[$alias]['mapper'] = $query->getConnection()->getMapper($e[0]); + $queryComponents[$alias]['table'] = $queryComponents[$alias]['mapper']->getTable(); + } else { + $queryComponents[$alias]['parent'] = $e[0]; + $queryComponents[$alias]['relation'] = $queryComponents[$e[0]]['table']->getAssociation($e[1]); + $queryComponents[$alias]['mapper'] = $query->getConnection()->getMapper($queryComponents[$alias]['relation']->getTargetEntityName()); + $queryComponents[$alias]['table'] = $queryComponents[$alias]['mapper']->getTable(); + } + + if (isset($v[1])) { + $queryComponents[$alias]['agg'] = $components[1]; + } + + if (isset($v[2])) { + $queryComponents[$alias]['map'] = $components[2]; + } + } + + return $queryComponents; + } + +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Exception.php b/lib/Doctrine/ORM/Query/Exception.php new file mode 100644 index 000000000..7937f168c --- /dev/null +++ b/lib/Doctrine/ORM/Query/Exception.php @@ -0,0 +1,37 @@ +. + */ + +/** + * Doctrine_ORM_Query_Exception + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.phpdoctrine.org + * @since 1.0 + * @version $Revision: 4628 $ + * @author Konsta Vesterinen + */ +class Doctrine_ORM_Query_Exception extends Doctrine_Exception +{ + public static function nonUniqueResult() + { + return new self("The query contains more than one result."); + } +} diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php new file mode 100644 index 000000000..547ca0569 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -0,0 +1,378 @@ +. + */ + +/** + * An LL(k) parser for the context-free grammar of Doctrine Query Language. + * Parses a DQL query, reports any errors in it, and generates the corresponding + * SQL. + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser +{ + /** + * The minimum number of tokens read after last detected error before + * another error can be reported. + * + * @var int + */ + const MIN_ERROR_DISTANCE = 2; + + + /** + * The Sql Builder object. + * + * @var Doctrine_ORM_Query_SqlBuilder + */ + protected $_sqlbuilder; + + /** + * DQL string. + * + * @var string + */ + protected $_input; + + /** + * A scanner object. + * + * @var Doctrine_ORM_Query_Scanner + */ + protected $_scanner; + + /** + * The Parser Result object. + * + * @var Doctrine_ORM_Query_ParserResult + */ + protected $_parserResult; + + /** + * Keyword symbol table + * + * @var Doctrine_ORM_Query_Token + */ + protected $_keywordTable; + + // Scanner Stuff + + /** + * @var array The next token in the query string. + */ + public $lookahead; + + /** + * @var array The last matched token. + */ + public $token; + + // End of Scanner Stuff + + + // Error management stuff + + /** + * Array containing errors detected in the query string during parsing process. + * + * @var array + */ + protected $_errors; + + /** + * @var int The number of tokens read since last error in the input string. + */ + protected $_errorDistance; + + /** + * The EntityManager. + * + * @var EnityManager + */ + protected $_em; + + // End of Error management stuff + + + /** + * Creates a new query parser object. + * + * @param string $dql DQL to be parsed. + * @param Doctrine_Connection $connection The connection to use + */ + public function __construct(Doctrine_ORM_Query $query) + { + $this->_em = $query->getEntityManager(); + $this->_input = $query->getDql(); + $this->_scanner = new Doctrine_ORM_Query_Scanner($this->_input); + $this->_sqlBuilder = new Doctrine_ORM_Query_SqlBuilder($this->_em); + $this->_keywordTable = new Doctrine_ORM_Query_Token(); + + $this->_parserResult = new Doctrine_ORM_Query_ParserResult( + '', + array( // queryComponent + 'dctrn' => array( + 'metadata' => null, + 'parent' => null, + 'relation' => null, + 'map' => null, + 'scalar' => null, + ), + ), + array( // tableAliasMap + 'dctrn' => 'dctrn', + ) + ); + + $this->_parserResult->setEntityManager($this->_em); + + $this->free(true); + } + + + /** + * 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->lookahead['value'] === $token); + } else { + $isMatch = ($this->lookahead['type'] === $token); + } + + if ( ! $isMatch) { + // No definition for value checking. + $this->syntaxError($this->_keywordTable->getLiteral($token)); + } + + $this->next(); + return true; + } + + + /** + * Moves the parser scanner to next token + * + * @return void + */ + public function next() + { + $this->token = $this->lookahead; + $this->lookahead = $this->_scanner->next(); + $this->_errorDistance++; + } + + + public function isA($value, $token) + { + return $this->_scanner->isA($value, $token); + } + + + /** + * Free this parser enabling it to be reused + * + * @param boolean $deep Whether to clean peek and reset errors + * @param integer $position Position to reset + * @return void + */ + public function free($deep = false, $position = 0) + { + // WARNING! Use this method with care. It resets the scanner! + $this->_scanner->resetPosition($position); + + // Deep = true cleans peek and also any previously defined errors + if ($deep) { + $this->_scanner->resetPeek(); + $this->_errors = array(); + } + + $this->token = null; + $this->lookahead = null; + + $this->_errorDistance = self::MIN_ERROR_DISTANCE; + } + + + /** + * Parses a query string. + */ + public function parse() + { + $this->lookahead = $this->_scanner->next(); + + // Building the Abstract Syntax Tree + // We have to double the call of QueryLanguage to allow it to work correctly... =\ + $DQL = new Doctrine_ORM_Query_Parser_QueryLanguage($this); + $AST = $DQL->parse('QueryLanguage', Doctrine_ORM_Query_ParserParamHolder::create()); + + // Check for end of string + if ($this->lookahead !== null) { + $this->syntaxError('end of string'); + } + + // Check for semantical errors + if (count($this->_errors) > 0) { + throw new Doctrine_ORM_Query_Parser_Exception(implode("\r\n", $this->_errors)); + } + + // Assign the executor in parser result + $this->_parserResult->setSqlExecutor(Doctrine_ORM_Query_SqlExecutor_Abstract::create($AST)); + + return $this->_parserResult; + } + + + /** + * Retrieves the assocated Doctrine_ORM_Query_SqlBuilder to this object. + * + * @return Doctrine_ORM_Query_SqlBuilder + */ + public function getSqlBuilder() + { + return $this->_sqlBuilder; + } + + + /** + * Returns the scanner object associated with this object. + * + * @return Doctrine_ORM_Query_Scanner + */ + public function getScanner() + { + return $this->_scanner; + } + + + /** + * Returns the parser result associated with this object. + * + * @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->lookahead; + } + + // Formatting message + $message = 'line 0, col ' . (isset($token['position']) ? $token['position'] : '-1') . ': Error: '; + + if ($expected !== '') { + $message .= "Expected '$expected', got "; + } else { + $message .= 'Unexpected '; + } + + if ($this->lookahead === null) { + $message .= 'end of string.'; + } else { + $message .= "'{$this->lookahead['value']}'"; + } + + throw new Doctrine_ORM_Query_Parser_Exception($message); + } + + + /** + * Generates a new semantical error. + * + * @param string $message Optional message. + * @param array $token Optional token. + */ + public function semanticalError($message = '', $token = null) + { + $this->_semanticalErrorCount++; + + if ($token === null) { + $token = $this->token; + } + + $this->_logError('Warning: ' . $message, $token); + } + + + /** + * Logs new error entry. + * + * @param string $message Message to log. + * @param array $token Token that it was processing. + */ + protected function _logError($message = '', $token) + { + if ($this->_errorDistance >= self::MIN_ERROR_DISTANCE) { + $message = 'line 0, col ' . $token['position'] . ': ' . $message; + $this->_errors[] = $message; + } + + $this->_errorDistance = 0; + } + + /** + * Gets the EntityManager used by the parser. + * + * @return EntityManager + */ + public function getEntityManager() + { + 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); + } +} diff --git a/lib/Doctrine/ORM/Query/Parser/AbstractSchemaName.php b/lib/Doctrine/ORM/Query/Parser/AbstractSchemaName.php new file mode 100644 index 000000000..def808b5f --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/AbstractSchemaName.php @@ -0,0 +1,67 @@ +. + */ + +/** + * AbstractSchemaName ::= identifier + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_AbstractSchemaName extends Doctrine_ORM_Query_ParserRule +{ + protected $_AST = null; + + + public function syntax($paramHolder) + { + // AbstractSchemaName ::= identifier + $this->_AST = $this->AST('AbstractSchemaName'); + + $this->_parser->match(Doctrine_ORM_Query_Token::T_IDENTIFIER); + $this->_AST->setComponentName($this->_parser->token['value']); + } + + + public function semantical($paramHolder) + { + $componentName = $this->_AST->getComponentName(); + + // Check if we are dealing with a real Doctrine_Entity or not + if ( ! $this->_isDoctrineEntity($componentName)) { + $this->_parser->semanticalError( + "Defined entity '" . $companyName . "' is not a valid Doctrine_Entity." + ); + } + + // Return AST node + return $this->_AST; + } + + + protected function _isDoctrineEntity($componentName) + { + return class_exists($componentName) && is_subclass_of($componentName, 'Doctrine_ORM_Entity'); + } +} diff --git a/lib/Doctrine/ORM/Query/Parser/AliasIdentificationVariable.php b/lib/Doctrine/ORM/Query/Parser/AliasIdentificationVariable.php new file mode 100644 index 000000000..b301e24f5 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/AliasIdentificationVariable.php @@ -0,0 +1,64 @@ +. + */ + +/** + * AliasIdentificationVariable = identifier + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_AliasIdentificationVariable extends Doctrine_ORM_Query_ParserRule +{ + protected $_AST = null; + + + public function syntax($paramHolder) + { + // AliasIdentificationVariable = identifier + $this->_AST = $this->AST('AliasIdentificationVariable'); + + $this->_parser->match(Doctrine_ORM_Query_Token::T_IDENTIFIER); + $this->_AST->setComponentAlias($this->_parser->token['value']); + } + + + public function semantical($paramHolder) + { + $parserResult = $this->_parser->getParserResult(); + + if ($parserResult->hasQueryComponent($this->_AST->getComponentAlias())) { + // We should throw semantical error if there's already a component for this alias + $queryComponent = $parserResult->getQueryComponent($this->_AST->getComponentAlias()); + $componentName = $queryComponent['metadata']->getClassName(); + + $message = "Cannot re-declare component alias '" . $this->_AST->getComponentAlias() . "'. " + . "It was already declared for component '" . $componentName . "'."; + + $this->_parser->semanticalError($message); + } + + return $this->_AST; + } +} diff --git a/lib/Doctrine/ORM/Query/Parser/DeleteStatement.php b/lib/Doctrine/ORM/Query/Parser/DeleteStatement.php new file mode 100644 index 000000000..46bb4820e --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/DeleteStatement.php @@ -0,0 +1,51 @@ +. + */ + +/** + * DeleteStatement ::= DeleteClause [WhereClause] + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_DeleteStatement extends Doctrine_ORM_Query_Parser +{ + protected $_AST = null; + + + public function syntax($paramHolder) + { + // DeleteStatement ::= DeleteClause [WhereClause] + $this->_AST = $this->AST('DeleteStatement'); + + $this->_AST->setDeleteClause($this->parse('DeleteClause', $paramHolder)); + + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_WHERE)) { + $this->_AST->setWhereClause($this->parse('WhereClause', $paramHolder)); + } + + // Return AST node + return $this->_AST; + } +} diff --git a/lib/Doctrine/ORM/Query/Parser/Exception.php b/lib/Doctrine/ORM/Query/Parser/Exception.php new file mode 100644 index 000000000..a760fd4b6 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/Exception.php @@ -0,0 +1,35 @@ +. + */ + +/** + * Doctrine_ORM_Query_Parser_Exception + * + * @package Doctrine + * @subpackage Query + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 1.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_Exception extends Doctrine_ORM_Query_Exception +{ +} diff --git a/lib/Doctrine/ORM/Query/Parser/FieldIdentificationVariable.php b/lib/Doctrine/ORM/Query/Parser/FieldIdentificationVariable.php new file mode 100644 index 000000000..caabf5ec7 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/FieldIdentificationVariable.php @@ -0,0 +1,59 @@ +. + */ + +/** + * FieldIdentificationVariable ::= identifier + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_FieldIdentificationVariable extends Doctrine_ORM_Query_ParserRule +{ + protected $_AST = null; + + + public function syntax($paramHolder) + { + // FieldIdentificationVariable ::= identifier + $this->_AST = $this->AST('FieldIdentificationVariable'); + + $this->_parser->match(Doctrine_ORM_Query_Token::T_IDENTIFIER); + $this->_AST->setFieldName($this->_parser->token['value']); + + // Return AST node + return $this->_AST; + } + + + public function semantical($paramHolder) + { + $parserResult = $this->_parser->getParserResult(); + + // [TODO] Check for field existance somewhere + + // Return AST node + return $this->_AST; + } +} diff --git a/lib/Doctrine/ORM/Query/Parser/FromClause.php b/lib/Doctrine/ORM/Query/Parser/FromClause.php new file mode 100644 index 000000000..b83192115 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/FromClause.php @@ -0,0 +1,59 @@ +. + */ + +/** + * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration} + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 1.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_FromClause extends Doctrine_ORM_Query_ParserRule +{ + protected $_AST = null; + + + public function syntax($paramHolder) + { + // FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration} + $this->_AST = $this->AST('FromClause'); + + $this->_parser->match(Doctrine_ORM_Query_Token::T_FROM); + + $this->_AST->addIdentificationVariableDeclaration( + $this->parse('IdentificationVariableDeclaration', $paramHolder) + ); + + while ($this->_isNextToken(',')) { + $this->_parser->match(','); + + $this->_AST->addIdentificationVariableDeclaration( + $this->parse('IdentificationVariableDeclaration', $paramHolder) + ); + } + + // Return AST node + return $this->_AST; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Parser/IdentificationVariable.php b/lib/Doctrine/ORM/Query/Parser/IdentificationVariable.php new file mode 100644 index 000000000..9919275e6 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/IdentificationVariable.php @@ -0,0 +1,62 @@ +. + */ + +/** + * IdentificationVariable ::= identifier + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_IdentificationVariable extends Doctrine_ORM_Query_ParserRule +{ + protected $_AST = null; + + + public function syntax($paramHolder) + { + // IdentificationVariable ::= identifier + $this->_AST = $this->AST('IdentificationVariable'); + + $this->_parser->match(Doctrine_ORM_Query_Token::T_IDENTIFIER); + $this->_AST->setComponentAlias($this->_parser->token['value']); + } + + + public function semantical($paramHolder) + { + $parserResult = $this->_parser->getParserResult(); + + if ( ! $parserResult->hasQueryComponent($this->_AST->getComponentAlias())) { + // We should throw semantical error if we cannot find the component alias + $message = "No entity related to declared alias '" . $this->_AST->getComponentAlias() + . "' near '" . $this->_parser->getQueryPiece($this->_parser->token) . "'."; + + $this->_parser->semanticalError($message); + } + + // Return AST node + return $this->_AST; + } +} diff --git a/lib/Doctrine/ORM/Query/Parser/IdentificationVariableDeclaration.php b/lib/Doctrine/ORM/Query/Parser/IdentificationVariableDeclaration.php new file mode 100644 index 000000000..fc1231b15 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/IdentificationVariableDeclaration.php @@ -0,0 +1,82 @@ +. + */ + +/** + * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 1.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_IdentificationVariableDeclaration extends Doctrine_ORM_Query_ParserRule +{ + protected $_AST = null; + + + public function syntax($paramHolder) + { + // IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* + $this->_AST = $this->AST('IdentificationVariableDeclaration'); + + $this->_AST->setRangeVariableDeclaration($this->parse('RangeVariableDeclaration', $paramHolder)); + + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_INDEX)) { + $this->_AST->setIndexBy($this->parse('IndexBy', $paramHolder)); + } + + while ( + $this->_isNextToken(Doctrine_ORM_Query_Token::T_LEFT) || + $this->_isNextToken(Doctrine_ORM_Query_Token::T_INNER) || + $this->_isNextToken(Doctrine_ORM_Query_Token::T_JOIN) + ) { + $this->_AST->addJoinVariableDeclaration($this->parse('JoinVariableDeclaration', $paramHolder)); + } + } + + + public function semantical($paramHolder) + { + // If we have an INDEX BY RangeVariableDeclaration + if ($this->_AST->getIndexby() !== null) { + // Grab Range component alias + $rangeComponentAlias = $this->_AST->getRangeVariableDeclaration() + ->getAliasIdentificationVariable()->getComponentAlias(); + + // Grab IndexBy component alias + $indexComponentAlias = $this->_AST->getIndexBy() + ->getSimpleStateFieldPathExpression()->getIdentificationVariable()->getComponentAlias(); + + // Check if we have same component being used in index + if ($rangeComponentAlias !== $indexComponentAlias) { + $message = "Invalid aliasing. Cannot index by '" . $indexComponentAlias + . "' inside '" . $rangeComponentAlias . "' scope."; + + $this->_parser->semanticalError($message); + } + } + + // Return AST node + return $this->_AST; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Parser/IndexBy.php b/lib/Doctrine/ORM/Query/Parser/IndexBy.php new file mode 100644 index 000000000..e5b2a58d4 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/IndexBy.php @@ -0,0 +1,99 @@ +. + */ + +/** + * IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_IndexBy extends Doctrine_ORM_Query_ParserRule +{ + protected $_AST = null; + + + public function syntax($paramHolder) + { + // IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression + $this->_AST = $this->AST('IndexBy'); + + $this->_parser->match(Doctrine_ORM_Query_Token::T_INDEX); + $this->_parser->match(Doctrine_ORM_Query_Token::T_BY); + + $this->_AST->setSimpleStateFieldPathExpression($this->parse('SimpleStateFieldPathExpression', $paramHolder)); + } + + + public function semantical($paramHolder) + { + // Retrieving required information + $parserResult = $this->_parser->getParserResult(); + + // Grab INDEX BY information + $componentAlias = $this->_AST->getSimpleStateFieldPathExpression() + ->getIdentificationVariable()->getComponentAlias(); + $componentFieldName = $this->_AST->getSimpleStateFieldPathExpression() + ->getSimpleStateField()->getFieldName(); + + // Trying to retrieve related query component + try { + $queryComponent = $parserResult->getQueryComponent($componentAlias); + $classMetadata = $queryComponent['metadata']; + } catch (Doctrine_Exception $e) { + $this->_parser->semanticalError($e->getMessage()); + + return; + } + + // The INDEX BY field must be either the (primary && not part of composite pk) || (unique && notnull) + $columnMapping = $classMetadata->getFieldMapping($componentFieldName); + + if ( + ! $classMetadata->isIdentifier($componentFieldName) && + ! $classMetadata->isUniqueField($componentFieldName) && + ! $classMetadata->isNotNull($componentFieldName) + ) { + $this->_parser->semanticalError( + "Field '" . $componentFieldName . "' of component '" . $classMetadata->getClassName() . + "' must be unique and notnull to be used as index.", + $this->_parser->token + ); + } + + if ($classMetadata->isIdentifier($componentFieldName) && $classMetadata->isIdentifierComposite()) { + $this->_parser->semanticalError( + "Field '" . $componentFieldName . "' of component '" . $classMetadata->getClassName() . + "' must be primary and not part of a composite primary key to be used as index.", + $this->_parser->token + ); + } + + $queryComponent['map'] = $componentFieldName; + $parserResult->setQueryComponent($componentAlias, $queryComponent); + + // Return AST node + return $this->_AST; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Parser/Join.php b/lib/Doctrine/ORM/Query/Parser/Join.php new file mode 100644 index 000000000..d21646cd3 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/Join.php @@ -0,0 +1,91 @@ +. + */ + +/** + * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression + * ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression] + * + * @author Guilherme Blanco + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_Join extends Doctrine_ORM_Query_ParserRule +{ + protected $_AST = null; + + + public function syntax($paramHolder) + { + // Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression + // ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression] + $this->_AST = $this->AST('Join'); + + // Check Join type + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_LEFT)) { + $this->_parser->match(Doctrine_ORM_Query_Token::T_LEFT); + + // Possible LEFT OUTER join + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_OUTER)) { + $this->_parser->match(Doctrine_ORM_Query_Token::T_OUTER); + + $this->_AST->setJoinType(Doctrine_ORM_Query_AST_Join::JOIN_TYPE_LEFTOUTER); + } else { + $this->_AST->setJoinType(Doctrine_ORM_Query_AST_Join::JOIN_TYPE_LEFT); + } + } else if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_INNER)) { + // Default Join type. Not need to setJoinType. + $this->_parser->match(Doctrine_ORM_Query_Token::T_INNER); + } + + $this->_parser->match(Doctrine_ORM_Query_Token::T_JOIN); + + $this->_AST->setJoinAssociationPathExpression($this->parse('JoinAssociationPathExpression', $paramHolder)); + + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_AS)) { + $this->_parser->match(Doctrine_ORM_Query_Token::T_AS); + } + + $this->_AST->setAliasIdentificationVariable($this->parse('AliasIdentificationVariable', $paramHolder)); + + // Check Join where type + if ( + $this->_isNextToken(Doctrine_ORM_Query_Token::T_ON) || + $this->_isNextToken(Doctrine_ORM_Query_Token::T_WITH) + ) { + // Apply matches and adjusts + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_ON)) { + $this->_parser->match(Doctrine_ORM_Query_Token::T_ON); + + $this->_AST->setWhereType(Doctrine_ORM_Query_AST_Join::JOIN_WHERE_ON); + } else { + // Default Join where type. Not need to setWhereType. + $this->_parser->match(Doctrine_ORM_Query_Token::T_WITH); + } + + $this->_AST->setConditionalExpression($this->parse('ConditionalExpression', $paramHolder)); + } + + // Return AST node + return $this->_AST; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Parser/JoinVariableDeclaration.php b/lib/Doctrine/ORM/Query/Parser/JoinVariableDeclaration.php new file mode 100644 index 000000000..f9ec6e0bb --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/JoinVariableDeclaration.php @@ -0,0 +1,74 @@ +. + */ + +/** + * JoinVariableDeclaration ::= Join [IndexBy] + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 1.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_JoinVariableDeclaration extends Doctrine_ORM_Query_ParserRule +{ + protected $_AST = null; + + + public function syntax($paramHolder) + { + // JoinVariableDeclaration ::= Join [IndexBy] + $this->_AST = $this->AST('JoinVariableDeclaration'); + + $this->_AST->setJoin($this->parse('Join', $paramHolder)); + + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_INDEX)) { + $this->_AST->setIndexBy($this->parse('IndexBy', $paramHolder)); + } + } + + + public function semantical($paramHolder) + { + // If we have an INDEX BY JoinVariableDeclaration + if ($this->_AST->getIndexby() !== null) { + // Grab Join component alias + $joinComponentAlias = $this->_AST->getJoin() + ->getAliasIdentificationVariable()->getComponentAlias(); + + // Grab IndexBy component alias + $indexComponentAlias = $this->_AST->getIndexBy() + ->getSimpleStateFieldPathExpression()->getIdentificationVariable()->getComponentAlias(); + + // Check if we have same component being used in index + if ($joinComponentAlias !== $indexComponentAlias) { + $message = "Invalid aliasing. Cannot index by '" . $indexComponentAlias + . "' inside '" . $joinComponentAlias . "' scope."; + + $this->_parser->semanticalError($message); + } + } + + // Return AST node + return $this->_AST; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Parser/QueryLanguage.php b/lib/Doctrine/ORM/Query/Parser/QueryLanguage.php new file mode 100644 index 000000000..ff08a30ff --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/QueryLanguage.php @@ -0,0 +1,57 @@ +. + */ + +/** + * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_QueryLanguage extends Doctrine_ORM_Query_ParserRule +{ + public function syntax($paramHolder) + { + // QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement + switch ($this->_parser->lookahead['type']) { + case Doctrine_ORM_Query_Token::T_SELECT: + return $this->parse('SelectStatement', $paramHolder); + break; + + case Doctrine_ORM_Query_Token::T_UPDATE: + return $this->parse('UpdateStatement', $paramHolder); + break; + + case Doctrine_ORM_Query_Token::T_DELETE: + return $this->parse('DeleteStatement', $paramHolder); + break; + + default: + $this->_parser->syntaxError('SELECT, UPDATE or DELETE'); + break; + } + } +} diff --git a/lib/Doctrine/ORM/Query/Parser/RangeVariableDeclaration.php b/lib/Doctrine/ORM/Query/Parser/RangeVariableDeclaration.php new file mode 100644 index 000000000..58a85f1fa --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/RangeVariableDeclaration.php @@ -0,0 +1,94 @@ +. + */ + +/** + * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 1.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_RangeVariableDeclaration extends Doctrine_ORM_Query_ParserRule +{ + protected $_AST = null; + + + public function syntax($paramHolder) + { + // RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable + $this->_AST = $this->AST('RangeVariableDeclaration'); + + $this->_AST->setAbstractSchemaName($this->parse('AbstractSchemaName', $paramHolder)); + + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_AS)) { + $this->_parser->match(Doctrine_ORM_Query_Token::T_AS); + } + + $this->_AST->setAliasIdentificationVariable($this->parse('AliasIdentificationVariable', $paramHolder)); + } + + + public function semantical($paramHolder) + { + $parserResult = $this->_parser->getParserResult(); + $componentName = $this->_AST->getAbstractSchemaName()->getComponentName(); + $componentAlias = $this->_AST->getAliasIdentificationVariable()->getComponentAlias(); + + // Check if we already have a component defined without an alias + if ($componentAlias === null && $parserResult->hasQueryComponent($componentName)) { + $this->_parser->semanticalError( + "Cannot re-declare component '{$componentName}'. Please assign an alias to it." + ); + // Define new queryComponent since it does not exist yet + } else { + // Retrieving ClassMetadata and Mapper + try { + $classMetadata = $this->_em->getClassMetadata($componentName); + + // Building queryComponent + $queryComponent = array( + 'metadata' => $classMetadata, + 'parent' => null, + 'relation' => null, + 'map' => null, + 'scalar' => null, + ); + } catch (Doctrine_Exception $e) { + $this->_parser->semanticalError($e->getMessage()); + } + + // Inspect for possible non-aliasing + if ($componentAlias === null) { + $componentAlias = $componentName; + } + + $tableAlias = $parserResult->generateTableAlias($classMetadata->getClassName()); + $parserResult->setQueryComponent($componentAlias, $queryComponent); + $parserResult->setTableAlias($tableAlias, $componentAlias); + } + + // Return AST node + return $this->_AST; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Parser/SelectClause.php b/lib/Doctrine/ORM/Query/Parser/SelectClause.php new file mode 100644 index 000000000..65e8ee26f --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/SelectClause.php @@ -0,0 +1,74 @@ +. + */ + +/** + * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_SelectClause extends Doctrine_ORM_Query_ParserRule +{ + protected $_AST = null; + + protected $_selectExpressions = array(); + + + public function syntax($paramHolder) + { + // SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} + $this->_AST = $this->AST('SelectClause'); + + $this->_parser->match(Doctrine_ORM_Query_Token::T_SELECT); + + // Inspecting if we are in a DISTINCT query + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_DISTINCT)) { + $this->_parser->match(Doctrine_ORM_Query_Token::T_DISTINCT); + + $this->_AST->setIsDistinct(true); + } + + // Process SelectExpressions (1..N) + $this->_selectExpressions[] = $this->parse('SelectExpression', $paramHolder); + + while ($this->_isNextToken(',')) { + $this->_parser->match(','); + + $this->_selectExpressions[] = $this->parse('SelectExpression', $paramHolder); + } + } + + + public function semantical($paramHolder) + { + // We need to validate each SelectExpression + for ($i = 0, $l = count($this->_selectExpressions); $i < $l; $i++) { + $this->_AST->addSelectExpression($this->_selectExpressions[$i]->semantical($paramHolder)); + } + + // Return AST node + return $this->_AST; + } +} diff --git a/lib/Doctrine/ORM/Query/Parser/SelectExpression.php b/lib/Doctrine/ORM/Query/Parser/SelectExpression.php new file mode 100644 index 000000000..60fe7507a --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/SelectExpression.php @@ -0,0 +1,80 @@ +. + */ + +/** + * SelectExpression ::= IdentificationVariable ["." "*"] | + * (StateFieldPathExpression | AggregateExpression | "(" Subselect ")" ) + * [["AS"] FieldIdentificationVariable] + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 1.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_SelectExpression extends Doctrine_ORM_Query_ParserRule +{ + protected $_AST = null; + + + public function syntax($paramHolder) + { + // SelectExpression ::= IdentificationVariable ["." "*"] | + // (StateFieldPathExpression | AggregateExpression | "(" Subselect ")" ) + // [["AS"] FieldIdentificationVariable] + + // First we recognize for an IdentificationVariable (Component alias) + if ($this->_isIdentificationVariable()) { + $identificationVariable = $this->parse('IdentificationVariable', $paramHolder); + + // Inspecting if we are in a ["." "*"] + if ($this->_isNextToken('.')) { + $this->_parser->match('.'); + $this->_parser->match('*'); + } + + return $identificationVariable; + } + } + + + protected function _isIdentificationVariable() + { + // Retrying to recoginize this grammar: IdentificationVariable ["." "*"] + $token = $this->_parser->lookahead; + $this->_parser->getScanner()->resetPeek(); + + // We have an identifier here + if ($token['type'] === Doctrine_ORM_Query_Token::T_IDENTIFIER) { + $token = $this->_parser->getScanner()->peek(); + + // If we have a dot ".", then next char must be the "*" + if ($token['value'] === '.') { + $token = $this->_parser->getScanner()->peek(); + + return $token['value'] === '*'; + } + } + + return false; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Parser/SelectStatement.php b/lib/Doctrine/ORM/Query/Parser/SelectStatement.php new file mode 100644 index 000000000..faf15464a --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/SelectStatement.php @@ -0,0 +1,81 @@ +. + */ + +/** + * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_SelectStatement extends Doctrine_ORM_Query_ParserRule +{ + protected $_AST = null; + + protected $_selectClause = null; + + + public function syntax($paramHolder) + { + // SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + $this->_AST = $this->AST('SelectStatement'); + + // Disable the semantical check for SelectClause now. This is needed + // since we dont know the query components yet (will be known only + // when the FROM and WHERE clause are processed). + $paramHolder->set('semanticalCheck', false); + $this->_selectClause = $this->parse('SelectClause', $paramHolder); + $paramHolder->remove('semanticalCheck'); + + $this->_AST->setFromClause($this->parse('FromClause', $paramHolder)); + + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_WHERE)) { + $this->_AST->setWhereClause($this->parse('WhereClause', $paramHolder)); + } + + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_GROUP)) { + $this->_AST->setGroupByClause($this->parse('GroupByClause', $paramHolder)); + } + + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_HAVING)) { + $this->_AST->setHavingClause($this->parse('HavingClause', $paramHolder)); + } + + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_ORDER)) { + $this->_AST->setOrderByClause($this->parse('OrderByClause', $paramHolder)); + } + } + + + public function semantical($paramHolder) + { + // We need to invoke the semantical check of SelectClause here, since + // it was not yet checked. + // The semantical checks will be forwarded to all SelectClause dependant grammar rules + $this->_AST->setSelectClause($this->_selectClause->semantical($paramHolder)); + + // Return AST node + return $this->_AST; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Parser/SimpleStateField.php b/lib/Doctrine/ORM/Query/Parser/SimpleStateField.php new file mode 100644 index 000000000..c71337d64 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/SimpleStateField.php @@ -0,0 +1,42 @@ +. + */ + +/** + * SimpleStateField ::= FieldIdentificationVariable + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_SimpleStateField extends Doctrine_ORM_Query_ParserRule +{ + protected $_AST = null; + + + public function syntax($paramHolder) + { + // SimpleStateField ::= FieldIdentificationVariable + return $this->parse('FieldIdentificationVariable', $paramHolder); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Parser/SimpleStateFieldPathExpression.php b/lib/Doctrine/ORM/Query/Parser/SimpleStateFieldPathExpression.php new file mode 100644 index 000000000..c56bbf0a1 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/SimpleStateFieldPathExpression.php @@ -0,0 +1,77 @@ +. + */ + +/** + * SimpleStateFieldPathExpression ::= IdentificationVariable "." SimpleStateField + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_SimpleStateFieldPathExpression extends Doctrine_ORM_Query_ParserRule +{ + protected $_AST = null; + + + public function syntax($paramHolder) + { + // SimpleStateFieldPathExpression ::= IdentificationVariable "." SimpleStateField + $this->_AST = $this->AST('SimpleStateFieldPathExpression'); + + $this->_AST->setIdentificationVariable($this->parse('IdentificationVariable', $paramHolder)); + + $this->_parser->match('.'); + + $this->_AST->setSimpleStateField($this->parse('SimpleStateField', $paramHolder)); + } + + + public function semantical($paramHolder) + { + $parserResult = $this->_parser->getParserResult(); + $componentAlias = $this->_AST->getIdentificationVariable()->getComponentAlias(); + $componentFieldName = $this->_AST->getSimpleStateField()->getFieldName(); + + // We need to make sure field exists + try { + $queryComponent = $parserResult->getQueryComponent($componentAlias); + $classMetadata = $queryComponent['metadata']; + } catch (Doctrine_Exception $e) { + $this->_parser->semanticalError($e->getMessage()); + + return; + } + + if ($classMetadata instanceof Doctrine_ClassMetadata && ! $classMetadata->hasField($componentFieldName)) { + $this->_parser->semanticalError( + "Cannot use key mapping. Field '" . $componentFieldName . "' " . + "does not exist in component '" . $classMetadata->getClassName() . "'.", + $this->_parser->token + ); + } + + // Return AST node + return $this->_AST; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Parser/UpdateStatement.php b/lib/Doctrine/ORM/Query/Parser/UpdateStatement.php new file mode 100644 index 000000000..5b0cbbdd5 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Parser/UpdateStatement.php @@ -0,0 +1,51 @@ +. + */ + +/** + * UpdateStatement ::= UpdateClause [WhereClause] + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Parser_UpdateStatement extends Doctrine_ORM_Query_Parser +{ + protected $_AST = null; + + + public function syntax($paramHolder) + { + // UpdateStatement ::= UpdateClause [WhereClause] + $this->_AST = $this->AST('UpdateStatement'); + + $this->_AST->setUpdateClause($this->parse('UpdateClause', $paramHolder)); + + if ($this->_isNextToken(Doctrine_ORM_Query_Token::T_WHERE)) { + $this->_AST->setWhereClause($this->parse('WhereClause', $paramHolder)); + } + + // Return AST node + return $this->_AST; + } +} diff --git a/lib/Doctrine/ORM/Query/ParserParamHolder.php b/lib/Doctrine/ORM/Query/ParserParamHolder.php new file mode 100644 index 000000000..b4ec0d3a5 --- /dev/null +++ b/lib/Doctrine/ORM/Query/ParserParamHolder.php @@ -0,0 +1,87 @@ +. + */ + +/** + * Production variables holder + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_ParserParamHolder +{ + 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]); + } + } +} diff --git a/lib/Doctrine/ORM/Query/ParserResult.php b/lib/Doctrine/ORM/Query/ParserResult.php new file mode 100644 index 000000000..88a74dfec --- /dev/null +++ b/lib/Doctrine/ORM/Query/ParserResult.php @@ -0,0 +1,186 @@ +. + */ + +/** + * Doctrine_ORM_Query_ParserResult + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_ParserResult extends Doctrine_ORM_Query_AbstractResult +{ + /** + * The EntityManager. + * + * @var Doctrine_EntityManager + */ + protected $_em; + + /** + * A simple array keys representing table aliases and values table alias + * seeds. The seeds are used for generating short table aliases. + * + * @var array $_tableAliasSeeds + */ + protected $_tableAliasSeeds = array(); + + /** + * Simple array of keys representing the fields used in query. + * + * @var array $_queryFields + */ + protected $_queryFields = array(); + + + /** + * Sets the Entity Manager. + * + * @param Doctrine_EntityManager $em The Entity Manager. + */ + public function setEntityManager($em) + { + $this->_em = $em; + } + + + /** + * Gets the Entity Manager. + * + * @return Doctrine_EntityManager + */ + public function getEntityManager() + { + return $this->_em; + } + + + /** + * @nodoc + */ + public function setSqlExecutor(Doctrine_ORM_Query_SqlExecutor_Abstract $executor) + { + $this->_data = $executor; + } + + + /** + * @nodoc + */ + public function getSqlExecutor() + { + return $this->_data; + } + + + /** + * Defines the mapping fields. + * + * @param array $queryFields Query fields. + */ + public function setQueryFields(array $queryFields) + { + $this->_queryFields = $queryFields; + } + + + /** + * Sets the declaration for given field alias. + * + * @param string $fieldAlias The field alias to set the declaration to. + * @param string $queryField Alias declaration. + */ + public function setQueryField($fieldAlias, $queryField) + { + $this->_queryFields[$fieldAlias] = $queryField; + } + + + /** + * Gets the mapping fields. + * + * @return array Query fields. + */ + public function getQueryFields() + { + return $this->_queryFields; + } + + + /** + * Get the declaration for given field alias. + * + * @param string $fieldAlias The field alias the retrieve the declaration from. + * @return array Alias declaration. + */ + public function getQueryField($fieldAlias) + { + if ( ! isset($this->_queryFields[$fieldAlias])) { + throw new Doctrine_ORM_Query_Exception('Unknown query field ' . $fieldAlias); + } + + return $this->_queryFields[$fieldAlias]; + } + + + /** + * Whether or not this object has a declaration for given field alias. + * + * @param string $fieldAlias Field alias the retrieve the declaration from. + * @return boolean True if this object has given alias, otherwise false. + */ + public function hasQueryField($fieldAlias) + { + return isset($this->_queryFields[$fieldAlias]); + } + + + /** + * Generates a table alias from given table name and associates + * it with given component alias + * + * @param string $componentName Component name to be associated with generated table alias + * @return string Generated table alias + */ + public function generateTableAlias($componentName) + { + $baseAlias = strtolower(preg_replace('/[^A-Z]/', '\\1', $componentName)); + + // We may have a situation where we have all chars are lowercased + if ( $baseAlias == '' ) { + // We simply grab the first 2 chars of component name + $baseAlias = substr($componentNam, 0, 2); + } + + $alias = $baseAlias; + + if ( ! isset($this->_tableAliasSeeds[$baseAlias])) { + $this->_tableAliasSeeds[$baseAlias] = 1; + } else { + $alias .= $this->_tableAliasSeeds[$baseAlias]++; + } + + return $alias; + } +} diff --git a/lib/Doctrine/ORM/Query/ParserResultDummy.php b/lib/Doctrine/ORM/Query/ParserResultDummy.php new file mode 100644 index 000000000..88a99ad8d --- /dev/null +++ b/lib/Doctrine/ORM/Query/ParserResultDummy.php @@ -0,0 +1,75 @@ +_isMixedQuery; + } + + public function isIdentityQuery() + { + return $this->_isIdentityQuery; + } + + public function setMixedQuery($bool) + { + $this->_isMixedQuery = (bool) $bool; + } + + public function getDatabaseStatement() + { + return $this->_dbStatement; + } + + public function setDatabaseStatement($stmt) + { + $this->_dbStatement = $stmt; + } + + public function getHydrationMode() + { + return $this->_hydrationMode; + } + + public function setHydrationMode($hydrationMode) + { + $this->_hydrationMode = $hydrationMode; + } + + public function getTableToClassAliasMap() + { + return $this->_tableToClassAliasMap; + } + + public function setTableToClassAliasMap(array $map) + { + $this->_tableToClassAliasMap = $map; + } + + public function setQueryComponents(array $queryComponents) + { + $this->_queryComponents = $queryComponents; + } + + public function getQueryComponents() + { + return $this->_queryComponents; + } +} + + +?> \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/ParserRule.php b/lib/Doctrine/ORM/Query/ParserRule.php new file mode 100644 index 000000000..cc8785e24 --- /dev/null +++ b/lib/Doctrine/ORM/Query/ParserRule.php @@ -0,0 +1,202 @@ +. + */ + +/** + * An abstract base class for the productions of the Doctrine Query Language + * context-free grammar. + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +abstract class Doctrine_ORM_Query_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; + + + /** + * 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(); + } + + + 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_ParserRule + */ + public function parse($RuleName, $paramHolder) + { + $BNFGrammarRule = $this->_getGrammarRule($RuleName); + + //echo "Processing class: " . get_class($BNFGrammarRule) . "...\n"; + //echo "Params: " . var_export($paramHolder, true) . "\n"; + + // Syntax check + if ( ! $paramHolder->has('syntaxCheck') || $paramHolder->get('syntaxCheck') === true) { + //echo "Processing syntax checks of " . $RuleName . "...\n"; + + $return = $BNFGrammarRule->syntax($paramHolder); + + if ($return !== null) { + //echo "Returning Gramma Rule class: " . (is_object($return) ? get_class($return) : $return) . "...\n"; + + return $return; + } + } + + // Semantical check + if ( ! $paramHolder->has('semanticalCheck') || $paramHolder->get('semanticalCheck') === true) { + //echo "Processing semantical checks of " . $RuleName . "...\n"; + + $return = $BNFGrammarRule->semantical($paramHolder); + + if ($return !== null) { + //echo "Returning Gramma 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 new Doctrine_ORM_Query_Parser_Exception( + "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; + + //echo $class . "\r\n"; + + if ( ! class_exists($class)) { + throw new Doctrine_ORM_Query_Parser_Exception( + "Unknown AST node '" . $AstName . "'. Could not find related compiler class." + ); + } + + return new $class($this->_parser->getParserResult()); + } + + /** + * @nodoc + */ + abstract public function syntax($paramHolder); + + + /** + * @nodoc + */ + public function semantical($paramHolder) + { + } + + public function getParser() + { + return $this->_parser; + } +} diff --git a/lib/Doctrine/ORM/Query/Printer.php b/lib/Doctrine/ORM/Query/Printer.php new file mode 100644 index 000000000..84af04054 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Printer.php @@ -0,0 +1,93 @@ +. + */ + +/** + * A parse tree printer for Doctrine Query Language parser. + * + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Printer +{ + /** + * Current indentation level + * + * @var int + */ + protected $_indent = 0; + + /** + * Defines whether parse tree is printed (default, false) or not (true). + * + * @var bool + */ + protected $_silent; + + /** + * Constructs a new parse tree printer. + * + * @param bool $silent Parse tree will not be printed if true. + */ + public function __construct($silent = false) + { + $this->_silent = $silent; + } + + /** + * Prints an opening parenthesis followed by production name and increases + * indentation level by one. + * + * This method is called before executing a production. + * + * @param string $name production name + */ + public function startProduction($name) + { + $this->println('(' . $name); + $this->_indent++; + } + + /** + * Decreases indentation level by one and prints a closing parenthesis. + * + * This method is called after executing a production. + */ + public function endProduction() + { + $this->_indent--; + $this->println(')'); + } + + /** + * Prints text indented with spaces depending on current indentation level. + * + * @param string $str text + */ + public function println($str) + { + if ( ! $this->_silent) { + echo str_repeat(' ', $this->_indent), $str, "\n"; + } + } +} diff --git a/lib/Doctrine/ORM/Query/Production/AggregateExpression.php b/lib/Doctrine/ORM/Query/Production/AggregateExpression.php new file mode 100644 index 000000000..311dde66a --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/AggregateExpression.php @@ -0,0 +1,118 @@ +. + */ + +/** + * AggregateExpression = ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] Expression ")" + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_AggregateExpression extends Doctrine_Query_Production +{ + protected $_functionName; + + protected $_isDistinct; + + protected $_expression; + + + public function syntax($paramHolder) + { + // AggregateExpression = ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] Expression ")" + $this->_isDistinct = false; + $token = $this->_parser->lookahead; + + switch ($token['type']) { + case Doctrine_Query_Token::T_AVG: + case Doctrine_Query_Token::T_MAX: + case Doctrine_Query_Token::T_MIN: + case Doctrine_Query_Token::T_SUM: + case Doctrine_Query_Token::T_COUNT: + $this->_parser->match($token['type']); + $this->_functionName = strtoupper($token['value']); + break; + + default: + $this->_parser->logError('AVG, MAX, MIN, SUM or COUNT'); + break; + } + + $this->_parser->match('('); + + if ($this->_isNextToken(Doctrine_Query_Token::T_DISTINCT)) { + $this->_parser->match(Doctrine_Query_Token::T_DISTINCT); + $this->_isDistinct = true; + } + + $this->_expression = $this->AST('Expression', $paramHolder); + + $this->_parser->match(')'); + } + + + public function semantical($paramHolder) + { + $this->_expression->semantical($paramHolder); + } + + + public function buildSql() + { + return $this->_functionName + . '(' . (($this->_isDistinct) ? 'DISTINCT ' : '') + . $this->_expression->buildSql() + . ')'; + } + + /** + * Visitor support. + * + * @param object $visitor + */ + public function accept($visitor) + { + $this->_expression->accept($visitor); + $visitor->visitAggregateExpression($this); + } + + /* Getters */ + + public function getExpression() + { + return $this->_expression; + } + + public function getFunctionName() + { + return $this->_functionName; + } + + public function isDistinct() + { + return $this->_isDistinct; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/Atom.php b/lib/Doctrine/ORM/Query/Production/Atom.php new file mode 100644 index 000000000..ce79e001e --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/Atom.php @@ -0,0 +1,119 @@ +. + */ + +/** + * Atom = string | integer | float | input_parameter + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 1.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_Atom extends Doctrine_Query_Production +{ + protected $_type; + + protected $_value; + + + public function syntax($paramHolder) + { + // Atom = string | integer | float | input_parameter + switch ($this->_parser->lookahead['type']) { + case Doctrine_Query_Token::T_STRING: + $this->_parser->match(Doctrine_Query_Token::T_STRING); + $this->_type = 'string'; + break; + + case Doctrine_Query_Token::T_INTEGER: + $this->_parser->match(Doctrine_Query_Token::T_INTEGER); + $this->_type = 'integer'; + break; + + case Doctrine_Query_Token::T_FLOAT: + $this->_parser->match(Doctrine_Query_Token::T_FLOAT); + $this->_type = 'float'; + break; + + case Doctrine_Query_Token::T_INPUT_PARAMETER: + $this->_parser->match(Doctrine_Query_Token::T_INPUT_PARAMETER); + $this->_type = 'param'; + break; + + default: + $this->_parser->syntaxError('string, number or parameter (? or :)'); + break; + } + + $this->_value = $this->_parser->token['value']; + } + + + public function buildSql() + { + $conn = $this->_em->getConnection(); + + switch ($this->_type) { + case 'param': + return $this->_value; + case 'string': + //FIXME: Remove the quotes from _value! Should the scanner do that or where? + // 'mystring' => mystring. Otherwise 'mystring' is the content (with quotes)! + // Eg: select ... from ... where f.foo = 'bar' + // => $conn->quote('bar',...), CURRENTLY: $conn->quote("'bar'",...) + + // This fix looks a bit ugly or ... ? Should this happen earlier? Syntax? + // Scanner? + if (strpos($this->_value, "'") === 0) { + $this->_value = substr($this->_value, 1, strlen($this->_value) - 2); + } + return $conn->quote($this->_value, $this->_type); + default: + return $conn->quote($this->_value, $this->_type); + } + } + + /** + * Visitor support. + * + * @param object $visitor + */ + public function accept($visitor) + { + $visitor->visitAtom($this); + } + + /* Getters */ + + public function getType() + { + return $this->_type; + } + + public function getValue() + { + return $this->_value; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/BetweenExpression.php b/lib/Doctrine/ORM/Query/Production/BetweenExpression.php new file mode 100644 index 000000000..75024d1f2 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/BetweenExpression.php @@ -0,0 +1,87 @@ +. + */ + +/** + * BetweenExpression = ["NOT"] "BETWEEN" Expression "AND" Expression + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_BetweenExpression extends Doctrine_Query_Production +{ + protected $_not; + + protected $_fromExpression; + + protected $_toExpression; + + + public function syntax($paramHolder) + { + // BetweenExpression = ["NOT"] "BETWEEN" Expression "AND" Expression + $this->_not = false; + + if ($this->_isNextToken(Doctrine_Query_Token::T_NOT)) { + $this->_parser->match(Doctrine_Query_Token::T_NOT); + $this->_not = true; + } + + $this->_parser->match(Doctrine_Query_Token::T_BETWEEN); + + $this->_fromExpression = $this->AST('Expression', $paramHolder); + + $this->_parser->match(Doctrine_Query_Token::T_AND); + + $this->_toExpression = $this->AST('Expression', $paramHolder); + } + + + public function buildSql() + { + return (($this->_not) ? 'NOT ' : '') . 'BETWEEN ' + . $this->_fromExpression->buildSql() . ' AND ' . $this->_toExpression->buildSql(); + } + + + /* Getters */ + public function isNot() + { + return $this->_not; + } + + + public function getFromExpression() + { + return $this->_fromExpression; + } + + + public function getToExpression() + { + return $this->_toExpression; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/ComparisonExpression.php b/lib/Doctrine/ORM/Query/Production/ComparisonExpression.php new file mode 100644 index 000000000..f26705771 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/ComparisonExpression.php @@ -0,0 +1,94 @@ +. + */ + +/** + * ComparisonExpression = ComparisonOperator ( QuantifiedExpression | Expression | "(" Subselect ")" ) + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_ComparisonExpression extends Doctrine_Query_Production +{ + protected $_operator; + + protected $_expression; + + protected $_isSubselect; + + + public function syntax($paramHolder) + { + // ComparisonExpression = ComparisonOperator ( QuantifiedExpression | Expression | "(" Subselect ")" ) + $this->_operator = $this->AST('ComparisonOperator', $paramHolder); + + if (($this->_isSubselect = $this->_isSubselect()) === true) { + $this->_parser->match('('); + $this->_expression = $this->AST('Subselect', $paramHolder); + $this->_parser->match(')'); + + $this->_isSubselect = true; + } else { + switch ($this->_parser->lookahead['type']) { + case Doctrine_Query_Token::T_ALL: + case Doctrine_Query_Token::T_ANY: + case Doctrine_Query_Token::T_SOME: + $this->_expression = $this->AST('QuantifiedExpression', $paramHolder); + break; + + default: + $this->_expression = $this->AST('Expression', $paramHolder); + break; + } + } + } + + public function buildSql() + { + return $this->_operator . ' ' . (($this->_isSubselect) ? + '(' . $this->_expression->buildSql() . ')' : $this->_expression->buildSql() + ); + } + + + /* Getters */ + public function getOperator() + { + return $this->_operator; + } + + + public function getExpression() + { + return $this->_expression; + } + + + public function isSubselect() + { + return $this->_isSubselect; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/ComparisonOperator.php b/lib/Doctrine/ORM/Query/Production/ComparisonOperator.php new file mode 100644 index 000000000..3728a2ac8 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/ComparisonOperator.php @@ -0,0 +1,82 @@ +. + */ + +/** + * ComparisonOperator = "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_ComparisonOperator extends Doctrine_Query_Production +{ + public function syntax($paramHolder) + { + switch ($this->_parser->lookahead['value']) { + case '=': + $this->_parser->match('='); + return '='; + break; + + case '<': + $this->_parser->match('<'); + $operator = '<'; + + if ($this->_isNextToken('=')) { + $this->_parser->match('='); + $operator .= '='; + } elseif ($this->_isNextToken('>')) { + $this->_parser->match('>'); + $operator .= '>'; + } + + return $operator; + break; + + case '>': + $this->_parser->match('>'); + $operator = '>'; + + if ($this->_isNextToken('=')) { + $this->_parser->match('='); + $operator .= '='; + } + + return $operator; + break; + + case '!': + $this->_parser->match('!'); + $this->_parser->match('='); + return '<>'; + break; + + default: + $this->_parser->syntaxError('=, <, <=, <>, >, >=, !='); + break; + } + } +} diff --git a/lib/Doctrine/ORM/Query/Production/ConditionalExpression.php b/lib/Doctrine/ORM/Query/Production/ConditionalExpression.php new file mode 100644 index 000000000..8537315e4 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/ConditionalExpression.php @@ -0,0 +1,79 @@ +. + */ + +/** + * ConditionalExpression = ConditionalTerm {"OR" ConditionalTerm} + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_ConditionalExpression extends Doctrine_Query_Production +{ + protected $_conditionalTerms = array(); + + + public function syntax($paramHolder) + { + // ConditionalExpression = ConditionalTerm {"OR" ConditionalTerm} + $this->_conditionalTerms[] = $this->AST('ConditionalTerm', $paramHolder); + + while ($this->_isNextToken(Doctrine_Query_Token::T_OR)) { + $this->_parser->match(Doctrine_Query_Token::T_OR); + $this->_conditionalTerms[] = $this->AST('ConditionalTerm', $paramHolder); + } + + // Optimize depth instances in AST + if (count($this->_conditionalTerms) == 1) { + return $this->_conditionalTerms[0]; + } + } + + + public function buildSql() + { + return implode(' OR ', $this->_mapConditionalTerms()); + } + + + protected function _mapConditionalTerms() + { + return array_map(array(&$this, '_mapConditionalTerm'), $this->_conditionalTerms); + } + + + protected function _mapConditionalTerm($value) + { + return $value->buildSql(); + } + + + /* Getters */ + public function getConditionalTerms() + { + return $this->_conditionalTerms; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/ConditionalFactor.php b/lib/Doctrine/ORM/Query/Production/ConditionalFactor.php new file mode 100644 index 000000000..19c277f26 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/ConditionalFactor.php @@ -0,0 +1,70 @@ +. + */ + +/** + * ConditionalFactor = ["NOT"] ConditionalPrimary + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_ConditionalFactor extends Doctrine_Query_Production +{ + protected $_conditionalPrimary; + + + public function syntax($paramHolder) + { + // ConditionalFactor = ["NOT"] ConditionalPrimary + $notFactor = false; + + if ($this->_isNextToken(Doctrine_Query_Token::T_NOT)) { + $this->_parser->match(Doctrine_Query_Token::T_NOT); + $notFactor = true; + } + + $this->_conditionalPrimary = $this->AST('ConditionalPrimary', $paramHolder); + + // Optimize depth instances in AST + if ( ! $notFactor) { + return $this->_conditionalPrimary; + } + } + + + public function buildSql() + { + // Do not need to check $notFactor. It'll be always present if we have this instance. + return 'NOT ' . $this->_conditionalPrimary->buildSql(); + } + + + /* Getters */ + public function getConditionalPrimary() + { + return $this->_conditionalPrimary; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/ConditionalPrimary.php b/lib/Doctrine/ORM/Query/Production/ConditionalPrimary.php new file mode 100644 index 000000000..74aee8b8b --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/ConditionalPrimary.php @@ -0,0 +1,108 @@ +. + */ + +/** + * ConditionalPrimary = SimpleConditionalExpression | "(" ConditionalExpression ")" + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_ConditionalPrimary extends Doctrine_Query_Production +{ + protected $_conditionalExpression; + + + public function syntax($paramHolder) + { + // ConditionalPrimary = SimpleConditionalExpression | "(" ConditionalExpression ")" + if ( ! $this->_isConditionalExpression()) { + return $this->AST('SimpleConditionalExpression', $paramHolder); + } + + $this->_parser->match('('); + $this->_conditionalExpression = $this->AST('ConditionalExpression', $paramHolder); + $this->_parser->match(')'); + } + + + public function buildSql() + { + return '(' . $this->_conditionalExpression->buildSql() . ')'; + } + + + protected function _isConditionalExpression() + { + $token = $this->_parser->lookahead; + $parenthesis = 0; + + if ($token['value'] === '(') { + $parenthesis++; + } + + while ($parenthesis > 0) { + $token = $this->_parser->getScanner()->peek(); + + if ($token['value'] === '(') { + $parenthesis++; + } elseif ($token['value'] === ')') { + $parenthesis--; + } else { + switch ($token['type']) { + case Doctrine_Query_Token::T_NOT: + case Doctrine_Query_Token::T_AND: + case Doctrine_Query_Token::T_OR: + case Doctrine_Query_Token::T_BETWEEN: + case Doctrine_Query_Token::T_LIKE: + case Doctrine_Query_Token::T_IN: + case Doctrine_Query_Token::T_IS: + case Doctrine_Query_Token::T_EXISTS: + return true; + + case Doctrine_Query_Token::T_NONE: + switch ($token['value']) { + case '=': + case '<': + case '>': + case '!': + return true; + } + break; + } + } + } + + return false; + } + + + /* Getters */ + public function getConditionalExpression() + { + return $this->_conditionalExpression; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/ConditionalTerm.php b/lib/Doctrine/ORM/Query/Production/ConditionalTerm.php new file mode 100644 index 000000000..fb0b9bfa0 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/ConditionalTerm.php @@ -0,0 +1,79 @@ +. + */ + +/** + * ConditionalTerm = ConditionalFactor {"AND" ConditionalFactor} + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_ConditionalTerm extends Doctrine_Query_Production +{ + protected $_conditionalFactors = array(); + + + public function syntax($paramHolder) + { + // ConditionalTerm = ConditionalFactor {"AND" ConditionalFactor} + $this->_conditionalFactors[] = $this->AST('ConditionalFactor', $paramHolder); + + while ($this->_isNextToken(Doctrine_Query_Token::T_AND)) { + $this->_parser->match(Doctrine_Query_Token::T_AND); + $this->_conditionalFactors[] = $this->AST('ConditionalFactor', $paramHolder); + } + + // Optimize depth instances in AST + if (count($this->_conditionalFactors) == 1) { + return $this->_conditionalFactors[0]; + } + } + + + public function buildSql() + { + return implode(' AND ', $this->_mapConditionalFactors()); + } + + + protected function _mapConditionalFactors() + { + return array_map(array(&$this, '_mapConditionalFactor'), $this->_conditionalFactors); + } + + + protected function _mapConditionalFactor($value) + { + return $value->buildSql(); + } + + + /* Getters */ + public function getConditionalFactors() + { + return $this->_conditionalFactors; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/DeleteClause.php b/lib/Doctrine/ORM/Query/Production/DeleteClause.php new file mode 100644 index 000000000..13d80936d --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/DeleteClause.php @@ -0,0 +1,73 @@ +. + */ + +/** + * DeleteClause = "DELETE" ["FROM"] VariableDeclaration + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_DeleteClause extends Doctrine_Query_Production +{ + protected $_variableDeclaration; + + + public function syntax($paramHolder) + { + // DeleteClause = "DELETE" ["FROM"] VariableDeclaration + $this->_parser->match(Doctrine_Query_Token::T_DELETE); + + if ($this->_isNextToken(Doctrine_Query_Token::T_FROM)) { + $this->_parser->match(Doctrine_Query_Token::T_FROM); + } + + $this->_variableDeclaration = $this->AST('VariableDeclaration', $paramHolder); + } + + public function buildSql() + { + return 'DELETE FROM ' . $this->_variableDeclaration->buildSql(); + } + + /** + * Visitor support + * + * @param object $visitor + */ + public function accept($visitor) + { + $this->_variableDeclaration->accept($visitor); + $visitor->visitDeleteClause($this); + } + + /* Getters */ + + public function getVariableDeclaration() + { + return $this->_variableDeclaration; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/DeleteStatement.php b/lib/Doctrine/ORM/Query/Production/DeleteStatement.php new file mode 100644 index 000000000..b048d6ad2 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/DeleteStatement.php @@ -0,0 +1,85 @@ +. + */ + +/** + * DeleteStatement = DeleteClause [WhereClause] + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_DeleteStatement extends Doctrine_Query_Production +{ + protected $_deleteClause; + + protected $_whereClause; + + + public function syntax($paramHolder) + { + // DeleteStatement = DeleteClause [WhereClause] + $this->_deleteClause = $this->AST('DeleteClause', $paramHolder); + + if ($this->_isNextToken(Doctrine_Query_Token::T_WHERE)) { + $this->_whereClause = $this->AST('WhereClause', $paramHolder); + } + } + + + public function buildSql() + { + // The 1=1 is needed to workaround the affected_rows in MySQL. + // Simple "DELETE FROM table_name" gives 0 affected rows. + return $this->_deleteClause->buildSql() . (($this->_whereClause !== null) + ? ' ' . $this->_whereClause->buildSql() : ' WHERE 1 = 1'); + } + + /** + * Visitor support + * + * @param object $visitor + */ + public function accept($visitor) + { + $this->_deleteClause->accept($visitor); + if ($this->_whereClause) { + $this->_whereClause->accept($visitor); + } + $visitor->visitDeleteStatement($this); + } + + /* Getters */ + + public function getDeleteClause() + { + return $this->_deleteClause; + } + + public function getWhereClause() + { + return $this->_whereClause; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/ExistsExpression.php b/lib/Doctrine/ORM/Query/Production/ExistsExpression.php new file mode 100644 index 000000000..c83032d52 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/ExistsExpression.php @@ -0,0 +1,61 @@ +. + */ + +/** + * ExistsExpression = "EXISTS" "(" Subselect ")" + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_ExistsExpression extends Doctrine_Query_Production +{ + protected $_subselect; + + + public function syntax($paramHolder) + { + // ExistsExpression = "EXISTS" "(" Subselect ")" + $this->_parser->match(Doctrine_Query_Token::T_EXISTS); + + $this->_parser->match('('); + $this->_subselect = $this->AST('Subselect', $paramHolder); + $this->_parser->match(')'); + } + + + public function buildSql() + { + return 'EXISTS (' . $this->_subselect->buildSql() . ')'; + } + + + /* Getters */ + public function getSubselect() + { + return $this->_subselect; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/Expression.php b/lib/Doctrine/ORM/Query/Production/Expression.php new file mode 100644 index 000000000..14e0183fe --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/Expression.php @@ -0,0 +1,96 @@ +. + */ + +/** + * Expression = Term {("+" | "-") Term} + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_Expression extends Doctrine_Query_Production +{ + protected $_terms = array(); + + + public function syntax($paramHolder) + { + // Expression = Term {("+" | "-") Term} + $this->_terms[] = $this->AST('Term', $paramHolder); + + while ($this->_isNextToken('+') || $this->_isNextToken('-')) { + if ($this->_isNextToken('+')) { + $this->_parser->match('+'); + $this->_terms[] = '+'; + } else{ + $this->_parser->match('-'); + $this->_terms[] = '-'; + } + + $this->_terms[] = $this->AST('Term', $paramHolder); + } + + // Optimize depth instances in AST + if (count($this->_terms) == 1) { + return $this->_terms[0]; + } + } + + + public function semantical($paramHolder) + { + for ($i = 0, $l = count($this->_terms); $i < $l; $i++) { + if ($this->_terms[$i] != '+' && $this->_terms[$i] != '-') { + $this->_terms[$i]->semantical($paramHolder); + } + } + } + + + public function buildSql() + { + return implode(' ', $this->_mapTerms()); + } + + + protected function _mapTerms() + { + return array_map(array(&$this, '_mapTerm'), $this->_terms); + } + + + protected function _mapTerm($value) + { + return (is_string($value) ? $value : $value->buildSql()); + } + + + /* Getters */ + public function getTerms() + { + return $this->_terms; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/Factor.php b/lib/Doctrine/ORM/Query/Production/Factor.php new file mode 100644 index 000000000..b4d585861 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/Factor.php @@ -0,0 +1,94 @@ +. + */ + +/** + * Factor = [("+" | "-")] Primary + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_Factor extends Doctrine_Query_Production +{ + protected $_type; + + protected $_primary; + + + public function syntax($paramHolder) + { + // Factor = [("+" | "-")] Primary + if ($this->_isNextToken('+')) { + $this->_parser->match('+'); + $this->_type = '+'; + } elseif ($this->_isNextToken('-')) { + $this->_parser->match('-'); + $this->_type = '-'; + } + + $this->_primary = $this->AST('Primary', $paramHolder); + + // Optimize depth instances in AST + if ($this->_type === null) { + return $this->_primary; + } + } + + + public function semantical($paramHolder) + { + $this->_primary->semantical($paramHolder); + } + + + public function buildSql() + { + return $this->_type . ' ' . $this->_primary->buildSql(); + } + + /** + * Visitor support + * + * @param object $visitor + */ + public function accept($visitor) + { + $this->_primary->accept($visitor); + $visitor->visitFactor($this); + } + + /* Getters */ + + public function getType() + { + return $this->_type; + } + + public function getPrimary() + { + return $this->_primary; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/FieldIdentificationVariable.php b/lib/Doctrine/ORM/Query/Production/FieldIdentificationVariable.php new file mode 100644 index 000000000..60cd86d46 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/FieldIdentificationVariable.php @@ -0,0 +1,91 @@ +. + */ + +/** + * FieldIdentificationVariable = identifier + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_FieldIdentificationVariable extends Doctrine_Query_Production +{ + protected $_fieldAlias; + + protected $_columnAlias; + + + public function syntax($paramHolder) + { + // FieldIdentificationVariable = identifier + $this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER); + $this->_fieldAlias = $this->_parser->token['value']; + } + + + public function semantical($paramHolder) + { + $parserResult = $this->_parser->getParserResult(); + + if ($parserResult->hasQueryField($this->_fieldAlias)) { + // We should throw semantical error if there's already a component for this alias + $fieldName = $parserResult->getQueryField($this->_fieldAlias); + + $message = "Cannot re-declare field alias '{$this->_fieldAlias}'" + . "for '".$paramHolder->get('fieldName')."'."; + + $this->_parser->semanticalError($message); + } + + // Now we map it in queryComponent + $componentAlias = Doctrine_Query_Production::DEFAULT_QUERYCOMPONENT; + $queryComponent = $parserResult->getQueryComponent($componentAlias); + + $idx = count($queryComponent['scalar']); + $queryComponent['scalar'][$idx] = $this->_fieldAlias; + $parserResult->setQueryComponent($componentAlias, $queryComponent); + + // And also in field aliases + $parserResult->setQueryField($queryComponent['scalar'][$idx], $idx); + + // Build the column alias + $this->_columnAlias = $parserResult->getTableAliasFromComponentAlias($componentAlias) + . Doctrine_Query_Production::SQLALIAS_SEPARATOR . $idx; + } + + + /* Getters */ + public function getFieldAlias() + { + return $this->_fieldAlias; + } + + + public function getColumnAlias() + { + return $this->_columnAlias; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/FromClause.php b/lib/Doctrine/ORM/Query/Production/FromClause.php new file mode 100644 index 000000000..604ac4beb --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/FromClause.php @@ -0,0 +1,81 @@ +. + */ + +/** + * FromClause = "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration} + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_FromClause extends Doctrine_Query_Production +{ + protected $_identificationVariableDeclaration = array(); + + + public function syntax($paramHolder) + { + // FromClause = "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration} + $this->_parser->match(Doctrine_Query_Token::T_FROM); + + $this->_identificationVariableDeclaration[] = $this->AST('IdentificationVariableDeclaration', $paramHolder); + + while ($this->_isNextToken(',')) { + $this->_parser->match(','); + $this->_identificationVariableDeclaration[] = $this->AST('IdentificationVariableDeclaration', $paramHolder); + } + } + + + public function buildSql() + { + //echo "FromClause:\n"; + //for ($i = 0; $i < count($this->_identificationVariableDeclaration);$i++) { + // echo (($this->_identificationVariableDeclaration[$i] instanceof IdentificationVariableDeclaration) ? get_class($this->_identificationVariableDeclaration[$i]) : get_class($this->_identificationVariableDeclaration[$i])) . "\n"; + //} + + return 'FROM ' . implode(', ', $this->_mapIdentificationVariableDeclarations()); + } + + + protected function _mapIdentificationVariableDeclarations() + { + return array_map(array(&$this, '_mapIdentificationVariableDeclaration'), $this->_identificationVariableDeclaration); + } + + + protected function _mapIdentificationVariableDeclaration($value) + { + return $value->buildSql(); + } + + + /* Getters */ + public function getIdentificationVariableDeclarations() + { + return $this->_identificationVariableDeclaration; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/Function.php b/lib/Doctrine/ORM/Query/Production/Function.php new file mode 100644 index 000000000..4e37d3ef0 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/Function.php @@ -0,0 +1,104 @@ +. + */ + +/** + * Function = identifier "(" [Expression {"," Expression}] ")" + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_Function extends Doctrine_Query_Production +{ + protected $_functionName; + + protected $_arguments = array(); + + + public function syntax($paramHolder) + { + // Function = identifier "(" [Expression {"," Expression}] ")" + $this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER); + $this->_functionName = $this->_parser->token['value']; + + $this->_parser->match('('); + + if ( ! $this->_isNextToken(')')) { + $this->_arguments[] = $this->AST('Expression', $paramHolder); + + while ($this->_isNextToken(',')) { + $this->_parser->match(','); + + $this->_arguments[] = $this->AST('Expression', $paramHolder); + } + } + + $this->_parser->match(')'); + } + + + public function buildSql() + { + return $this->_functionName . '(' . implode(', ', $this->_mapArguments()) . ')'; + } + + + protected function _mapArguments() + { + return array_map(array(&$this, '_mapArgument'), $this->_arguments); + } + + + protected function _mapArgument($value) + { + return $value->buildSql(); + } + + /** + * Visitor support + * + * @param object $visitor + */ + public function accept($visitor) + { + foreach ($this->_arguments as $argument) { + $argument->accept($visitor); + } + $visitor->visitFunction($this); + } + + /* Getters */ + + public function getFunctionName() + { + return $this->_functionName; + } + + public function getArguments() + { + return $this->_arguments; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/GroupByClause.php b/lib/Doctrine/ORM/Query/Production/GroupByClause.php new file mode 100644 index 000000000..f9afdfa2d --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/GroupByClause.php @@ -0,0 +1,89 @@ +. + */ + +/** + * GroupByClause = "GROUP" "BY" GroupByItem {"," GroupByItem} + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_GroupByClause extends Doctrine_Query_Production +{ + protected $_groupByItems = array(); + + + public function syntax($paramHolder) + { + $this->_parser->match(Doctrine_Query_Token::T_GROUP); + $this->_parser->match(Doctrine_Query_Token::T_BY); + + $this->_groupByItems[] = $this->AST('GroupByItem', $paramHolder); + + while ($this->_isNextToken(',')) { + $this->_parser->match(','); + $this->_groupByItems[] = $this->AST('GroupByItem', $paramHolder); + } + } + + + public function buildSql() + { + return 'GROUP BY ' . implode(', ', $this->_mapGroupByItems()); + } + + + protected function _mapGroupByItems() + { + return array_map(array(&$this, '_mapGroupByItem'), $this->_groupByItems); + } + + + protected function _mapGroupByItem($value) + { + return $value->buildSql(); + } + + /** + * Visitor support + * + * @param object $visitor + */ + public function accept($visitor) + { + foreach ($this->_groupByItems as $item) { + $item->accept($visitor); + } + $visitor->visitGroupByClause($this); + } + + /* Getters */ + + public function getGroupByItems() + { + return $this->_groupByItems; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/GroupByItem.php b/lib/Doctrine/ORM/Query/Production/GroupByItem.php new file mode 100644 index 000000000..bb441f7df --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/GroupByItem.php @@ -0,0 +1,50 @@ +. + */ + +/** + * OrderByItem = PathExpression + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_GroupByItem extends Doctrine_Query_Production +{ + public function syntax($paramHolder) + { + return $this->AST('PathExpression', $paramHolder); + } + + /** + * Visitor support + * + * @param object $visitor + */ + public function accept($visitor) + { + $visitor->visitGroupByItem($this); + } +} diff --git a/lib/Doctrine/ORM/Query/Production/HavingClause.php b/lib/Doctrine/ORM/Query/Production/HavingClause.php new file mode 100644 index 000000000..1c5817d82 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/HavingClause.php @@ -0,0 +1,70 @@ +. + */ + +/** + * HavingClause = "HAVING" ConditionalExpression + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_HavingClause extends Doctrine_Query_Production +{ + protected $_conditionalExpression; + + + public function syntax($paramHolder) + { + // HavingClause = "HAVING" ConditionalExpression + $this->_parser->match(Doctrine_Query_Token::T_HAVING); + + $this->_conditionalExpression = $this->AST('ConditionalExpression', $paramHolder); + } + + + public function buildSql() + { + return 'HAVING ' . $this->_conditionalExpression->buildSql(); + } + + /** + * Visitor support + * + * @param object $visitor + */ + public function accept($visitor) + { + $this->_conditionalExpression->accept($visitor); + $visitor->visitHavingClause($this); + } + + /* Getters */ + + public function getConditionalExpression() + { + return $this->_conditionalExpression; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/IdentificationVariable.php b/lib/Doctrine/ORM/Query/Production/IdentificationVariable.php new file mode 100644 index 000000000..3fd786d17 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/IdentificationVariable.php @@ -0,0 +1,65 @@ +. + */ + +/** + * IdentificationVariable = identifier + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_IdentificationVariable extends Doctrine_Query_Production +{ + protected $_componentAlias; + + + public function syntax($paramHolder) + { + // IdentificationVariable = identifier + $this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER); + $this->_componentAlias = $this->_parser->token['value']; + } + + + public function semantical($paramHolder) + { + $parserResult = $this->_parser->getParserResult(); + + if ($parserResult->hasQueryComponent($this->_componentAlias)) { + // We should throw semantical error if there's already a component for this alias + $queryComponent = $parserResult->getQueryComponent($this->_componentAlias); + $componentName = $queryComponent['metadata']->getClassName(); + + $message = "Cannot re-declare component alias '{$this->_componentAlias}'" + . "for '".$paramHolder->get('componentName')."'. It was already declared for " + . "component '{$componentName}'."; + + $this->_parser->semanticalError($message); + } + + return $this->_componentAlias; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/IdentificationVariableDeclaration.php b/lib/Doctrine/ORM/Query/Production/IdentificationVariableDeclaration.php new file mode 100644 index 000000000..f11dae0c1 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/IdentificationVariableDeclaration.php @@ -0,0 +1,92 @@ +. + */ + +/** + * IdentificationVariableDeclaration = RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration} + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_IdentificationVariableDeclaration extends Doctrine_Query_Production +{ + protected $_rangeVariableDeclaration; + + protected $_indexBy; + + protected $_joinVariableDeclarations = array(); + + + public function syntax($paramHolder) + { + $this->_rangeVariableDeclaration = $this->AST('RangeVariableDeclaration', $paramHolder); + + if ($this->_isNextToken(Doctrine_Query_Token::T_INDEX)) { + $paramHolder->set('componentAlias', $this->_rangeVariableDeclaration->getIdentificationVariable()); + $this->_indexBy = $this->AST('IndexBy', $paramHolder); + $paramHolder->remove('componentAlias'); + } + + while ( + $this->_isNextToken(Doctrine_Query_Token::T_LEFT) || + $this->_isNextToken(Doctrine_Query_Token::T_INNER) || + $this->_isNextToken(Doctrine_Query_Token::T_JOIN) + ) { + $this->_joinVariableDeclarations[] = $this->AST('JoinVariableDeclaration', $paramHolder); + } + } + + + public function buildSql() + { + $str = $this->_rangeVariableDeclaration->buildSql(); + + for ($i = 0, $l = count($this->_joinVariableDeclarations); $i < $l; $i++) { + $str .= ' ' . $this->_joinVariableDeclarations[$i]->buildSql(); + } + + return $str; + } + + + /* Getters */ + public function getRangeVariableDeclaration() + { + return $this->_rangeVariableDeclaration; + } + + + public function getIndexBy() + { + return $this->_indexBy; + } + + + public function getJoinVariableDeclarations() + { + return $this->_joinVariableDeclarations; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/InExpression.php b/lib/Doctrine/ORM/Query/Production/InExpression.php new file mode 100644 index 000000000..dd89222c2 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/InExpression.php @@ -0,0 +1,109 @@ +. + */ + +/** + * InExpression = ["NOT"] "IN" "(" (Atom {"," Atom} | Subselect) ")" + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_InExpression extends Doctrine_Query_Production +{ + protected $_not; + + protected $_subselect; + + protected $_atoms = array(); + + + public function syntax($paramHolder) + { + // InExpression = ["NOT"] "IN" "(" (Atom {"," Atom} | Subselect) ")" + $this->_not = false; + + if ($this->_isNextToken(Doctrine_Query_Token::T_NOT)) { + $this->_parser->match(Doctrine_Query_Token::T_NOT); + $this->_not = true; + } + + $this->_parser->match(Doctrine_Query_Token::T_IN); + + $this->_parser->match('('); + + if ($this->_isNextToken(Doctrine_Query_Token::T_SELECT)) { + $this->_subselect = $this->AST('Subselect', $paramHolder); + } else { + $this->_atoms[] = $this->AST('Atom', $paramHolder); + + while ($this->_isNextToken(',')) { + $this->_parser->match(','); + $this->_atoms[] = $this->AST('Atom', $paramHolder); + } + } + + $this->_parser->match(')'); + } + + + public function buildSql() + { + return (($this->_not) ? 'NOT ' : '') . 'IN (' + . (($this->_subselect !== null) ? $this->_subselect->buildSql() : implode(', ', $this->_mapAtoms())) + . ')'; + } + + + protected function _mapAtoms() + { + return array_map(array(&$this, '_mapAtom'), $this->_atoms); + } + + + protected function _mapAtom($value) + { + return $value->buildSql(); + } + + + /* Getters */ + public function isNot() + { + return $this->_not; + } + + + public function getSubselect() + { + return $this->_subselect; + } + + + public function getAtoms() + { + return $this->_atoms; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/IndexBy.php b/lib/Doctrine/ORM/Query/Production/IndexBy.php new file mode 100644 index 000000000..fc02a7c91 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/IndexBy.php @@ -0,0 +1,121 @@ +. + */ + +/** + * IndexBy = "INDEX" "BY" identifier + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_IndexBy extends Doctrine_Query_Production +{ + protected $_componentAlias; + + protected $_fieldName; + + + public function syntax($paramHolder) + { + $this->_componentAlias = $paramHolder->get('componentAlias'); + + $this->_parser->match(Doctrine_Query_Token::T_INDEX); + $this->_parser->match(Doctrine_Query_Token::T_BY); + $this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER); + + $this->_fieldName = $this->_parser->token['value']; + } + + + public function semantical($paramHolder) + { + $parserResult = $this->_parser->getParserResult(); + + //echo "Component alias: " . $this->_componentAlias . "\n"; + //echo "Has query component: " . ($parserResult->hasQueryComponent($this->_componentAlias) ? "TRUE" : "FALSE") . "\n"; + //$qc = $parserResult->getQueryComponents(); + //$qc = array_keys($qc); + //echo "Query Components: " . var_export($qc, true) . "\n"; + + try { + $queryComponent = $parserResult->getQueryComponent($this->_componentAlias); + $classMetadata = $queryComponent['metadata']; + } catch (Doctrine_Exception $e) { + $this->_parser->semanticalError($e->getMessage()); + + return; + } + + if ($classMetadata instanceof Doctrine_ClassMetadata && ! $classMetadata->hasField($this->_fieldName)) { + $this->_parser->semanticalError( + "Cannot use key mapping. Field '" . $this->_fieldName . "' " . + "does not exist in component '" . $classMetadata->getClassName() . "'.", + $this->_parser->token + ); + } + + // The INDEXBY field must be either the (primary && not part of composite pk) || (unique && notnull) + $columnMapping = $classMetadata->getFieldMapping($this->_fieldName); + + if ( ! $classMetadata->isIdentifier($this->_fieldName) && ! $classMetadata->isUniqueField($this->_fieldName) && ! $classMetadata->isNotNull($this->_fieldName)) { + $this->_parser->semanticalError( + "Field '" . $this->_fieldName . "' of component '" . $classMetadata->getClassName() . + "' must be unique and notnull to be used as index.", + $this->_parser->token + ); + } + + if ($classMetadata->isIdentifier($this->_fieldName) && $classMetadata->isIdentifierComposite()) { + $this->_parser->semanticalError( + "Field '" . $this->_fieldName . "' of component '" . $classMetadata->getClassName() . + "' must be primary and not part of a composite primary key to be used as index.", + $this->_parser->token + ); + } + + $queryComponent['map'] = $this->_fieldName; + $parserResult->setQueryComponent($this->_componentAlias, $queryComponent); + } + + + public function buildSql() + { + return ''; + } + + + /* Getters */ + public function getComponentAlias() + { + return $this->_componentAlias; + } + + + public function getFieldName() + { + return $this->_fieldName; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/Join.php b/lib/Doctrine/ORM/Query/Production/Join.php new file mode 100644 index 000000000..4af2f8ee6 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/Join.php @@ -0,0 +1,162 @@ +. + */ + +/** + * Join = ["LEFT" | "INNER"] "JOIN" RangeVariableDeclaration [("ON" | "WITH") ConditionalExpression] + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_Join extends Doctrine_Query_Production +{ + protected $_joinType; + + protected $_rangeVariableDeclaration; + + protected $_whereType; + + protected $_conditionalExpression; + + + public function syntax($paramHolder) + { + $this->_joinType = 'INNER'; + $this->_whereType = 'WITH'; + + if ($this->_isNextToken(Doctrine_Query_Token::T_LEFT)) { + $this->_parser->match(Doctrine_Query_Token::T_LEFT); + + $this->_joinType = 'LEFT'; + } else if ($this->_isNextToken(Doctrine_Query_Token::T_INNER)) { + $this->_parser->match(Doctrine_Query_Token::T_INNER); + } + + $this->_parser->match(Doctrine_Query_Token::T_JOIN); + + $this->_rangeVariableDeclaration = $this->AST('RangeVariableDeclaration', $paramHolder); + + if ($this->_isNextToken(Doctrine_Query_Token::T_ON)) { + $this->_parser->match(Doctrine_Query_Token::T_ON); + + $this->_whereType = 'ON'; + + $this->_conditionalExpression = $this->AST('ConditionalExpression', $paramHolder); + } else if ($this->_isNextToken(Doctrine_Query_Token::T_WITH)) { + $this->_parser->match(Doctrine_Query_Token::T_WITH); + + $this->_conditionalExpression = $this->AST('ConditionalExpression', $paramHolder); + } + } + + + public function buildSql() + { + $parserResult = $this->_parser->getParserResult(); + + // Get the connection for the component + $conn = $this->_em->getConnection(); + + $sql = $this->_joinType . ' JOIN ' . $this->_rangeVariableDeclaration->buildSql(); + $conditionExpression = isset($this->_conditionExpression) + ? $this->_conditionExpression->buildSql() : ''; + + if ($this->_whereType == 'ON') { + return $sql . ' ON ' . $conditionExpression; + } + + // We need to build the join conditions. Retrieving AssociationMapping + $queryComponent = $this->_rangeVariableDeclaration->getQueryComponent(); + $association = $queryComponent['relation']; + $joinColumns = array(); + + if ($association->isOneToMany() || $association->isOneToOne()) { + if ($association->isInverseSide()) { + // joinColumns are found on the other (owning) side + $targetClass = $this->_em->getClassMetadata($association->getTargetEntityName()); + $joinColumns = $targetClass->getAssociationMapping($association->getMappedByFieldName()) + ->getTargetToSourceKeyColumns(); + } else { + $joinColumns = $association->getSourceToTargetKeyColumns(); + } + } else { + //TODO: many-many + } + + $relationConditionExpression = ''; + + // We have an array('localColumn' => 'foreignColumn', ...) here + foreach ($joinColumns as $localColumn => $foreignColumn) { + // leftExpression = rightExpression + + // Defining leftExpression + $leftExpression = $conn->quoteIdentifier( + $parserResult->getTableAliasFromComponentAlias($queryComponent['parent']) . '.' . $localColumn + ); + + // Defining rightExpression + $rightExpression = $conn->quoteIdentifier( + $parserResult->getTableAliasFromComponentAlias( + $this->_rangeVariableDeclaration->getIdentificationVariable() + ) . '.' . $foreignColumn + ); + + // Building the relation + $relationConditionExpression .= (($relationConditionExpression != '') ? ' AND ' : '') + . $leftExpression . ' = ' . $rightExpression; + } + + $sql .= ' ON ' . $relationConditionExpression; + $sql .= empty($conditionExpression) ? '' : ' AND (' . $conditionExpression . ')'; + + return $sql; + } + + + /* Getters */ + public function getJoinType() + { + return $this->_joinType; + } + + + public function getRangeVariableDeclaration() + { + return $this->_rangeVariableDeclaration; + } + + + public function getWhereType() + { + return $this->_whereType; + } + + + public function getConditionalExpression() + { + return $this->_conditionalExpression; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/JoinVariableDeclaration.php b/lib/Doctrine/ORM/Query/Production/JoinVariableDeclaration.php new file mode 100644 index 000000000..433c17209 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/JoinVariableDeclaration.php @@ -0,0 +1,70 @@ +. + */ + +/** + * JoinVariableDeclaration = Join [IndexBy] + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_JoinVariableDeclaration extends Doctrine_Query_Production +{ + protected $_join; + + protected $_indexBy; + + + public function syntax($paramHolder) + { + $this->_join = $this->AST('Join', $paramHolder); + + if ($this->_isNextToken(Doctrine_Query_Token::T_INDEX)) { + $paramHolder->set('componentAlias', $this->_join->getRangeVariableDeclaration()->getIdentificationVariable()); + $this->_indexBy = $this->AST('IndexBy', $paramHolder); + $paramHolder->remove('componentAlias'); + } + } + + + public function buildSql() + { + return $this->_join->buildSql() . (isset($this->_indexby) ? $this->_indexby->buildSql() . ' ' : ''); + } + + + /* Getters */ + public function getJoin() + { + return $this->_join; + } + + + public function getIndexBy() + { + return $this->_indexBy; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Production/LikeExpression.php b/lib/Doctrine/ORM/Query/Production/LikeExpression.php new file mode 100644 index 000000000..f14b767e4 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/LikeExpression.php @@ -0,0 +1,91 @@ +. + */ + +/** + * LikeExpression = ["NOT"] "LIKE" Expression ["ESCAPE" string] + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_LikeExpression extends Doctrine_Query_Production +{ + protected $_not; + + protected $_expression; + + protected $_escapeString; + + + public function syntax($paramHolder) + { + // LikeExpression = ["NOT"] "LIKE" Expression ["ESCAPE" string] + $this->_escapeString = null; + $this->_not = false; + + if ($this->_isNextToken(Doctrine_Query_Token::T_NOT)) { + $this->_parser->match(Doctrine_Query_Token::T_NOT); + $this->_not = true; + } + + $this->_parser->match(Doctrine_Query_Token::T_LIKE); + + $this->_expression = $this->AST('Expression', $paramHolder); + + if ($this->_isNextToken(Doctrine_Query_Token::T_ESCAPE)) { + $this->_parser->match(Doctrine_Query_Token::T_ESCAPE); + $this->_parser->match(Doctrine_Query_Token::T_STRING); + + $this->_escapeString = $this->_parser->token['value']; + } + } + + + public function buildSql() + { + return (($this->_not) ? 'NOT ' : '') . 'LIKE ' . $this->_expression->buildSql() + . (($this->_escapeString !== null) ? ' ESCAPE ' . $this->_escapeString : ''); + } + + + /* Getters */ + public function isNot() + { + return $this->_not; + } + + + public function getExpression() + { + return $this->_expression; + } + + + public function getEscapeString() + { + return $this->_escapeString; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/LimitClause.php b/lib/Doctrine/ORM/Query/Production/LimitClause.php new file mode 100644 index 000000000..969152501 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/LimitClause.php @@ -0,0 +1,65 @@ +. + */ + +/** + * LimitClause = "LIMIT" integer + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_LimitClause extends Doctrine_Query_Production +{ + protected $_limit; + + + public function execute(array $params = array()) + { + $this->_parser->match(Doctrine_Query_Token::T_LIMIT); + + $this->_parser->match(Doctrine_Query_Token::T_INTEGER); + $this->_limit = $this->_parser->token['value']; + + return $this; + } + + /** + * Visitor support + * + * @param object $visitor + */ + public function accept($visitor) + { + $visitor->visitLimitClause($this); + } + + /* Getters */ + + public function getLimit() + { + return $this->_limit; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/NullComparisonExpression.php b/lib/Doctrine/ORM/Query/Production/NullComparisonExpression.php new file mode 100644 index 000000000..9064f3d98 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/NullComparisonExpression.php @@ -0,0 +1,65 @@ +. + */ + +/** + * NullComparisonExpression = "IS" ["NOT"] "NULL" + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_NullComparisonExpression extends Doctrine_Query_Production +{ + protected $_not; + + + public function syntax($paramHolder) + { + $this->_not = false; + + $this->_parser->match(Doctrine_Query_Token::T_IS); + + if ($this->_isNextToken(Doctrine_Query_Token::T_NOT)) { + $this->_parser->match(Doctrine_Query_Token::T_NOT); + $this->_not = true; + } + + $this->_parser->match(Doctrine_Query_Token::T_NULL); + } + + + public function buildSql() + { + return 'IS ' . (($this->_not) ? 'NOT ' : '') . 'NULL'; + } + + + /* Getters */ + public function isNot() + { + return $this->_not; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/OffsetClause.php b/lib/Doctrine/ORM/Query/Production/OffsetClause.php new file mode 100644 index 000000000..b8d689343 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/OffsetClause.php @@ -0,0 +1,74 @@ +. + */ + +/** + * OffsetClause = "OFFSET" integer + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_OffsetClause extends Doctrine_Query_Production +{ + protected $_offset; + + + public function execute(array $params = array()) + { + $this->_parser->match(Doctrine_Query_Token::T_OFFSET); + + $this->_parser->match(Doctrine_Query_Token::T_INTEGER); + $this->_offset = $this->_parser->token['value']; + + return $this; + } + + + public function buildSql() + { + // [TODO] How to deal with different DBMS here? + // The responsability to apply the limit-subquery is from + // SelectStatement, not this object's one. + return ' OFFSET ' . $this->_offset; + } + + /** + * Visitor support + * + * @param object $visitor + */ + public function accept($visitor) + { + $visitor->visitOffsetClause($this); + } + + /* Getters */ + + public function getOffset() + { + return $this->_offset; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/OrderByClause.php b/lib/Doctrine/ORM/Query/Production/OrderByClause.php new file mode 100644 index 000000000..bfee179f7 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/OrderByClause.php @@ -0,0 +1,88 @@ +. + */ + +/** + * OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem} + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_OrderByClause extends Doctrine_Query_Production +{ + protected $_orderByItems = array(); + + + public function syntax($paramHolder) + { + $this->_parser->match(Doctrine_Query_Token::T_ORDER); + $this->_parser->match(Doctrine_Query_Token::T_BY); + + $this->_orderByItems[] = $this->AST('OrderByItem', $paramHolder); + + while ($this->_isNextToken(',')) { + $this->_parser->match(','); + $this->_orderByItems[] = $this->AST('OrderByItem', $paramHolder); + } + } + + + public function buildSql() + { + $str = 'ORDER BY '; + + for ($i = 0, $l = count($this->_orderByItems); $i < $l; $i++) { + if ($i != 0) { + $str .= ', '; + } + + $str .= ( $this->_orderByItems[$i] instanceof Doctrine_Query_Production ) ? + $this->_orderByItems[$i]->buildSql() : $this->_orderByItems[$i]; + } + + return $str; + } + + /** + * Visitor support + * + * @param object $visitor + */ + public function accept($visitor) + { + foreach ($this->_orderByItems as $item) { + $item->accept($visitor); + } + $visitor->visitOrderByClause($this); + } + + /* Getters */ + + public function getOrderByItems() + { + return $this->_orderByItems; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/OrderByItem.php b/lib/Doctrine/ORM/Query/Production/OrderByItem.php new file mode 100644 index 000000000..76a27553a --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/OrderByItem.php @@ -0,0 +1,82 @@ +. + */ + +/** + * OrderByItem = Expression ["ASC" | "DESC"] + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_OrderByItem extends Doctrine_Query_Production +{ + protected $_expression; + + protected $_orderType; + + + public function syntax($paramHolder) + { + $this->_expression = $this->AST('Expression', $paramHolder); + $this->_orderType = 'ASC'; + + if ($this->_isNextToken(Doctrine_Query_Token::T_ASC)) { + $this->_parser->match(Doctrine_Query_Token::T_ASC); + } elseif ($this->_isNextToken(Doctrine_Query_Token::T_DESC)) { + $this->_parser->match(Doctrine_Query_Token::T_DESC); + $this->_orderType = 'DESC'; + } + } + + + public function buildSql() + { + return $this->_expression->buildSql() . ' ' . $this->_orderType; + } + + /** + * Visitor support + * + * @param object $visitor + */ + public function accept($visitor) + { + $this->_expression->accept($visitor); + $visitor->visitOrderByItem($this); + } + + /* Getters */ + + public function getExpression() + { + return $this->_expression; + } + + public function getOrderType() + { + return $this->_orderType; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/PathExpression.php b/lib/Doctrine/ORM/Query/Production/PathExpression.php new file mode 100644 index 000000000..1f8e4b7e2 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/PathExpression.php @@ -0,0 +1,171 @@ +. + */ + +/** + * PathExpression = identifier { "." identifier } + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_PathExpression extends Doctrine_Query_Production +{ + protected $_identifiers = array(); + + protected $_fieldName; + + protected $_componentAlias; + + + public function syntax($paramHolder) + { + $this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER); + $this->_identifiers[] = $this->_parser->token['value']; + + while ($this->_isNextToken('.')) { + $this->_parser->match('.'); + $this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER); + + $this->_identifiers[] = $this->_parser->token['value']; + } + + $this->_fieldName = array_pop($this->_identifiers); + } + + + public function semantical($paramHolder) + { + $parserResult = $this->_parser->getParserResult(); + $classMetadata = null; + + if (($l = count($this->_identifiers)) == 0) { + // No metadata selection until now. We might need to deal with: + // DELETE FROM Obj alias WHERE field = X + $queryComponents = $parserResult->getQueryComponents(); + + // Check if we have more than one queryComponent defined + if (count($queryComponents) != 2) { + $this->_parser->semanticalError("Undefined component alias for field '{$this->_fieldName}'", $this->_parser->token); + } + + // Retrieve ClassMetadata + $k = array_keys($queryComponents); + $this->_componentAlias = $k[1]; + + $classMetadata = $queryComponents[$this->_componentAlias]['metadata']; + } else { + $this->_componentAlias = $path = $this->_identifiers[0]; + $queryComponent = $parserResult->getQueryComponent($path); + + // We should have a semantical error if the queryComponent does not exists yet + if ($queryComponent === null) { + $this->_parser->semanticalError("Undefined component alias for '{$path}'", $this->_parser->token); + } + + // Initializing ClassMetadata + $classMetadata = $queryComponent['metadata']; + + // Looping through relations + for ($i = 1; $i < $l; $i++) { + $relationName = $this->_identifiers[$i]; + $path .= '.' . $relationName; + + if ( ! $classMetadata->hasAssociation($relationName)) { + $className = $classMetadata->getClassName(); + + $this->_parser->semanticalError( + "Relation '{$relationName}' does not exist in component '{$className}' when trying to get the path '{$path}'", + $this->_parser->token + ); + } + + // We inspect for queryComponent of relations, since we are using them + if ( ! $parserResult->hasQueryComponent($path)) { + $this->_parser->semanticalError("Cannot use the path '{$path}' without defining it in FROM.", $this->_parser->token); + } + + // Assigning new componentAlias, queryComponent and classMetadata + $this->_componentAlias = $path; + + $queryComponent = $parserResult->getQueryComponent($path); + $classMetadata = $queryComponent['metadata']; + } + } + + // Now we inspect for field existance + if ( ! $classMetadata->hasField($this->_fieldName)) { + $className = $classMetadata->getClassName(); + + $this->_parser->semanticalError("Field '{$this->_fieldName}' does not exist in component '{$className}'", $this->_parser->token); + } + } + + + public function buildSql() + { + // Basic handy variables + $parserResult = $this->_parser->getParserResult(); + + // Retrieving connection + $conn = $this->_em->getConnection(); + + // Looking for queryComponent to fetch + $queryComponent = $parserResult->getQueryComponent($this->_componentAlias); + + // Generating the SQL piece + $str = $parserResult->getTableAliasFromComponentAlias($this->_componentAlias) . '.' + . $queryComponent['metadata']->getColumnName($this->_fieldName); + + return $conn->quoteIdentifier($str); + } + + /** + * Visitor support + * + * @param object $visitor + */ + public function accept($visitor) + { + $visitor->visitPathExpression($this); + } + + /* Getters */ + + public function getIdentifiers() + { + return $this->_identifiers; + } + + public function getFieldName() + { + return $this->_fieldName; + } + + public function getComponentAlias() + { + return $this->_componentAlias; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/PathExpressionEndingWithAsterisk.php b/lib/Doctrine/ORM/Query/Production/PathExpressionEndingWithAsterisk.php new file mode 100644 index 000000000..d58369090 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/PathExpressionEndingWithAsterisk.php @@ -0,0 +1,169 @@ +. + */ + +/** + * PathExpressionEndingWithAsterisk = {identifier "."} "*" + * + * @package Doctrine + * @subpackage Query + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_PathExpressionEndingWithAsterisk extends Doctrine_Query_Production +{ + protected $_identifiers = array(); + + protected $_queryComponent; + + + public function syntax($paramHolder) + { + // PathExpressionEndingWithAsterisk = {identifier "."} "*" + while ($this->_isNextToken(Doctrine_Query_Token::T_IDENTIFIER)) { + $this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER); + $this->_identifiers[] = $this->_parser->token['value']; + + $this->_parser->match('.'); + } + + $this->_parser->match('*'); + } + + + public function semantical($paramHolder) + { + $parserResult = $this->_parser->getParserResult(); + + if (($l = count($this->_identifiers)) > 0) { + // We are dealing with component{.component}.* + $path = $this->_identifiers[0]; + $this->_queryComponent = $parserResult->getQueryComponent($path); + + // We should have a semantical error if the queryComponent does not exists yet + if ($this->_queryComponent === null) { + $this->_parser->semanticalError("Undefined component alias for '{$path}'", $this->_parser->token); + } + + // Initializing ClassMetadata + $classMetadata = $this->_queryComponent['metadata']; + + // Looping through relations + for ($i = 1; $i < $l; $i++) { + $relationName = $this->_identifiers[$i]; + $path .= '.' . $relationName; + + if ( ! $classMetadata->hasAssociation($relationName)) { + $className = $classMetadata->getClassName(); + + $this->_parser->semanticalError( + "Relation '{$relationName}' does not exist in component '{$className}' when trying to get the path '{$path}'", + $this->_parser->token + ); + } + + // We inspect for queryComponent of relations, since we are using them + if ( ! $parserResult->hasQueryComponent($path)) { + $this->_parser->semanticalError("Cannot use the path '{$path}' without defining it in FROM.", $this->_parser->token); + } + + // Assigning new queryComponent and classMetadata + $this->_queryComponent = $parserResult->getQueryComponent($path); + + $classMetadata = $this->_queryComponent['metadata']; + } + } else { + // We are dealing with a simple * as our PathExpression. + // We need to check if there's only one query component. + $queryComponents = $parserResult->getQueryComponents(); + + if (count($queryComponents) != 2) { + $this->_parser->semanticalError( + "Cannot use * as selector expression for multiple components." + ); + } + + // We simplify our life adding the component alias to our AST, + // since we have it on hands now. + $k = array_keys($queryComponents); + $componentAlias = $k[1]; + + $this->_queryComponent = $queryComponents[$componentAlias]; + } + } + + + public function buildSql() + { + // Basic handy variables + $parserResult = $this->_parser->getParserResult(); + + // Retrieving connection + $conn = $this->_em->getConnection(); + + // Looking for componentAlias to fetch + $componentAlias = implode('.', $this->_identifiers); + + if (count($this->_identifiers) == 0) { + $queryComponents = $parserResult->getQueryComponents(); + + // Retrieve ClassMetadata + $k = array_keys($queryComponents); + $componentAlias = $k[1]; + } + + // Generating the SQL piece + $fields = $this->_queryComponent['metadata']->getFieldMappings(); + $tableAlias = $parserResult->getTableAliasFromComponentAlias($componentAlias); + $str = ''; + + foreach ($fields as $fieldName => $fieldMap) { + $str .= ($str != '') ? ', ' : ''; + + // DB Field name + $column = $tableAlias . '.' . $this->_queryComponent['metadata']->getColumnName($fieldName); + $column = $conn->quoteIdentifier($column); + + // DB Field alias + $columnAlias = $tableAlias . '__' . $this->_queryComponent['metadata']->getColumnName($fieldName); + $columnAlias = $conn->quoteIdentifier($columnAlias); + + $str .= $column . ' AS ' . $columnAlias; + } + + return $str; + } + + + /* Getters */ + public function getIdentifiers() + { + return $this->_identifiers; + } + + + public function getQueryComponent() + { + return $this->_queryComponent; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/Primary.php b/lib/Doctrine/ORM/Query/Production/Primary.php new file mode 100644 index 000000000..1049bc157 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/Primary.php @@ -0,0 +1,110 @@ +. + */ + +/** + * Primary = PathExpression | Atom | "(" Expression ")" | Function | AggregateExpression + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_Primary extends Doctrine_Query_Production +{ + protected $_expression; + + + public function syntax($paramHolder) + { + // Primary = PathExpression | Atom | "(" Expression ")" | Function | AggregateExpression + switch ($this->_parser->lookahead['type']) { + case Doctrine_Query_Token::T_IDENTIFIER: + if ($this->_isFunction()) { + return $this->AST('Function', $paramHolder); + } else { + return $this->AST('PathExpression', $paramHolder); + } + break; + + case Doctrine_Query_Token::T_STRING: + case Doctrine_Query_Token::T_INTEGER: + case Doctrine_Query_Token::T_FLOAT: + case Doctrine_Query_Token::T_INPUT_PARAMETER: + return $this->AST('Atom', $paramHolder); + break; + + case Doctrine_Query_Token::T_AVG: + case Doctrine_Query_Token::T_COUNT: + case Doctrine_Query_Token::T_MAX: + case Doctrine_Query_Token::T_MIN: + case Doctrine_Query_Token::T_SUM: + return $this->AST('AggregateExpression', $paramHolder); + break; + + case Doctrine_Query_Token::T_NONE: + if ($this->_isNextToken('(')) { + $this->_parser->match('('); + $this->_expression = $this->AST('Expression', $paramHolder); + $this->_parser->match(')'); + } + break; + + default: + $this->_parser->syntaxError('Could not process primary type'); + break; + } + } + + + public function semantical($paramHolder) + { + $this->_expression->semantical($paramHolder); + } + + + public function buildSql() + { + return '(' . $this->_expression->buildSql() . ')'; + } + + /** + * Visitor support + * + * @param object $visitor + */ + public function accept($visitor) + { + $this->_expression->accept($visitor); + $visitor->visitPrimary($this); + } + + /* Getters */ + + public function getExpression() + { + return $this->_expression; + } + +} diff --git a/lib/Doctrine/ORM/Query/Production/QuantifiedExpression.php b/lib/Doctrine/ORM/Query/Production/QuantifiedExpression.php new file mode 100644 index 000000000..6d55b3f63 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/QuantifiedExpression.php @@ -0,0 +1,85 @@ +. + */ + +/** + * QuantifiedExpression = ("ALL" | "ANY" | "SOME") "(" Subselect ")" + * + * @package Doctrine + * @subpackage Query + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_QuantifiedExpression extends Doctrine_Query_Production +{ + protected $_type; + + protected $_subselect; + + + public function syntax($paramHolder) + { + switch ($this->_parser->lookahead['type']) { + case Doctrine_Query_Token::T_ALL: + $this->_parser->match(Doctrine_Query_Token::T_ALL); + break; + + case Doctrine_Query_Token::T_ANY: + $this->_parser->match(Doctrine_Query_Token::T_ANY); + break; + + case Doctrine_Query_Token::T_SOME: + $this->_parser->match(Doctrine_Query_Token::T_SOME); + break; + + default: + $this->_parser->logError('ALL, ANY or SOME'); + break; + } + + $this->_type = strtoupper($this->_parser->lookahead['value']); + + $this->_parser->match('('); + $this->_subselect = $this->AST('Subselect', $paramHolder); + $this->_parser->match(')'); + } + + + public function buildSql() + { + return $this->_type . ' (' . $this->_subselect->buildSql() . ')'; + } + + + /* Getters */ + public function getType() + { + return $this->_type; + } + + + public function getSubselect() + { + return $this->_subselect; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/QueryLanguage.php b/lib/Doctrine/ORM/Query/Production/QueryLanguage.php new file mode 100644 index 000000000..80413792c --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/QueryLanguage.php @@ -0,0 +1,57 @@ +. + */ + +/** + * QueryLanguage = SelectStatement | UpdateStatement | DeleteStatement + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_QueryLanguage extends Doctrine_Query_Production +{ + public function syntax($paramHolder) + { + // QueryLanguage = SelectStatement | UpdateStatement | DeleteStatement + switch ($this->_parser->lookahead['type']) { + case Doctrine_Query_Token::T_SELECT: + return $this->AST('SelectStatement', $paramHolder); + break; + + case Doctrine_Query_Token::T_UPDATE: + return $this->AST('UpdateStatement', $paramHolder); + break; + + case Doctrine_Query_Token::T_DELETE: + return $this->AST('DeleteStatement', $paramHolder); + break; + + default: + $this->_parser->syntaxError('SELECT, UPDATE or DELETE'); + break; + } + } +} diff --git a/lib/Doctrine/ORM/Query/Production/RangeVariableDeclaration.php b/lib/Doctrine/ORM/Query/Production/RangeVariableDeclaration.php new file mode 100644 index 000000000..f0ecf2799 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/RangeVariableDeclaration.php @@ -0,0 +1,260 @@ +. + */ + +/** + * RangeVariableDeclaration = identifier {"." identifier} [["AS"] IdentificationVariable] + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_RangeVariableDeclaration extends Doctrine_Query_Production +{ + protected $_identifiers = array(); + + protected $_queryComponent; + + protected $_identificationVariable; + + + public function syntax($paramHolder) + { + // RangeVariableDeclaration = identifier {"." identifier} [["AS"] IdentificationVariable] + $this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER); + $this->_identifiers[] = $this->_parser->token['value']; + + while ($this->_isNextToken('.')) { + $this->_parser->match('.'); + $this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER); + + $this->_identifiers[] = $this->_parser->token['value']; + } + + if ($this->_isNextToken(Doctrine_Query_Token::T_AS)) { + $this->_parser->match(Doctrine_Query_Token::T_AS); + } + + if ($this->_isNextToken(Doctrine_Query_Token::T_IDENTIFIER)) { + $paramHolder->set('componentName', implode('.', $this->_identifiers)); + + // Will return an identifier, with the semantical check already applied + $this->_identificationVariable = $this->AST('IdentificationVariable', $paramHolder); + + $paramHolder->remove('componentName'); + } + } + + + public function semantical($paramHolder) + { + $parserResult = $this->_parser->getParserResult(); + $componentName = implode('.', $this->_identifiers); + + if ($parserResult->hasQueryComponent($componentName)) { + //echo "Entered in if of hasQueryComponent(".$componentName."): true\n"; + + // As long as name != alias, try to bring the queryComponent from name (already processed) + $queryComponent = $parserResult->getQueryComponent($componentName); + + // Check if we defined _identificationVariable. We throw semantical error if not + if ($this->_identificationVariable === null) { + $componentName = $queryComponent['metadata']->getClassName(); + + $this->_parser->semanticalError( + "Cannot re-declare component '{$componentName}'. Please assign an alias to it." + ); + + return; + } + } else { + //echo "Entered in if hasQueryComponent(".$componentName."), alias ".var_export($this->_identificationVariable, true).": false\n"; + + // No queryComponent was found. We will have to build it for the first time + if (count($this->_identifiers) > 1) { + // We are in a multiple identifier declaration; we are dealing with relations here + $this->_semanticalWithMultipleIdentifier(); + } else { + // We are in a single identifier declaration; our identifier is the class name + $this->_semanticalWithSingleIdentifier(); + } + } + } + + + public function buildSql() + { + // We need to bring the queryComponent and get things from there. + $parserResult = $this->_parser->getParserResult(); + + // Retrieving connection + $conn = $this->_em->getConnection(); + + return $conn->quoteIdentifier($this->_queryComponent['metadata']->getTableName()) . ' ' + . $conn->quoteIdentifier($parserResult->getTableAliasFromComponentAlias($this->_identificationVariable)); + } + + + private function _semanticalWithSingleIdentifier() + { + $parserResult = $this->_parser->getParserResult(); + + // Get the connection for the component + $conn = $this->_em->getConnection(); + $componentName = $this->_identifiers[0]; + + // Retrieving ClassMetadata and Mapper + try { + $classMetadata = $this->_em->getClassMetadata($componentName); + + // Building queryComponent + $this->_queryComponent = array( + 'metadata' => $classMetadata, + 'parent' => null, + 'relation' => null, + 'map' => null, + 'scalar' => null, + ); + } catch (Doctrine_Exception $e) { + //echo "Tried to load class metadata from '".$componentName."': " . $e->getMessage() . "\n"; + $this->_parser->semanticalError($e->getMessage()); + + return; + } + + if ($this->_identificationVariable === null) { + $this->_identificationVariable = $componentName; + } + + //echo "Identification Variable: " .$this->_identificationVariable . "\n"; + + $tableAlias = $parserResult->generateTableAlias($classMetadata->getClassName()); + $parserResult->setQueryComponent($this->_identificationVariable, $this->_queryComponent); + $parserResult->setTableAlias($tableAlias, $this->_identificationVariable); + } + + + private function _semanticalWithMultipleIdentifier() + { + $parserResult = $this->_parser->getParserResult(); + + // Get the connection for the component + $conn = $this->_em->getConnection(); + + // Retrieve the base component + try { + $this->_queryComponent = $parserResult->getQueryComponent($this->_identifiers[0]); + $classMetadata = $this->_queryComponent['metadata']; + $className = $classMetadata->getClassName(); + $parent = $path = $this->_identifiers[0]; + } catch (Doctrine_Exception $e) { + $this->_parser->semanticalError($e->getMessage()); + + return; + } + + // We loop into others identifier to build query components + for ($i = 1, $l = count($this->_identifiers); $i < $l; $i++) { + $relationName = $this->_identifiers[$i]; + $path .= '.' . $relationName; + + if ($parserResult->hasQueryComponent($path)) { + // We already have the query component on hands, get it + $this->_queryComponent = $parserResult->getQueryComponent($path); + $classMetadata = $this->_queryComponent['metadata']; + + // If we are in our last check and identification variable is null, we throw semantical error + if ($i == $l - 1 && $this->_identificationVariable === null) { + $componentName = $classMetadata->getClassName(); + + $this->_parser->semanticalError( + "Cannot re-declare component '{$componentName}' in path '{$path}'. " . + "Please assign an alias to it." + ); + + return; + } + } else { + // We don't have the query component yet + if ( ! $classMetadata->hasAssociation($relationName)) { + $className = $classMetadata->getClassName(); + + $this->_parser->semanticalError("Relation '{$relationName}' does not exist in component '{$className}'"); + + return; + } + + // Retrieving ClassMetadata + try { + $relation = $classMetadata->getAssociationMapping($relationName); + $targetClassMetadata = $this->_em->getClassMetadata($relation->getTargetEntityName()); + + $this->_queryComponent = array( + 'metadata' => $targetClassMetadata, + 'parent' => $parent, + 'relation' => $relation, + 'map' => null, + 'scalar' => null, + ); + + $parent = $path; + } catch (Doctrine_Exception $e) { + //echo "Tried to load class metadata from '".$relationName."'\n"; + $this->_parser->semanticalError($e->getMessage()); + + return; + } + } + } + + if ($this->_identificationVariable === null) { + $this->_identificationVariable = $path; + } + + $tableAlias = $parserResult->generateTableAlias($targetClassMetadata->getClassName()); + + $parserResult->setQueryComponent($this->_identificationVariable, $this->_queryComponent); + $parserResult->setTableAlias($tableAlias, $this->_identificationVariable); + } + + + /* Getters */ + public function getIdentifiers() + { + return $this->_identifiers; + } + + + public function getQueryComponent() + { + return $this->_queryComponent; + } + + + public function getIdentificationVariable() + { + return $this->_identificationVariable; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/SelectClause.php b/lib/Doctrine/ORM/Query/Production/SelectClause.php new file mode 100644 index 000000000..ec7588f07 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/SelectClause.php @@ -0,0 +1,101 @@ +. + */ + +/** + * SelectClause = "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_SelectClause extends Doctrine_Query_Production +{ + protected $_isDistinct; + + protected $_selectExpressions = array(); + + + public function syntax($paramHolder) + { + // SelectClause = "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} + $this->_isDistinct = false; + + $this->_parser->match(Doctrine_Query_Token::T_SELECT); + + if ($this->_isNextToken(Doctrine_Query_Token::T_DISTINCT)) { + $this->_parser->match(Doctrine_Query_Token::T_DISTINCT); + $this->_isDistinct = true; + } + + $this->_selectExpressions[] = $this->AST('SelectExpression', $paramHolder); + + while ($this->_isNextToken(',')) { + $this->_parser->match(','); + $this->_selectExpressions[] = $this->AST('SelectExpression', $paramHolder); + } + } + + + public function semantical($paramHolder) + { + // We need to validate each SelectExpression + for ($i = 0, $l = count($this->_selectExpressions); $i < $l; $i++) { + $this->_selectExpressions[$i]->semantical($paramHolder); + } + } + + + public function buildSql() + { + return 'SELECT ' . (($this->_isDistinct) ? 'DISTINCT ' : '') + . implode(', ', $this->_mapSelectExpressions()); + } + + + protected function _mapSelectExpressions() + { + return array_map(array(&$this, '_mapSelectExpression'), $this->_selectExpressions); + } + + + protected function _mapSelectExpression($value) + { + return $value->buildSql(); + } + + + /* Getters */ + public function isDistinct() + { + return $this->_isDistinct; + } + + + public function getSelectExpressions() + { + return $this->_selectExpressions; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/SelectExpression.php b/lib/Doctrine/ORM/Query/Production/SelectExpression.php new file mode 100644 index 000000000..ff1882921 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/SelectExpression.php @@ -0,0 +1,212 @@ +. + */ + +/** + * SelectExpression = (PathExpressionEndingWithAsterisk | Expression | "(" Subselect ")") + * [["AS"] IdentificationVariable] + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 1.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_SelectExpression extends Doctrine_Query_Production +{ + protected $_leftExpression; + + protected $_isSubselect; + + protected $_fieldIdentificationVariable; + + private $__columnAliasInSql; + + + public function syntax($paramHolder) + { + // SelectExpression = (PathExpressionEndingWithAsterisk | Expression | "(" Subselect ")") + // [["AS"] IdentificationVariable] + $this->_isSubselect = false; + + if ($this->_isPathExpressionEndingWithAsterisk()) { + $this->_leftExpression = $this->AST('PathExpressionEndingWithAsterisk', $paramHolder); + + $fieldName = implode('.', $this->_leftExpression->getIdentifiers()) . '.*'; + } else if (($this->_isSubselect = $this->_isSubselect()) === true) { + $this->_parser->match('('); + $this->_leftExpression = $this->AST('Subselect', $paramHolder); + $this->_parser->match(')'); + + // [TODO] Any way to make it more fancy for user error? + $fieldName = ''; + } else { + $this->_leftExpression = $this->AST('Expression', $paramHolder); + + // [TODO] Any way to make it more fancy for user error? + $fieldName = ''; + } + + if ($this->_isNextToken(Doctrine_Query_Token::T_AS)) { + $this->_parser->match(Doctrine_Query_Token::T_AS); + } + + if ($this->_isNextToken(Doctrine_Query_Token::T_IDENTIFIER)) { + $paramHolder->set('fieldName', $fieldName); + + // Will return an identifier, with the semantical check already applied + $this->_fieldIdentificationVariable = $this->AST('FieldIdentificationVariable', $paramHolder); + + $paramHolder->remove('fieldName'); + } + } + + + public function semantical($paramHolder) + { + $parserResult = $this->_parser->getParserResult(); + + // We cannot have aliases for foo.* + if ($this->_leftExpression instanceof Doctrine_Query_Production_PathExpressionEndingWithAsterisk + && $this->_fieldIdentificationVariable !== null) { + $this->_parser->semanticalError( + "Cannot assign an identification variable to a path expression ending with asterisk (ie. foo.bar.* AS foobaz)." + ); + } + + // Also, we cannot have aliases for path expressions: foo.bar + if ($this->_leftExpression instanceof Doctrine_Query_Production_PathExpressionEndingWithAsterisk + && $this->_fieldIdentificationVariable !== null) { + $this->_parser->semanticalError( + "Cannot assign an identification variable to a path expression (ie. foo.bar AS foobaz)." + ); + } + + // Make semantical checks + $this->_leftExpression->semantical($paramHolder); + + if($this->_fieldIdentificationVariable !== null) { + $this->_fieldIdentificationVariable->semantical($paramHolder); + } + } + + + public function buildSql() + { + return $this->_leftExpression->buildSql() . $this->_buildColumnAliasInSql(); + } + + + protected function _isPathExpressionEndingWithAsterisk() + { + $token = $this->_parser->lookahead; + $this->_parser->getScanner()->resetPeek(); + + while (($token['type'] === Doctrine_Query_Token::T_IDENTIFIER) || ($token['value'] === '.')) { + $token = $this->_parser->getScanner()->peek(); + } + + return $token['value'] === '*'; + } + + + protected function _buildColumnAliasInSql() + { + // Retrieving parser result + $parserResult = $this->_parser->getParserResult(); + + // Retrieving connection + $conn = $this->_em->getConnection(); + + switch (get_class($this->_leftExpression)) { + case 'Doctrine_Query_Production_PathExpressionEndingWithAsterisk': + return ''; + break; + + case 'Doctrine_Query_Production_PathExpression': + // We bring the queryComponent from the class instance + $componentAlias = $this->_leftExpression->getComponentAlias(); + $queryComponent = $parserResult->getQueryComponent($componentAlias); + $fieldName = $this->_leftExpression->getFieldName(); + + // Build the column alias now + $columnAlias = $parserResult->getTableAliasFromComponentAlias($componentAlias) + . Doctrine_Query_Production::SQLALIAS_SEPARATOR + . $queryComponent['metadata']->getColumnName($fieldName); + break; + + default: + // We bring the default queryComponent + $componentAlias = Doctrine_Query_Production::DEFAULT_QUERYCOMPONENT; + $queryComponent = $parserResult->getQueryComponent($componentAlias); + + // If we have FieldIdentificationVariable, we have to use the scalar map of it + if ($this->_fieldIdentificationVariable !== null) { + $columnAlias = $this->_fieldIdentificationVariable->getColumnAlias(); + } else { + // We have to include the map now, since we don't have the scalar mapped + $queryFields = $parserResult->getQueryFields(); + $itemIndex = 'item' . count(array_filter($queryFields, array($this, "_nonIdentifiedVariable"))); + $idx = count($queryFields); + + $queryComponent['scalar'][$idx] = $itemIndex; + $parserResult->setQueryComponent($componentAlias, $queryComponent); + + // And also in field aliases + $parserResult->setQueryField($itemIndex, $idx); + + // Build the column alias + $columnAlias = $parserResult->getTableAliasFromComponentAlias($componentAlias) + . Doctrine_Query_Production::SQLALIAS_SEPARATOR . $idx; + } + break; + } + + return ' AS ' . $conn->quoteIdentifier($columnAlias); + } + + + protected function _nonIdentifiedVariable($value) + { + return ! is_string($value); + } + + + /* Getters */ + public function getLeftExpression() + { + return $this->_leftExpression; + } + + + public function isSubselect() + { + return $this->_isSubselect; + } + + + public function getFieldIdentificationVariable() + { + return $this->_fieldIdentificationVariable; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/SelectStatement.php b/lib/Doctrine/ORM/Query/Production/SelectStatement.php new file mode 100644 index 000000000..661422f2c --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/SelectStatement.php @@ -0,0 +1,133 @@ +. + */ + +/** + * SelectStatement = SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_SelectStatement extends Doctrine_Query_Production +{ + protected $_selectClause; + + protected $_fromClause; + + protected $_whereClause; + + protected $_groupByClause; + + protected $_havingClause; + + protected $_orderByClause; + + + public function syntax($paramHolder) + { + // SelectStatement = SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + + // Disable the semantical check for SelectClause now. This is needed + // since we dont know the query components yet (will be known only + // when the FROM clause be processed). + $paramHolder->set('semanticalCheck', false); + $this->_selectClause = $this->AST('SelectClause', $paramHolder); + $paramHolder->remove('semanticalCheck'); + + $this->_fromClause = $this->AST('FromClause', $paramHolder); + + if ($this->_isNextToken(Doctrine_Query_Token::T_WHERE)) { + $this->_whereClause = $this->AST('WhereClause', $paramHolder); + } + + if ($this->_isNextToken(Doctrine_Query_Token::T_GROUP)) { + $this->_groupByClause = $this->AST('GroupByClause', $paramHolder); + } + + if ($this->_isNextToken(Doctrine_Query_Token::T_HAVING)) { + $this->_havingClause = $this->AST('HavingClause', $paramHolder); + } + + if ($this->_isNextToken(Doctrine_Query_Token::T_ORDER)) { + $this->_orderByClause = $this->AST('OrderByClause', $paramHolder); + } + } + + + public function semantical($paramHolder) + { + // We need to invoke the semantical check of SelectClause here, since + // it was not yet checked. + $this->_selectClause->semantical($paramHolder); + } + + + public function buildSql() + { + return $this->_selectClause->buildSql() . ' ' . $this->_fromClause->buildSql() + . (($this->_whereClause !== null) ? ' ' . $this->_whereClause->buildSql() : ' WHERE 1 = 1') + . (($this->_groupByClause !== null) ? ' ' . $this->_groupByClause->buildSql() : '') + . (($this->_havingClause !== null) ? ' ' . $this->_havingClause->buildSql() : '') + . (($this->_orderByClause !== null) ? ' ' . $this->_orderByClause->buildSql() : ''); + } + + + /* Getters */ + public function getSelectClause() + { + return $this->_selectClause; + } + + + public function getFromClause() + { + return $this->_fromClause; + } + + + public function getWhereClause() + { + return $this->_whereClause; + } + + + public function getGroupByClause() + { + return $this->_groupByClause; + } + + + public function getHavingClause() + { + return $this->_havingClause; + } + + + public function getOrderByClause() + { + return $this->_orderByClause; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/SimpleConditionalExpression.php b/lib/Doctrine/ORM/Query/Production/SimpleConditionalExpression.php new file mode 100644 index 000000000..f93a3380e --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/SimpleConditionalExpression.php @@ -0,0 +1,122 @@ +. + */ + +/** + * SimpleConditionalExpression = + * ExistsExpression | Expression (ComparisonExpression | BetweenExpression | + * LikeExpression | InExpression | NullComparisonExpression | QuantifiedExpression) + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_SimpleConditionalExpression extends Doctrine_Query_Production +{ + protected $_leftExpression; + + protected $_rightExpression; + + + public function syntax($paramHolder) + { + // SimpleConditionalExpression = + // ExistsExpression | Expression (ComparisonExpression | BetweenExpression | + // LikeExpression | InExpression | NullComparisonExpression | QuantifiedExpression) + if ($this->_getExpressionType() === Doctrine_Query_Token::T_EXISTS) { + return $this->AST('ExistsExpression', $paramHolder); + } + + $this->_leftExpression = $this->AST('Expression', $paramHolder); + + switch ($this->_getExpressionType()) { + case Doctrine_Query_Token::T_BETWEEN: + $this->_rightExpression = $this->AST('BetweenExpression', $paramHolder); + break; + + case Doctrine_Query_Token::T_LIKE: + $this->_rightExpression = $this->AST('LikeExpression', $paramHolder); + break; + + case Doctrine_Query_Token::T_IN: + $this->_rightExpression = $this->AST('InExpression', $paramHolder); + break; + + case Doctrine_Query_Token::T_IS: + $this->_rightExpression = $this->AST('NullComparisonExpression', $paramHolder); + break; + + case Doctrine_Query_Token::T_ALL: + case Doctrine_Query_Token::T_ANY: + case Doctrine_Query_Token::T_SOME: + $this->_rightExpression = $this->AST('QuantifiedExpression', $paramHolder); + break; + + case Doctrine_Query_Token::T_NONE: + // [TODO] Check out ticket #935 to understand what will be done with enumParams + $this->_rightExpression = $this->AST('ComparisonExpression', $paramHolder); + break; + + default: + $message = "BETWEEN, LIKE, IN, IS, quantified (ALL, ANY or SOME) " + . "or comparison (=, <, <=, <>, >, >=, !=)"; + $this->_parser->syntaxError($message); + break; + } + } + + + public function buildSql() + { + return $this->_leftExpression->buildSql() . ' ' . $this->_rightExpression->buildSql(); + } + + + protected function _getExpressionType() { + if ($this->_isNextToken(Doctrine_Query_Token::T_NOT)) { + $scanner = $this->_parser->getScanner(); + + $token = $scanner->peek(); + $scanner->resetPeek(); + } else { + $token = $this->_parser->lookahead; + } + + return $token['type']; + } + + + /* Getters */ + public function getLeftExpression() + { + return $this->_leftExpression; + } + + + public function getRightExpression() + { + return $this->_rightExpression; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/SimpleSelectClause.php b/lib/Doctrine/ORM/Query/Production/SimpleSelectClause.php new file mode 100644 index 000000000..e014fe439 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/SimpleSelectClause.php @@ -0,0 +1,91 @@ +. + */ + +/** + * SimpleSelectClause = "SELECT" ["DISTINCT"] SelectExpression + * + * @package Doctrine + * @subpackage Query + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_SimpleSelectClause extends Doctrine_Query_Production +{ + protected $_isDistinct; + + protected $_selectExpression; + + + public function syntax($paramHolder) + { + // SimpleSelectClause = "SELECT" ["DISTINCT"] SelectExpression + $this->_isDistinct = false; + + $this->_parser->match(Doctrine_Query_Token::T_SELECT); + + if ($this->_isNextToken(Doctrine_Query_Token::T_DISTINCT)) { + $this->_parser->match(Doctrine_Query_Token::T_DISTINCT); + $this->_isDistinct = true; + } + + $this->_selectExpression = $this->AST('SelectExpression', $paramHolder); + } + + + public function semantical($paramHolder) + { + // We need to validate the SelectExpression + $this->_selectExpression->semantical($paramHolder); + } + + + public function buildSql() + { + return 'SELECT ' . (($this->_isDistinct) ? 'DISTINCT ' : '') + . $this->_selectExpression->buildSql(); + } + + /** + * Visitor support + * + * @param object $visitor + */ + public function acccept($visitor) + { + $this->_selectExpression->accept($visitor); + $visitor->visitSimpleSelectClause($this); + } + + /* Getters */ + + public function isDistinct() + { + return $this->_isDistinct; + } + + public function getSelectExpression() + { + return $this->_selectExpression; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/Subselect.php b/lib/Doctrine/ORM/Query/Production/Subselect.php new file mode 100644 index 000000000..15a70843b --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/Subselect.php @@ -0,0 +1,161 @@ +. + */ + +/** + * Subselect = SimpleSelectClause FromClause [WhereClause] [GroupByClause] + * [HavingClause] [OrderByClause] [LimitClause] [OffsetClause] + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_Subselect extends Doctrine_Query_Production +{ + protected $_simpleSelectClause; + + protected $_fromClause; + + protected $_whereClause; + + protected $_groupByClause; + + protected $_havingClause; + + protected $_orderByClause; + + protected $_limitClause; + + + public function syntax($paramHolder) + { + // Subselect = SimpleSelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] [LimitClause] + + // Disable the semantical check for SelectClause now. This is needed + // since we dont know the query components yet (will be known only + // when the FROM clause be processed). + $paramHolder->set('semanticalCheck', false); + $this->_simpleSelectClause = $this->AST('SimpleSelectClause', $paramHolder); + $paramHolder->remove('semanticalCheck'); + + $this->_fromClause = $this->AST('FromClause', $paramHolder); + + if ($this->_isNextToken(Doctrine_Query_Token::T_WHERE)) { + $this->_whereClause = $this->AST('WhereClause', $paramHolder); + } + + if ($this->_isNextToken(Doctrine_Query_Token::T_GROUP)) { + $this->_groupByClause = $this->AST('GroupByClause', $paramHolder); + } + + if ($this->_isNextToken(Doctrine_Query_Token::T_HAVING)) { + $this->_havingClause = $this->AST('HavingClause', $paramHolder); + } + + if ($this->_isNextToken(Doctrine_Query_Token::T_ORDER)) { + $this->_orderByClause = $this->AST('OrderByClause', $paramHolder); + } + + if ($this->_isNextToken(Doctrine_Query_Token::T_LIMIT)) { + $this->_limitClause = $this->AST('LimitClause', $paramHolder); + } + + } + + + public function semantical($paramHolder) + { + // We need to invoke the semantical check of SelectClause here, since + // it was not yet checked. + $this->_simpleSelectClause->semantical($paramHolder); + } + + + public function buildSql() + { + return $this->_simpleSelectClause->buildSql() . ' ' . $this->_fromClause->buildSql() + . (($this->_whereClause !== null) ? ' ' . $this->_whereClause->buildSql() : '') + . (($this->_groupByClause !== null) ? ' ' . $this->_groupByClause->buildSql() : '') + . (($this->_havingClause !== null) ? ' ' . $this->_havingClause->buildSql() : '') + . (($this->_orderByClause !== null) ? ' ' . $this->_orderByClause->buildSql() : ''); + } + + /** + * Visitor support + * + * @param object $visitor + */ + public function accept($visitor) + { + $this->_simpleSelectClause->accept($visitor); + $this->_fromClause->accept($visitor); + if ($this->_whereClause) { + $this->_whereClause->accept($visitor); + } + if ($this->_groupByClause) { + $this->_groupByClause->accept($visitor); + } + if ($this->_havingClause) { + $this->_havingClause->accept($visitor); + } + if ($this->_orderByClause) { + $this->_orderByClause->accept($visitor); + } + $visitor->visitSubselect($this); + } + + /* Getters */ + + public function getSimpleSelectClause() + { + return $this->_simpleSelectClause; + } + + public function getFromClause() + { + return $this->_fromClause; + } + + public function getWhereClause() + { + return $this->_whereClause; + } + + public function getGroupByClause() + { + return $this->_groupByClause; + } + + public function getHavingClause() + { + return $this->_havingClause; + } + + public function getOrderByClause() + { + return $this->_orderByClause; + } + +} diff --git a/lib/Doctrine/ORM/Query/Production/Term.php b/lib/Doctrine/ORM/Query/Production/Term.php new file mode 100644 index 000000000..45e1b1d27 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/Term.php @@ -0,0 +1,107 @@ +. + */ + +/** + * Term = Factor {("*" | "/") Factor} + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_Term extends Doctrine_Query_Production +{ + protected $_factors = array(); + + + public function syntax($paramHolder) + { + // Term = Factor {("*" | "/") Factor} + $this->_factors[] = $this->AST('Factor', $paramHolder); + + while ($this->_isNextToken('*') || $this->_isNextToken('/')) { + if ($this->_isNextToken('*')) { + $this->_parser->match('*'); + $this->_factors[] = '*'; + } else { + $this->_parser->match('/'); + $this->_factors[] = '/'; + } + + $this->_factors[] = $this->AST('Factor', $paramHolder); + } + + // Optimize depth instances in AST + if (count($this->_factors) == 1) { + return $this->_factors[0]; + } + } + + + public function semantical($paramHolder) + { + for ($i = 0, $l = count($this->_factors); $i < $l; $i++) { + if ($this->_factors[$i] != '*' && $this->_factors[$i] != '/') { + $this->_factors[$i]->semantical($paramHolder); + } + } + } + + + public function buildSql() + { + return implode(' ', $this->_mapFactors()); + } + + + protected function _mapFactors() + { + return array_map(array(&$this, '_mapFactor'), $this->_factors); + } + + + protected function _mapFactor($value) + { + return (is_string($value) ? $value : $value->buildSql()); + } + + /** + * Visitor support. + */ + public function accept($visitor) + { + foreach ($this->_factors as $factor) { + $factor->accept($visitor); + } + $visitor->visitTerm($this); + } + + /* Getters */ + + public function getFactors() + { + return $this->_factors; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/UpdateClause.php b/lib/Doctrine/ORM/Query/Production/UpdateClause.php new file mode 100644 index 000000000..5ff5d2ab1 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/UpdateClause.php @@ -0,0 +1,101 @@ +. + */ + +/** + * UpdateClause = "UPDATE" VariableDeclaration "SET" UpdateItem {"," UpdateItem} + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_UpdateClause extends Doctrine_Query_Production +{ + protected $_variableDeclaration; + + protected $_updateItems = array(); + + + public function syntax($paramHolder) + { + // UpdateClause = "UPDATE" VariableDeclaration "SET" UpdateItem {"," UpdateItem} + $this->_parser->match(Doctrine_Query_Token::T_UPDATE); + + $this->_variableDeclaration = $this->AST('VariableDeclaration', $paramHolder); + + $this->_parser->match(Doctrine_Query_Token::T_SET); + + $this->_updateItems[] = $this->AST('UpdateItem', $paramHolder); + + while ($this->_isNextToken(',')) { + $this->_parser->match(','); + + $this->_updateItems[] = $this->AST('UpdateItem', $paramHolder); + } + } + + + public function buildSql() + { + return 'UPDATE ' . $this->_variableDeclaration->buildSql() + . ' SET ' . implode(', ', $this->_mapUpdateItems()); + } + + + protected function _mapUpdateItems() + { + return array_map(array(&$this, '_mapUpdateItem'), $this->_updateItems); + } + + + protected function _mapUpdateItem($value) + { + return $value->buildSql(); + } + + /** + * Visitor support. + */ + public function accept($visitor) + { + $this->_variableDeclaration->accept($visitor); + foreach ($this->_updateItems as $item) { + $item->accept($visitor); + } + $visitor->visitUpdateClause($this); + } + + /* Getters */ + + public function getVariableDeclaration() + { + return $this->_variableDeclaration; + } + + public function getUpdateItems() + { + return $this->_updateItems; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/UpdateItem.php b/lib/Doctrine/ORM/Query/Production/UpdateItem.php new file mode 100644 index 000000000..1c9546643 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/UpdateItem.php @@ -0,0 +1,86 @@ +. + */ + +/** + * UpdateItem = PathExpression "=" (Expression | "NULL") + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_UpdateItem extends Doctrine_Query_Production +{ + protected $_pathExpression; + + protected $_expression; + + + public function syntax($paramHolder) + { + // UpdateItem = PathExpression "=" (Expression | "NULL") + $this->_pathExpression = $this->AST('PathExpression', $paramHolder); + + $this->_parser->match('='); + + if ($this->_isNextToken(Doctrine_Query_Token::T_NULL)) { + $this->_parser->match(Doctrine_Query_Token::T_NULL); + $this->_expression = null; + } else { + $this->_expression = $this->AST('Expression', $paramHolder); + } + } + + + public function buildSql() + { + return $this->_pathExpression->buildSql() . ' = ' + . ($this->_expression === null ? 'NULL' : $this->_expression->buildSql()); + } + + /** + * Visitor support. + */ + public function accept($visitor) + { + $this->_pathExpression->accept($visitor); + if ($this->_expression) { + $this->_expression->accept($visitor); + } + $visitor->visitUpdateItem($this); + } + + /* Getters */ + + public function getPathExpression() + { + return $this->_pathExpression; + } + + public function getExpression() + { + return $this->_expression; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/UpdateStatement.php b/lib/Doctrine/ORM/Query/Production/UpdateStatement.php new file mode 100644 index 000000000..f9c9b930e --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/UpdateStatement.php @@ -0,0 +1,82 @@ +. + */ + +/** + * UpdateStatement = UpdateClause [WhereClause] + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_UpdateStatement extends Doctrine_Query_Production +{ + protected $_updateClause; + + protected $_whereClause; + + + public function syntax($paramHolder) + { + // UpdateStatement = UpdateClause [WhereClause] + $this->_updateClause = $this->AST('UpdateClause', $paramHolder); + + if ($this->_isNextToken(Doctrine_Query_Token::T_WHERE)) { + $this->_whereClause = $this->AST('WhereClause', $paramHolder); + } + } + + + public function buildSql() + { + // The 1=1 is needed to workaround the affected_rows in MySQL. + // Simple "UPDATE table_name SET column_name = value" gives 0 affected rows. + return $this->_updateClause->buildSql() . (($this->_whereClause !== null) + ? ' ' . $this->_whereClause->buildSql() : ' WHERE 1 = 1'); + } + + /** + * Visitor support. + */ + public function accept($visitor) + { + $this->_updateClause->accept($visitor); + if ($this->_whereClause) { + $this->_whereClause->accept($visitor); + } + $visitor->visitUpdateStatment($this); + } + + /* Getters */ + public function getUpdateClause() + { + return $this->_updateClause; + } + + public function getWhereClause() + { + return $this->_whereClause; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/VariableDeclaration.php b/lib/Doctrine/ORM/Query/Production/VariableDeclaration.php new file mode 100644 index 000000000..e8fbbbc16 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/VariableDeclaration.php @@ -0,0 +1,151 @@ +. + */ + +/** + * VariableDeclaration = identifier [["AS"] IdentificationVariable] + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_VariableDeclaration extends Doctrine_Query_Production +{ + protected $_componentName; + + protected $_componentAlias; + + + public function syntax($paramHolder) + { + // VariableDeclaration = identifier [["AS"] IdentificationVariable] + if ($this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER)) { + // identifier + $this->_componentName = $this->_parser->token['value']; + } + + if ($this->_isNextToken(Doctrine_Query_Token::T_AS)) { + $this->_parser->match(Doctrine_Query_Token::T_AS); + } + + if ($this->_isNextToken(Doctrine_Query_Token::T_IDENTIFIER)) { + $paramHolder->set('componentName', $this->_componentName); + + // Will return an identifier, with the semantical check already applied + $this->_componentAlias = $this->AST('IdentificationVariable', $paramHolder); + + $paramHolder->remove('componentName'); + } + } + + + public function semantical($paramHolder) + { + $parserResult = $this->_parser->getParserResult(); + + if ($parserResult->hasQueryComponent($this->_componentName)) { + // As long as name != alias, try to bring the queryComponent from name (already processed) + $queryComponent = $parserResult->getQueryComponent($this->_componentName); + + // Check if we defined _componentAlias. We throw semantical error if not + if ($this->_componentAlias === null) { + $componentName = $queryComponent['metadata']->getClassName(); + + $this->_parser->semanticalError( + "Cannot re-declare component '{$this->_componentName}'. Please assign an alias to it." + ); + + return; + } + } else { + // No queryComponent was found. We will have to build it for the first time + + // Get the connection for the component + $conn = $this->_em->getConnection(); + + // Retrieving ClassMetadata and Mapper + try { + $classMetadata = $this->_em->getMetadata($this->_componentName); + + // Building queryComponent + $queryComponent = array( + 'metadata' => $classMetadata, + 'parent' => null, + 'relation' => null, + 'map' => null, + 'scalar' => null, + ); + } catch (Doctrine_Exception $e) { + $this->_parser->semanticalError($e->getMessage()); + + return; + } + } + + // Define ParserResult assertions for later usage + $tableAlias = $this->_parser->getParserResult()->generateTableAlias($this->_componentName); + + if ($this->_componentAlias === null) { + $this->_componentAlias = $this->_componentName; + } + + $parserResult->setQueryComponent($this->_componentAlias, $queryComponent); + $parserResult->setTableAlias($tableAlias, $this->_componentAlias); + } + + + public function buildSql() + { + // Basic handy variables + $parserResult = $this->_parser->getParserResult(); + $queryComponent = $parserResult->getQueryComponent($this->_componentAlias); + + // Retrieving connection + $conn = $this->_em->getConnection(); + + return $conn->quoteIdentifier($queryComponent['metadata']->getTableName()) . ' ' + . $conn->quoteIdentifier($parserResult->getTableAliasFromComponentAlias($this->_componentAlias)); + } + + /** + * Visitor support. + */ + public function accept($visitor) + { + $visitor->visitVariableDeclaration($this); + } + + /* Getters */ + + public function getComponentName() + { + return $this->_componentName; + } + + public function getComponentAlias() + { + return $this->_componentAlias; + } +} diff --git a/lib/Doctrine/ORM/Query/Production/WhereClause.php b/lib/Doctrine/ORM/Query/Production/WhereClause.php new file mode 100644 index 000000000..8f4edf122 --- /dev/null +++ b/lib/Doctrine/ORM/Query/Production/WhereClause.php @@ -0,0 +1,59 @@ +. + */ + +/** + * WhereClause = "WHERE" ConditionalExpression + * + * @package Doctrine + * @subpackage Query + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_Query_Production_WhereClause extends Doctrine_Query_Production +{ + protected $_conditionalExpression; + + + public function syntax($paramHolder) + { + // WhereClause = "WHERE" ConditionalExpression + $this->_parser->match(Doctrine_Query_Token::T_WHERE); + + $this->_conditionalExpression = $this->AST('ConditionalExpression', $paramHolder); + } + + + public function buildSql() + { + return 'WHERE ' . $this->_conditionalExpression->buildSql(); + } + + + /* Getters */ + public function getConditionalExpression() + { + return $this->_conditionalExpression; + } +} diff --git a/lib/Doctrine/ORM/Query/QueryResult.php b/lib/Doctrine/ORM/Query/QueryResult.php new file mode 100644 index 000000000..eda2ae343 --- /dev/null +++ b/lib/Doctrine/ORM/Query/QueryResult.php @@ -0,0 +1,44 @@ +. + */ + +/** + * Doctrine_ORM_Query_QueryResult + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_QueryResult extends Doctrine_ORM_Query_AbstractResult +{ + /** + * Returns cached resultset. + * + * @return array Resultset. + */ + public function getResultSet() + { + return $this->_data; + } + +} diff --git a/lib/Doctrine/ORM/Query/Scanner.php b/lib/Doctrine/ORM/Query/Scanner.php new file mode 100644 index 000000000..08acf9ccb --- /dev/null +++ b/lib/Doctrine/ORM/Query/Scanner.php @@ -0,0 +1,230 @@ +. + */ + +/** + * Scans a DQL query for tokens. + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_Scanner +{ + /** + * Array of scanned tokens. + * + * @var array + */ + protected $_tokens = array(); + /** + * @todo Doc + */ + protected $_position = 0; + /** + * @todo Doc + */ + protected $_peek = 0; + + /** + * Creates a new query scanner object. + * + * @param string $input a query string + */ + public function __construct($input) + { + $this->_scan($input); + } + + /** + * Checks if an identifier is a keyword and returns its correct type. + * + * @param string $identifier identifier name + * @return int token type + */ + public function _checkLiteral($identifier) + { + $name = 'Doctrine_ORM_Query_Token::T_' . strtoupper($identifier); + + if (defined($name)) { + $type = constant($name); + + if ($type > 100) { + return $type; + } + } + + return Doctrine_ORM_Query_Token::T_IDENTIFIER; + } + + /** + * Scans the input string for tokens. + * + * @param string $input a query string + */ + protected function _scan($input) + { + static $regex; + + if ( ! isset($regex)) { + $patterns = array( + '[a-z_][a-z0-9_]*', + '(?:[0-9]+(?:[,\.][0-9]+)*)(?:e[+-]?[0-9]+)?', + "'(?:[^']|'')*'", + '\?|:[a-z]+' + ); + $regex = '/(' . implode(')|(', $patterns) . ')|\s+|(.)/i'; + } + + $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; + $matches = preg_split($regex, $input, -1, $flags); + + foreach ($matches as $match) { + $value = $match[0]; + $type = $this->_getType($value); + $this->_tokens[] = array( + 'value' => $value, + 'type' => $type, + 'position' => $match[1] + ); + } + } + + /** + * @todo Doc + */ + protected function _getType(&$value) + { + // $value is referenced because it can be changed if it is numeric. + // [TODO] Revisit the _isNumeric and _getNumeric methods to reduce overhead. + $type = Doctrine_ORM_Query_Token::T_NONE; + + $newVal = $this->_getNumeric($value); + if ($newVal !== false){ + $value = $newVal; + if (strpos($value, '.') !== false || stripos($value, 'e') !== false) { + $type = Doctrine_ORM_Query_Token::T_FLOAT; + } else { + $type = Doctrine_ORM_Query_Token::T_INTEGER; + } + } + if ($value[0] === "'" && $value[strlen($value) - 1] === "'") { + $type = Doctrine_ORM_Query_Token::T_STRING; + } else if (ctype_alpha($value[0]) || $value[0] === '_') { + $type = $this->_checkLiteral($value); + } else if ($value[0] === '?' || $value[0] === ':') { + $type = Doctrine_ORM_Query_Token::T_INPUT_PARAMETER; + } + + return $type; + } + + /** + * @todo Doc + */ + protected function _getNumeric($value) + { + if ( ! is_scalar($value)) { + return false; + } + // Checking for valid numeric numbers: 1.234, -1.234e-2 + if (is_numeric($value)) { + return $value; + } + + // World number: 1.000.000,02 or -1,234e-2 + $worldnum = strtr($value, array('.' => '', ',' => '.')); + if (is_numeric($worldnum)) { + return $worldnum; + } + + // American extensive number: 1,000,000.02 + $american_en = strtr($value, array(',' => '')); + if (is_numeric($american_en)) { + return $american_en; + } + + return false; + + } + + /** + * @todo Doc + */ + public function isA($value, $token) + { + $type = $this->_getType($value); + + return $type === $token; + } + + /** + * @todo Doc + */ + public function peek() + { + if (isset($this->_tokens[$this->_position + $this->_peek])) { + return $this->_tokens[$this->_position + $this->_peek++]; + } else { + return null; + } + } + + /** + * @todo Doc + */ + public function resetPeek() + { + $this->_peek = 0; + } + + /** + * Returns the next token in the input string. + * + * A token is an associative array containing three items: + * - 'value' : the string value of the token in the input string + * - 'type' : the type of the token (identifier, numeric, string, input + * parameter, none) + * - 'position' : the position of the token in the input string + * + * @return array|null the next token; null if there is no more tokens left + */ + public function next() + { + $this->_peek = 0; + + if (isset($this->_tokens[$this->_position])) { + return $this->_tokens[$this->_position++]; + } else { + return null; + } + } + + /** + * @todo Doc + */ + public function resetPosition($position = 0) + { + $this->_position = $position; + } +} diff --git a/lib/Doctrine/ORM/Query/SqlBuilder.php b/lib/Doctrine/ORM/Query/SqlBuilder.php new file mode 100644 index 000000000..943a3d0e8 --- /dev/null +++ b/lib/Doctrine/ORM/Query/SqlBuilder.php @@ -0,0 +1,65 @@ +. + */ + +/** + * The SqlBuilder. Creates SQL out of an AST. + * + * INTERNAL: For platform-specific SQL, the platform of the connection should be used. + * ($this->_connection->getDatabasePlatform()) + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_SqlBuilder +{ + protected $_em; + protected $_conn; + + public function __construct(Doctrine_ORM_EntityManager $em) + { + $this->_em = $em; + $this->_conn = $this->_em->getConnection(); + } + + /** + * Retrieves the assocated Doctrine_Connection to this object. + * + * @return Doctrine_Connection + */ + public function getConnection() + { + return $this->_connection; + } + + /** + * @nodoc + */ + public function quoteIdentifier($identifier) + { + return $this->_conn->quoteIdentifier($identifier); + } + +} diff --git a/lib/Doctrine/ORM/Query/SqlBuilder/Mysql.php b/lib/Doctrine/ORM/Query/SqlBuilder/Mysql.php new file mode 100644 index 000000000..b3aae8366 --- /dev/null +++ b/lib/Doctrine/ORM/Query/SqlBuilder/Mysql.php @@ -0,0 +1,36 @@ +. + */ + +/** + * MySql class of Sql Builder object + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 1.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_SqlBuilder_Mysql extends Doctrine_ORM_Query_SqlBuilder +{ + +} diff --git a/lib/Doctrine/ORM/Query/SqlBuilder/Sqlite.php b/lib/Doctrine/ORM/Query/SqlBuilder/Sqlite.php new file mode 100644 index 000000000..060539750 --- /dev/null +++ b/lib/Doctrine/ORM/Query/SqlBuilder/Sqlite.php @@ -0,0 +1,36 @@ +. + */ + +/** + * Sqlite class of Sql Builder object + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 1.0 + * @version $Revision$ + */ +class Doctrine_ORM_Query_SqlBuilder_Sqlite extends Doctrine_ORM_Query_SqlBuilder +{ + +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/SqlExecutor/Abstract.php b/lib/Doctrine/ORM/Query/SqlExecutor/Abstract.php new file mode 100644 index 000000000..ff97359e6 --- /dev/null +++ b/lib/Doctrine/ORM/Query/SqlExecutor/Abstract.php @@ -0,0 +1,110 @@ +. + */ + +/** + * Doctrine_ORM_Query_QueryResult + * + * @author Roman Borschel + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +abstract class Doctrine_ORM_Query_SqlExecutor_Abstract implements Serializable +{ + // [TODO] Remove me later! + //public $AST; + + protected $_sqlStatements; + + public function __construct(Doctrine_ORM_Query_AST $AST) + { + // [TODO] Remove me later! + //$this->AST = $AST; + } + + /** + * Gets the SQL statements that are executed by the executor. + * + * @return array All the SQL update statements. + */ + public function getSqlStatements() + { + return $this->_sqlStatements; + } + + /** + * Executes all sql statements. + * + * @param Doctrine_Connection $conn The database connection that is used to execute the queries. + * @param array $params The parameters. + */ + abstract public function execute(Doctrine_Connection $conn, array $params); + + /** + * Factory method. + * Creates an appropriate sql executor for the given AST. + * + * @param Doctrine_ORM_Query_Production $AST The root node of the AST. + * @return Doctrine_ORM_Query_SqlExecutor_Abstract The executor that is suitable for the given AST. + */ + public static function create(Doctrine_ORM_Query_AST $AST) + { + $isDeleteStatement = $AST instanceof Doctrine_ORM_Query_AST_DeleteStatement; + $isUpdateStatement = $AST instanceof Doctrine_ORM_Query_AST_UpdateStatement; + + if ($isUpdateStatement || $isDeleteStatement) { + // TODO: Inspect the $AST and create the proper executor like so (pseudo-code): + /* + if (primaryClassInFromClause->isMultiTable()) { + if ($isDeleteStatement) { + return new Doctrine_ORM_Query_SqlExecutor_MultiTableDelete($AST); + } else { + return new Doctrine_ORM_Query_SqlExecutor_MultiTableUpdate($AST); + } + } else ... + */ + return new Doctrine_ORM_Query_SqlExecutor_SingleTableDeleteUpdate($AST); + } else { + return new Doctrine_ORM_Query_SqlExecutor_SingleSelect($AST); + } + } + + + /** + * Serializes the sql statements of the executor. + * + * @return string + */ + public function serialize() + { + return serialize($this->_sqlStatements); + } + + + /** + * Reconstructs the executor with it's sql statements. + */ + public function unserialize($serialized) + { + $this->_sqlStatements = unserialize($serialized); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/SqlExecutor/MultiTableDelete.php b/lib/Doctrine/ORM/Query/SqlExecutor/MultiTableDelete.php new file mode 100644 index 000000000..c7d57f4d0 --- /dev/null +++ b/lib/Doctrine/ORM/Query/SqlExecutor/MultiTableDelete.php @@ -0,0 +1,59 @@ +. + */ + +/** + * Executes the SQL statements for bulk DQL DELETE statements on classes in + * Class Table Inheritance (JOINED). + * + * @author Roman Borschel + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + * @todo For a good implementation that uses temporary tables see the Hibernate sources: + * (org.hibernate.hql.ast.exec.MultiTableDeleteExecutor). + * @todo Rename to MultiTableDeleteExecutor + */ +class Doctrine_ORM_Query_SqlExecutor_MultiTableDelete extends Doctrine_ORM_Query_SqlExecutor_Abstract +{ + /** + * Enter description here... + * + * @param Doctrine_ORM_Query_AST $AST + */ + public function __construct(Doctrine_ORM_Query_AST $AST) + { + // TODO: Inspect the AST, create the necessary SQL queries and store them + // in $this->_sqlStatements + } + + /** + * Executes all sql statements. + * + * @param Doctrine_Connection $conn The database connection that is used to execute the queries. + * @param array $params The parameters. + * @override + */ + public function execute(Doctrine_Connection $conn, array $params) + { + //... + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/SqlExecutor/MultiTableUpdate.php b/lib/Doctrine/ORM/Query/SqlExecutor/MultiTableUpdate.php new file mode 100644 index 000000000..11673ac4d --- /dev/null +++ b/lib/Doctrine/ORM/Query/SqlExecutor/MultiTableUpdate.php @@ -0,0 +1,54 @@ +. + */ + +/** + * Executes the SQL statements for bulk DQL UPDATE statements on classes in + * Class Table Inheritance (JOINED). + * + * @author Roman Borschel + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + * @todo For a good implementation that uses temporary tables see the Hibernate sources: + * (org.hibernate.hql.ast.exec.MultiTableUpdateExecutor). + * @todo Rename to MultiTableUpdateExecutor + */ +class Doctrine_ORM_Query_SqlExecutor_MultiTableUpdate extends Doctrine_ORM_Query_SqlExecutor_Abstract +{ + public function __construct(Doctrine_ORM_Query_AST $AST) + { + // TODO: Inspect the AST, create the necessary SQL queries and store them + // in $this->_sqlStatements + } + + /** + * Executes all sql statements. + * + * @param Doctrine_Connection $conn The database connection that is used to execute the queries. + * @param array $params The parameters. + * @override + */ + public function execute(Doctrine_Connection $conn, array $params) + { + //... + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/SqlExecutor/SingleSelect.php b/lib/Doctrine/ORM/Query/SqlExecutor/SingleSelect.php new file mode 100644 index 000000000..643d5b576 --- /dev/null +++ b/lib/Doctrine/ORM/Query/SqlExecutor/SingleSelect.php @@ -0,0 +1,43 @@ +. + */ + +/** + * Executor that executes the SQL statement for simple DQL SELECT statements. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @author Roman Borschel + * @version $Revision$ + * @link www.phpdoctrine.org + * @since 2.0 + */ +class Doctrine_ORM_Query_SqlExecutor_SingleSelect extends Doctrine_ORM_Query_SqlExecutor_Abstract +{ + public function __construct(Doctrine_ORM_Query_AST $AST) + { + parent::__construct($AST); + $this->_sqlStatements = $AST->buildSql(); + } + + public function execute(Doctrine_Connection $conn, array $params) + { + return $conn->execute($this->_sqlStatements, $params); + } +} diff --git a/lib/Doctrine/ORM/Query/SqlExecutor/SingleTableDeleteUpdate.php b/lib/Doctrine/ORM/Query/SqlExecutor/SingleTableDeleteUpdate.php new file mode 100644 index 000000000..1062ddd96 --- /dev/null +++ b/lib/Doctrine/ORM/Query/SqlExecutor/SingleTableDeleteUpdate.php @@ -0,0 +1,45 @@ +. + */ + +/** + * Executor that executes the SQL statements for DQL DELETE/UPDATE statements on classes + * that are mapped to a single table. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @author Roman Borschel + * @version $Revision$ + * @link www.phpdoctrine.org + * @since 2.0 + * @todo This is exactly the same as SingleSelectExecutor. Unify in SingleStatementExecutor. + */ +class Doctrine_ORM_Query_SqlExecutor_SingleTableDeleteUpdate extends Doctrine_ORM_Query_SqlExecutor_Abstract +{ + public function __construct(Doctrine_ORM_Query_AST $AST) + { + parent::__construct($AST); + $this->_sqlStatements = $AST->buildSql(); + } + + public function execute(Doctrine_Connection $conn, array $params) + { + return $conn->exec($this->_sqlStatements, $params); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/Token.php b/lib/Doctrine/ORM/Query/Token.php new file mode 100644 index 000000000..440086a9a --- /dev/null +++ b/lib/Doctrine/ORM/Query/Token.php @@ -0,0 +1,154 @@ +. + */ + +/** + * Container for token type constants of Doctrine Query Language. + * + * @author Guilherme Blanco + * @author Janne Vanhala + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link http://www.phpdoctrine.org + * @since 2.0 + * @version $Revision$ + */ +final class Doctrine_ORM_Query_Token +{ + const T_NONE = 1; + const T_IDENTIFIER = 2; + const T_INTEGER = 3; + const T_STRING = 4; + const T_INPUT_PARAMETER = 5; + const T_FLOAT = 6; + + const T_ALL = 101; + const T_AND = 102; + const T_ANY = 103; + const T_AS = 104; + const T_ASC = 105; + const T_AVG = 106; + const T_BETWEEN = 107; + const T_BY = 108; + const T_COUNT = 109; + const T_DELETE = 110; + const T_DESC = 111; + const T_DISTINCT = 112; + const T_ESCAPE = 113; + const T_EXISTS = 114; + const T_FROM = 115; + const T_GROUP = 116; + const T_HAVING = 117; + const T_IN = 118; + const T_INDEX = 119; + const T_INNER = 120; + const T_IS = 121; + const T_JOIN = 122; + const T_LEFT = 123; + const T_LIKE = 124; + const T_LIMIT = 125; + const T_MAX = 126; + const T_MIN = 127; + const T_MOD = 128; + const T_NOT = 129; + const T_NULL = 130; + const T_OFFSET = 131; + const T_ON = 132; + const T_OR = 133; + const T_ORDER = 134; + const T_OUTER = 135; + const T_SELECT = 136; + const T_SET = 137; + const T_SIZE = 138; + const T_SOME = 139; + const T_SUM = 140; + const T_UPDATE = 141; + const T_WHERE = 142; + const T_WITH = 143; + + const T_TRUE = 144; + const T_FALSE = 145; + + + protected $_keywordsTable = array(); + + + public function __construct() + { + $this->addKeyword(self::T_ALL, "ALL"); + $this->addKeyword(self::T_AND, "AND"); + $this->addKeyword(self::T_ANY, "ANY"); + $this->addKeyword(self::T_AS, "AS"); + $this->addKeyword(self::T_ASC, "ASC"); + $this->addKeyword(self::T_AVG, "AVG"); + $this->addKeyword(self::T_BETWEEN, "BETWEEN"); + $this->addKeyword(self::T_BY, "BY"); + $this->addKeyword(self::T_COUNT, "COUNT"); + $this->addKeyword(self::T_DELETE, "DELETE"); + $this->addKeyword(self::T_DESC, "DESC"); + $this->addKeyword(self::T_DISTINCT, "DISTINCT"); + $this->addKeyword(self::T_ESCAPE, "ESPACE"); + $this->addKeyword(self::T_EXISTS, "EXISTS"); + $this->addKeyword(self::T_FALSE, "FALSE"); + $this->addKeyword(self::T_FROM, "FROM"); + $this->addKeyword(self::T_GROUP, "GROUP"); + $this->addKeyword(self::T_HAVING, "HAVING"); + $this->addKeyword(self::T_IN, "IN"); + $this->addKeyword(self::T_INDEX, "INDEX"); + $this->addKeyword(self::T_INNER, "INNER"); + $this->addKeyword(self::T_IS, "IS"); + $this->addKeyword(self::T_JOIN, "JOIN"); + $this->addKeyword(self::T_LEFT, "LEFT"); + $this->addKeyword(self::T_LIKE, "LIKE"); + $this->addKeyword(self::T_LIMIT, "LIMIT"); + $this->addKeyword(self::T_MAX, "MAX"); + $this->addKeyword(self::T_MIN, "MIN"); + $this->addKeyword(self::T_MOD, "MOD"); + $this->addKeyword(self::T_NOT, "NOT"); + $this->addKeyword(self::T_NULL, "NULL"); + $this->addKeyword(self::T_OFFSET, "OFFSET"); + $this->addKeyword(self::T_ON, "ON"); + $this->addKeyword(self::T_OR, "OR"); + $this->addKeyword(self::T_ORDER, "ORDER"); + $this->addKeyword(self::T_OUTER, "OUTER"); + $this->addKeyword(self::T_SELECT, "SELECT"); + $this->addKeyword(self::T_SET, "SET"); + $this->addKeyword(self::T_SIZE, "SIZE"); + $this->addKeyword(self::T_SOME, "SOME"); + $this->addKeyword(self::T_SUM, "SUM"); + $this->addKeyword(self::T_TRUE, "TRUE"); + $this->addKeyword(self::T_UPDATE, "UPDATE"); + $this->addKeyword(self::T_WHERE, "WHERE"); + $this->addKeyword(self::T_WITH, "WITH"); + } + + + protected function addKeyword($token, $value) + { + $this->_keywordsTable[$token] = $value; + } + + + public function getLiteral($token) + { + return isset($this->_keywordsTable[$token]) + ? $this->_keywordsTable[$token] + : (is_string($token) ? $token : ''); + } +}