diff --git a/lib/Doctrine/ORM/Query/Lexer.php b/lib/Doctrine/ORM/Query/Lexer.php index 04c9b4916..bbb6a8e64 100644 --- a/lib/Doctrine/ORM/Query/Lexer.php +++ b/lib/Doctrine/ORM/Query/Lexer.php @@ -55,41 +55,42 @@ class Lexer const T_DESC = 112; const T_DISTINCT = 113; const T_DOT = 114; - const T_ESCAPE = 115; - const T_EXISTS = 116; - const T_FROM = 117; - const T_GROUP = 118; - const T_HAVING = 119; - const T_IN = 120; - const T_INDEX = 121; - const T_INNER = 122; - const T_IS = 123; - const T_JOIN = 124; - const T_LEFT = 125; - const T_LIKE = 126; - const T_LIMIT = 127; - const T_MAX = 128; - const T_MIN = 129; - const T_MOD = 130; - const T_NOT = 131; - const T_NULL = 132; - const T_OFFSET = 133; - const T_ON = 134; - const T_OR = 135; - const T_ORDER = 136; - const T_OUTER = 137; - const T_SELECT = 138; - const T_SET = 139; - const T_SIZE = 140; - const T_SOME = 141; - const T_SUM = 142; - const T_UPDATE = 143; - const T_WHERE = 144; - const T_WITH = 145; - const T_TRUE = 146; - const T_FALSE = 147; - const T_MEMBER = 148; - const T_OF = 149; + const T_EMPTY = 115; + const T_ESCAPE = 116; + const T_EXISTS = 117; + const T_FROM = 118; + const T_GROUP = 119; + const T_HAVING = 120; + const T_IN = 121; + const T_INDEX = 122; + const T_INNER = 123; + const T_IS = 124; + const T_JOIN = 125; + const T_LEFT = 126; + const T_LIKE = 127; + const T_LIMIT = 128; + const T_MAX = 129; + const T_MIN = 130; + const T_MOD = 131; + const T_NOT = 132; + const T_NULL = 133; + const T_OFFSET = 134; + const T_ON = 135; + const T_OR = 136; + const T_ORDER = 137; + const T_OUTER = 138; + const T_SELECT = 139; + const T_SET = 140; + const T_SIZE = 141; + const T_SOME = 142; + const T_SUM = 143; + const T_UPDATE = 144; + const T_WHERE = 145; + const T_WITH = 146; + const T_TRUE = 147; + const T_FALSE = 148; + const T_MEMBER = 149; + const T_OF = 150; private $_keywordsTable; @@ -354,6 +355,7 @@ class Lexer self::T_DESC => "DESC", self::T_DISTINCT => "DISTINCT", self::T_DOT => ".", + self::T_EMPTY => "EMPTY", self::T_ESCAPE => "ESCAPE", self::T_EXISTS => "EXISTS", self::T_FALSE => "FALSE", diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 47cb72900..bd37dac97 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -1639,7 +1639,7 @@ class Parser * InExpression | NullComparisonExpression | ExistsExpression | * EmptyCollectionComparisonExpression | CollectionMemberExpression * - * @todo Posy 2.0 release. Missing EmptyCollectionComparisonExpression implementation + * @todo Post 2.0 release. Missing EmptyCollectionComparisonExpression implementation */ public function SimpleConditionalExpression() { @@ -1669,9 +1669,18 @@ class Parser if ($peek['type'] === Lexer::T_NOT) { $peek = $this->_lexer->peek(); } - - $this->_lexer->resetPeek(); + $token = $peek; + + // We need to go even further in case of IS (differenciate between NULL and EMPTY) + $lookahead = $this->_lexer->peek(); + + // Also peek beyond a NOT if there is one + if ($lookahead['type'] === Lexer::T_NOT) { + $lookahead = $this->_lexer->peek(); + } + + $this->_lexer->resetPeek(); } if ($pathExprOrInputParam) { @@ -1689,7 +1698,11 @@ class Parser return $this->InExpression(); case Lexer::T_IS: - return $this->NullComparisonExpression(); + if ($lookahead['type'] == Lexer::T_NULL) { + return $this->NullComparisonExpression(); + } + + return $this->EmptyCollectionComparisonExpression(); case Lexer::T_MEMBER: return $this->CollectionMemberExpression(); @@ -1703,6 +1716,28 @@ class Parser } + /** + * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" + * + * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression + */ + public function EmptyCollectionComparisonExpression() + { + $emptyColletionCompExpr = new AST\EmptyCollectionComparisonExpression( + $this->CollectionValuedPathExpression() + ); + $this->match(Lexer::T_IS); + + if ($this->_lexer->isNextToken(Lexer::T_NOT)) { + $this->match(Lexer::T_NOT); + $emptyColletionCompExpr->setNot(true); + } + + $this->match(Lexer::T_EMPTY); + + return $emptyColletionCompExpr; + } + /** * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression * @@ -2122,9 +2157,6 @@ class Parser /** * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) * - * @return AST\ComparisonExpression - * @todo Semantical checks whether $leftExpr $operator and $rightExpr are compatible. - * * @return \Doctrine\ORM\Query\AST\ComparisonExpression */ public function ComparisonExpression() diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 4d998e0d6..11a3cb16e 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -40,31 +40,42 @@ class SqlWalker implements TreeWalker { /** The ResultSetMapping. */ private $_rsm; + /** Counter for generating unique column aliases. */ private $_aliasCounter = 0; + /** Counter for generating unique table aliases. */ private $_tableAliasCounter = 0; private $_scalarResultCounter = 1; + /** Counter for SQL parameter positions. */ private $_sqlParamIndex = 1; + /** The ParserResult. */ private $_parserResult; + /** The EntityManager. */ private $_em; + /** The Connection of the EntityManager. */ private $_conn; + /** The Query instance. */ private $_query; private $_dqlToSqlAliasMap = array(); + /** Map of all components/classes that appear in the DQL query. */ private $_queryComponents; + /** A list of classes that appear in non-scalar SelectExpressions. */ private $_selectedClasses = array(); + /** * The DQL alias of the root class of the currently traversed query. * TODO: May need to be turned into a stack for usage in subqueries */ private $_currentRootAlias; + /** * Flag that indicates whether to generate SQL table aliases in the SQL. * These should only be generated for SELECT queries. @@ -123,6 +134,7 @@ class SqlWalker implements TreeWalker { $sql = $this->walkSelectClause($AST->getSelectClause()); $sql .= $this->walkFromClause($AST->getFromClause()); + if ($whereClause = $AST->getWhereClause()) { $sql .= $this->walkWhereClause($whereClause); } else if ($discSql = $this->_generateDiscriminatorColumnConditionSql($this->_currentRootAlias)) { @@ -135,8 +147,8 @@ class SqlWalker implements TreeWalker $q = $this->getQuery(); $sql = $this->getConnection()->getDatabasePlatform()->modifyLimitQuery( - $sql, $q->getMaxResults(), $q->getFirstResult() - ); + $sql, $q->getMaxResults(), $q->getFirstResult() + ); return $sql; } @@ -148,9 +160,9 @@ class SqlWalker implements TreeWalker */ public function walkSelectClause($selectClause) { - $sql = 'SELECT ' . (($selectClause->isDistinct()) ? 'DISTINCT ' : '') - . implode(', ', array_map(array($this, 'walkSelectExpression'), - $selectClause->getSelectExpressions())); + $sql = 'SELECT ' . (($selectClause->isDistinct()) ? 'DISTINCT ' : '') . implode( + ', ', array_map(array($this, 'walkSelectExpression'), $selectClause->getSelectExpressions()) + ); foreach ($this->_selectedClasses as $dqlAlias => $class) { if ($this->_queryComponents[$dqlAlias]['relation'] === null) { @@ -169,6 +181,7 @@ class SqlWalker implements TreeWalker $discrColumn = $rootClass->discriminatorColumn; $columnAlias = $this->getSqlColumnAlias($discrColumn['name']); $sql .= ", $tblAlias." . $discrColumn['name'] . ' AS ' . $columnAlias; + $this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias); $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']); } @@ -225,9 +238,9 @@ class SqlWalker implements TreeWalker public function walkOrderByClause($orderByClause) { // OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* - return ' ORDER BY ' - . implode(', ', array_map(array($this, 'walkOrderByItem'), - $orderByClause->getOrderByItems())); + return ' ORDER BY ' . implode( + ', ', array_map(array($this, 'walkOrderByItem'), $orderByClause->getOrderByItems()) + ); } /** @@ -244,6 +257,7 @@ class SqlWalker implements TreeWalker $dqlAlias = $expr->getIdentificationVariable(); $qComp = $this->_queryComponents[$dqlAlias]; $columnName = $qComp['metadata']->getColumnName($parts[0]); + $sql = $this->getSqlTableAlias($qComp['metadata']->getTableName(), $dqlAlias) . '.' . $columnName; $sql .= $orderByItem->isDesc() ? ' DESC' : ' ASC'; @@ -258,8 +272,11 @@ class SqlWalker implements TreeWalker */ public function walkHavingClause($havingClause) { - return ' HAVING ' . implode(' OR ', array_map(array($this, 'walkConditionalTerm'), - $havingClause->getConditionalExpression()->getConditionalTerms())); + $condExpr = $havingClause->getConditionalExpression(); + + return ' HAVING ' . implode( + ' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->getConditionalTerms()) + ); } /** @@ -272,6 +289,7 @@ class SqlWalker implements TreeWalker { $join = $joinVarDecl->getJoin(); $joinType = $join->getJoinType(); + if ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) { $sql = ' LEFT JOIN '; } else { @@ -304,6 +322,7 @@ class SqlWalker implements TreeWalker } else { $first = false; } + if ($targetQComp['relation']->isOwningSide()) { $sql .= "$sourceTableAlias.$sourceColumn = $targetTableAlias.$targetColumn"; } else { @@ -315,34 +334,34 @@ class SqlWalker implements TreeWalker $joinTable = $assoc->getJoinTable(); $joinTableAlias = $this->getSqlTableAlias($joinTable['name']); $sql .= $joinTable['name'] . ' ' . $joinTableAlias . ' ON '; + if ($targetQComp['relation']->isOwningSide) { $sourceToRelationJoinColumns = $assoc->getSourceToRelationKeyColumns(); + foreach ($sourceToRelationJoinColumns as $sourceColumn => $relationColumn) { $sql .= "$sourceTableAlias.$sourceColumn = $joinTableAlias.$relationColumn"; } } else { $targetToRelationJoinColumns = $assoc->getTargetToRelationKeyColumns(); + foreach ($targetToRelationJoinColumns as $targetColumn => $relationColumn) { $sql .= "$sourceTableAlias.$targetColumn = $joinTableAlias.$relationColumn"; } } // Join target table - if ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) { - $sql .= ' LEFT JOIN '; - } else { - $sql .= ' INNER JOIN '; - } - - $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON '; + $sql .= (($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) + ? ' LEFT JOIN ' : ' INNER JOIN ') . $targetTableName . ' ' . $targetTableAlias . ' ON '; if ($targetQComp['relation']->isOwningSide) { $targetToRelationJoinColumns = $assoc->getTargetToRelationKeyColumns(); + foreach ($targetToRelationJoinColumns as $targetColumn => $relationColumn) { $sql .= "$targetTableAlias.$targetColumn = $joinTableAlias.$relationColumn"; } } else { $sourceToRelationJoinColumns = $assoc->getSourceToRelationKeyColumns(); + foreach ($sourceToRelationJoinColumns as $sourceColumn => $relationColumn) { $sql .= "$targetTableAlias.$sourceColumn = $joinTableAlias.$relationColumn"; } @@ -350,6 +369,7 @@ class SqlWalker implements TreeWalker } $discrSql = $this->_generateDiscriminatorColumnConditionSql($joinedDqlAlias); + if ($discrSql) { $sql .= ' AND ' . $discrSql; } @@ -389,8 +409,7 @@ class SqlWalker implements TreeWalker $columnAlias = $this->getSqlColumnAlias($columnName); $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; - $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName); - + $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName); } else { throw DoctrineException::updateMe("Encountered invalid PathExpression during SQL construction."); } @@ -400,8 +419,10 @@ class SqlWalker implements TreeWalker } else { $resultAlias = $selectExpression->getFieldIdentificationVariable(); } + $columnAlias = 'sclr' . $this->_aliasCounter++; $sql .= $this->walkAggregateExpression($expr) . ' AS ' . $columnAlias; + $this->_rsm->addScalarResult($columnAlias, $resultAlias); } else if ($expr instanceof AST\Subselect) { $sql .= $this->walkSubselect($expr); @@ -411,12 +432,13 @@ class SqlWalker implements TreeWalker } else { $resultAlias = $selectExpression->getFieldIdentificationVariable(); } + $columnAlias = 'sclr' . $this->_aliasCounter++; $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; + $this->_rsm->addScalarResult($columnAlias, $resultAlias); } else { // IdentificationVariable - $dqlAlias = $expr; $queryComp = $this->_queryComponents[$dqlAlias]; $class = $queryComp['metadata']; @@ -434,40 +456,52 @@ class SqlWalker implements TreeWalker } else { $tableName = $class->primaryTable['name']; } + if ($beginning) $beginning = false; else $sql .= ', '; + $sqlTableAlias = $this->getSqlTableAlias($tableName, $dqlAlias); $columnAlias = $this->getSqlColumnAlias($mapping['columnName']); $sql .= $sqlTableAlias . '.' . $this->_conn->quoteIdentifier($mapping['columnName']) . ' AS ' . $columnAlias; + $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName); } // Add any additional fields of subclasses (not inherited fields) foreach ($class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); + foreach ($subClass->fieldMappings as $fieldName => $mapping) { if (isset($mapping['inherited'])) { continue; } + if ($beginning) $beginning = false; else $sql .= ', '; + $sqlTableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'], $dqlAlias); $columnAlias = $this->getSqlColumnAlias($mapping['columnName']); $sql .= $sqlTableAlias . '.' . $this->_conn->quoteIdentifier($mapping['columnName']) . ' AS ' . $columnAlias; + $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName); } } } else { $fieldMappings = $class->fieldMappings; + foreach ($class->subClasses as $subclassName) { $fieldMappings = array_merge( $fieldMappings, $this->_em->getClassMetadata($subclassName)->fieldMappings ); } + $sqlTableAlias = $this->getSqlTableAlias($class->getTableName(), $dqlAlias); + foreach ($fieldMappings as $fieldName => $mapping) { if ($beginning) $beginning = false; else $sql .= ', '; + $columnAlias = $this->getSqlColumnAlias($mapping['columnName']); $sql .= $sqlTableAlias . '.' . $this->_conn->quoteIdentifier($mapping['columnName']) . ' AS ' . $columnAlias; + $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName); } @@ -475,13 +509,16 @@ class SqlWalker implements TreeWalker //FIXME: Evaluate HINT_INCLUDE_META_COLUMNS //FIXME: Needs to be done in the case of Class Table Inheritance, too // (see upper block of the if/else) - if ( ! $this->_em->getConfiguration()->getAllowPartialObjects() && - ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { + if ( + ! $this->_em->getConfiguration()->getAllowPartialObjects() && + ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) + ) { foreach ($class->associationMappings as $assoc) { if ($assoc->isOwningSide && $assoc->isOneToOne()) { foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { $columnAlias = $this->getSqlColumnAlias($srcColumn); $sql .= ', ' . $sqlTableAlias . '.' . $this->_conn->quoteIdentifier($srcColumn) . ' AS ' . $columnAlias; + $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn); } } @@ -503,9 +540,11 @@ class SqlWalker implements TreeWalker public function walkQuantifiedExpression($qExpr) { $sql = ''; + if ($qExpr->isAll()) $sql .= ' ALL'; else if ($qExpr->isAny()) $sql .= ' ANY'; else if ($qExpr->isSome()) $sql .= ' SOME'; + return $sql .= '(' . $this->walkSubselect($qExpr->getSubselect()) . ')'; } @@ -519,12 +558,14 @@ class SqlWalker implements TreeWalker { $useAliasesBefore = $this->_useSqlTableAliases; $this->_useSqlTableAliases = true; + $sql = $this->walkSimpleSelectClause($subselect->getSimpleSelectClause()); $sql .= $this->walkSubselectFromClause($subselect->getSubselectFromClause()); $sql .= $subselect->getWhereClause() ? $this->walkWhereClause($subselect->getWhereClause()) : ''; $sql .= $subselect->getGroupByClause() ? $this->walkGroupByClause($subselect->getGroupByClause()) : ''; $sql .= $subselect->getHavingClause() ? $this->walkHavingClause($subselect->getHavingClause()) : ''; $sql .= $subselect->getOrderByClause() ? $this->walkOrderByClause($subselect->getOrderByClause()) : ''; + $this->_useSqlTableAliases = $useAliasesBefore; return $sql; @@ -543,7 +584,7 @@ class SqlWalker implements TreeWalker $firstIdentificationVarDecl = $identificationVarDecls[0]; $rangeDecl = $firstIdentificationVarDecl->getRangeVariableDeclaration(); $sql .= $rangeDecl->getClassMetadata()->getTableName() . ' ' - . $this->getSqlTableAlias($rangeDecl->getClassMetadata()->getTableName(), $rangeDecl->getAliasIdentificationVariable()); + . $this->getSqlTableAlias($rangeDecl->getClassMetadata()->getTableName(), $rangeDecl->getAliasIdentificationVariable()); foreach ($firstIdentificationVarDecl->getJoinVariableDeclarations() as $joinVarDecl) { $sql .= $this->walkJoinVariableDeclaration($joinVarDecl); @@ -561,10 +602,13 @@ class SqlWalker implements TreeWalker public function walkSimpleSelectClause($simpleSelectClause) { $sql = 'SELECT'; + if ($simpleSelectClause->isDistinct()) { $sql .= ' DISTINCT'; } + $sql .= $this->walkSimpleSelectExpression($simpleSelectClause->getSimpleSelectExpression()); + return $sql; } @@ -578,6 +622,7 @@ class SqlWalker implements TreeWalker { $sql = ''; $expr = $simpleSelectExpression->getExpression(); + if ($expr instanceof AST\PathExpression) { $sql .= ' ' . $this->walkPathExpression($expr); //... @@ -587,6 +632,7 @@ class SqlWalker implements TreeWalker } else { $alias = $simpleSelectExpression->getFieldIdentificationVariable(); } + $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias; } else { // IdentificationVariable @@ -596,6 +642,7 @@ class SqlWalker implements TreeWalker $sql .= ' ' . $this->getSqlTableAlias($class->getTableName(), $expr) . '.'; $sql .= $class->getColumnName($class->identifier[0]); } + return $sql; } @@ -616,9 +663,12 @@ class SqlWalker implements TreeWalker $columnName = $qComp['metadata']->getColumnName($fieldName); $sql .= $aggExpression->getFunctionName() . '('; + if ($aggExpression->isDistinct()) $sql .= 'DISTINCT '; + $sql .= $this->getSqlTableAlias($qComp['metadata']->getTableName(), $dqlAlias) . '.' . $columnName; $sql .= ')'; + return $sql; } @@ -630,9 +680,9 @@ class SqlWalker implements TreeWalker */ public function walkGroupByClause($groupByClause) { - return ' GROUP BY ' - . implode(', ', array_map(array($this, 'walkGroupByItem'), - $groupByClause->getGroupByItems())); + return ' GROUP BY ' . implode( + ', ', array_map(array($this, 'walkGroupByItem'), $groupByClause->getGroupByItems()) + ); } /** @@ -648,6 +698,7 @@ class SqlWalker implements TreeWalker $dqlAlias = $pathExpr->getIdentificationVariable(); $qComp = $this->_queryComponents[$dqlAlias]; $columnName = $qComp['metadata']->getColumnName($parts[0]); + return $this->getSqlTableAlias($qComp['metadata']->getTableName(), $dqlAlias) . '.' . $columnName; } @@ -661,11 +712,13 @@ class SqlWalker implements TreeWalker { $this->_useSqlTableAliases = false; // TODO: Ask platform instead? $sql = $this->walkUpdateClause($AST->getUpdateClause()); + if ($whereClause = $AST->getWhereClause()) { $sql .= $this->walkWhereClause($whereClause); } else if ($discSql = $this->_generateDiscriminatorColumnConditionSql($this->_currentRootAlias)) { $sql .= ' WHERE ' . $discSql; } + return $sql; } @@ -679,11 +732,13 @@ class SqlWalker implements TreeWalker { $this->_useSqlTableAliases = false; // TODO: Ask platform instead? $sql = $this->walkDeleteClause($AST->getDeleteClause()); + if ($whereClause = $AST->getWhereClause()) { $sql .= $this->walkWhereClause($whereClause); } else if ($discSql = $this->_generateDiscriminatorColumnConditionSql($this->_currentRootAlias)) { $sql .= ' WHERE ' . $discSql; } + return $sql; } @@ -698,9 +753,11 @@ class SqlWalker implements TreeWalker $sql = 'DELETE FROM '; $class = $this->_em->getClassMetadata($deleteClause->getAbstractSchemaName()); $sql .= $class->getTableName(); + if ($this->_useSqlTableAliases) { $sql .= ' ' . $this->getSqlTableAlias($class->getTableName()); } + $this->_currentRootAlias = $deleteClause->getAliasIdentificationVariable(); return $sql; @@ -717,13 +774,16 @@ class SqlWalker implements TreeWalker $sql = 'UPDATE '; $class = $this->_em->getClassMetadata($updateClause->getAbstractSchemaName()); $sql .= $class->getTableName(); + if ($this->_useSqlTableAliases) { $sql .= ' ' . $this->getSqlTableAlias($class->getTableName()); } + $this->_currentRootAlias = $updateClause->getAliasIdentificationVariable(); - $sql .= ' SET ' . implode(', ', array_map(array($this, 'walkUpdateItem'), - $updateClause->getUpdateItems())); + $sql .= ' SET ' . implode( + ', ', array_map(array($this, 'walkUpdateItem'), $updateClause->getUpdateItems()) + ); return $sql; } @@ -746,6 +806,7 @@ class SqlWalker implements TreeWalker if ($this->_useSqlTableAliases) { $sql .= $this->getSqlTableAlias($qComp['metadata']->getTableName()) . '.'; } + $sql .= $qComp['metadata']->getColumnName($updateItem->getField()) . ' = '; $newValue = $updateItem->getNewValue(); @@ -776,12 +837,15 @@ class SqlWalker implements TreeWalker $sql = ' WHERE '; $condExpr = $whereClause->getConditionalExpression(); - $sql .= implode(' OR ', array_map(array($this, 'walkConditionalTerm'), - $condExpr->getConditionalTerms())); + $sql .= implode( + ' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->getConditionalTerms()) + ); $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_currentRootAlias); + if ($discrSql) { if ($termsSql) $sql .= ' AND'; + $sql .= ' ' . $discrSql; } @@ -799,19 +863,25 @@ class SqlWalker implements TreeWalker $sql = ''; if ($dqlAlias) { $class = $this->_queryComponents[$dqlAlias]['metadata']; + if ($class->isInheritanceTypeSingleTable()) { $conn = $this->_em->getConnection(); $values = array($conn->quote($class->discriminatorValue)); + foreach ($class->subClasses as $subclassName) { $values[] = $conn->quote($this->_em->getClassMetadata($subclassName)->discriminatorValue); } + $discrColumn = $class->discriminatorColumn; + if ($this->_useSqlTableAliases) { $sql .= $this->getSqlTableAlias($class->getTableName(), $dqlAlias) . '.'; } + $sql .= $discrColumn['name'] . ' IN (' . implode(', ', $values) . ')'; } } + return $sql; } @@ -823,8 +893,9 @@ class SqlWalker implements TreeWalker */ public function walkConditionalTerm($condTerm) { - return implode(' AND ', array_map(array($this, 'walkConditionalFactor'), - $condTerm->getConditionalFactors())); + return implode( + ' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->getConditionalFactors()) + ); } /** @@ -836,14 +907,20 @@ class SqlWalker implements TreeWalker public function walkConditionalFactor($factor) { $sql = ''; + if ($factor->isNot()) $sql .= 'NOT '; + $primary = $factor->getConditionalPrimary(); + if ($primary->isSimpleConditionalExpression()) { $sql .= $primary->getSimpleConditionalExpression()->dispatch($this); } else if ($primary->isConditionalExpression()) { - $sql .= '(' . implode(' OR ', array_map(array($this, 'walkConditionalTerm'), - $primary->getConditionalExpression()->getConditionalTerms())) . ')'; + $condExpr = $primary->getConditionalExpression(); + $sql .= '(' . implode( + ' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->getConditionalTerms()) + ) . ')'; } + return $sql; } @@ -856,8 +933,11 @@ class SqlWalker implements TreeWalker public function walkExistsExpression($existsExpr) { $sql = ''; + if ($existsExpr->isNot()) $sql .= ' NOT'; + $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->getSubselect()) . ')'; + return $sql; } @@ -881,7 +961,6 @@ class SqlWalker implements TreeWalker if ($entityExpr instanceof AST\InputParameter) { $dqlParamKey = $entityExpr->isNamed() ? $entityExpr->getName() : $entityExpr->getPosition(); $entity = $this->_query->getParameter($dqlParamKey); - } else { throw DoctrineException::notImplemented(); } @@ -898,15 +977,19 @@ class SqlWalker implements TreeWalker $owningAssoc = $targetClass->associationMappings[$assoc->mappedByFieldName]; $first = true; + foreach ($owningAssoc->targetToSourceKeyColumns as $targetColumn => $sourceColumn) { if ($first) $first = false; else $sql .= ' AND '; + $sql .= $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn; } $sql .= ' AND '; $first = true; + foreach ($targetClass->identifier as $idField) { if ($first) $first = false; else $sql .= ' AND '; + $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); $sql .= $targetTableAlias . '.' . $targetClass->columnNames[$idField] . ' = ?'; } @@ -919,15 +1002,17 @@ class SqlWalker implements TreeWalker // join to target table $sql .= $this->_conn->quoteIdentifier($joinTable['name']) - . ' ' . $joinTableAlias . ' INNER JOIN ' - . $this->_conn->quoteIdentifier($targetClass->primaryTable['name']) - . ' ' . $targetTableAlias . ' ON '; + . ' ' . $joinTableAlias . ' INNER JOIN ' + . $this->_conn->quoteIdentifier($targetClass->primaryTable['name']) + . ' ' . $targetTableAlias . ' ON '; // join conditions $joinColumns = $assoc->isOwningSide ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns']; $first = true; + foreach ($joinColumns as $joinColumn) { if ($first) $first = false; else $sql .= ' AND '; + $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $joinColumn['referencedColumnName']; } @@ -935,15 +1020,19 @@ class SqlWalker implements TreeWalker $joinColumns = $assoc->isOwningSide ? $joinTable['inverseJoinColumns'] : $joinTable['joinColumns']; $first = true; + foreach ($joinColumns as $joinColumn) { if ($first) $first = false; else $sql .= ' AND '; + $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $targetTableAlias . '.' . $joinColumn['referencedColumnName']; } $sql .= ' AND '; $first = true; + foreach ($targetClass->identifier as $idField) { if ($first) $first = false; else $sql .= ' AND '; + $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); $sql .= $targetTableAlias . '.' . $targetClass->columnNames[$idField] . ' = ?'; } @@ -951,6 +1040,22 @@ class SqlWalker implements TreeWalker return $sql . ')'; } + + /** + * Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL. + * + * @param EmptyCollectionComparisonExpression + * @return string The SQL. + * + * @todo Finish this implementation. It is quite incomplete! + */ + public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr) + { + $sql = $this->walkPathExpression($emptyCollCompExpr->getExpression()); + $sql .= ' IS' . ($emptyCollCompExpr->isNot() ? ' NOT' : '') . ' EMPTY'; + + return $sql; + } /** * Walks down a NullComparisonExpression AST node, thereby generating the appropriate SQL. @@ -962,6 +1067,7 @@ class SqlWalker implements TreeWalker { $sql = ''; $innerExpr = $nullCompExpr->getExpression(); + if ($innerExpr instanceof AST\InputParameter) { $dqlParamKey = $innerExpr->isNamed() ? $innerExpr->getName() : $innerExpr->getPosition(); $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); @@ -969,7 +1075,9 @@ class SqlWalker implements TreeWalker } else { $sql .= $this->walkPathExpression($innerExpr); } + $sql .= ' IS' . ($nullCompExpr->isNot() ? ' NOT' : '') . ' NULL'; + return $sql; } @@ -982,14 +1090,19 @@ class SqlWalker implements TreeWalker public function walkInExpression($inExpr) { $sql = $this->walkPathExpression($inExpr->getPathExpression()); + if ($inExpr->isNot()) $sql .= ' NOT'; + $sql .= ' IN ('; + if ($inExpr->getSubselect()) { $sql .= $this->walkSubselect($inExpr->getSubselect()); } else { $sql .= implode(', ', array_map(array($this, 'walkLiteral'), $inExpr->getLiterals())); } + $sql .= ')'; + return $sql; } @@ -1004,10 +1117,11 @@ class SqlWalker implements TreeWalker if ($literal instanceof AST\InputParameter) { $dqlParamKey = $literal->isNamed() ? $literal->getName() : $literal->getPosition(); $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); + return '?'; - } else { - return $literal; //TODO: quote() ? } + + return $literal; //TODO: quote() ? } /** @@ -1019,9 +1133,12 @@ class SqlWalker implements TreeWalker public function walkBetweenExpression($betweenExpr) { $sql = $this->walkArithmeticExpression($betweenExpr->getBaseExpression()); + if ($betweenExpr->getNot()) $sql .= ' NOT'; + $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->getLeftBetweenExpression()) - . ' AND ' . $this->walkArithmeticExpression($betweenExpr->getRightBetweenExpression()); + . ' AND ' . $this->walkArithmeticExpression($betweenExpr->getRightBetweenExpression()); + return $sql; } @@ -1033,12 +1150,11 @@ class SqlWalker implements TreeWalker */ public function walkLikeExpression($likeExpr) { - $sql = ''; $stringExpr = $likeExpr->getStringExpression(); - - $sql .= $stringExpr->dispatch($this); + $sql = $stringExpr->dispatch($this); if ($likeExpr->isNot()) $sql .= ' NOT'; + $sql .= ' LIKE '; if ($likeExpr->getStringPattern() instanceof AST\InputParameter) { @@ -1049,9 +1165,11 @@ class SqlWalker implements TreeWalker } else { $sql .= $this->_conn->quote($likeExpr->getStringPattern()); } + if ($likeExpr->getEscapeChar()) { $sql .= ' ESCAPE ' . $this->_conn->quote($likeExpr->getEscapeChar()); } + return $sql; } @@ -1105,6 +1223,7 @@ class SqlWalker implements TreeWalker { $dqlParamKey = $inputParam->isNamed() ? $inputParam->getName() : $inputParam->getPosition(); $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); + return '?'; } @@ -1117,6 +1236,7 @@ class SqlWalker implements TreeWalker public function walkArithmeticExpression($arithmeticExpr) { $sql = ''; + if ($arithmeticExpr->isSimpleArithmeticExpression()) { foreach ($arithmeticExpr->getSimpleArithmeticExpression()->getArithmeticTerms() as $term) { $sql .= $this->walkArithmeticTerm($term); @@ -1124,6 +1244,7 @@ class SqlWalker implements TreeWalker } else { $sql .= $this->walkSubselect($arithmeticExpr->getSubselect()); } + return $sql; } @@ -1137,8 +1258,9 @@ class SqlWalker implements TreeWalker { if (is_string($term)) return $term; - return implode(' ', array_map(array($this, 'walkArithmeticFactor'), - $term->getArithmeticFactors())); + return implode( + ' ', array_map(array($this, 'walkArithmeticFactor'), $term->getArithmeticFactors()) + ); } /** @@ -1149,11 +1271,9 @@ class SqlWalker implements TreeWalker */ public function walkStringPrimary($stringPrimary) { - if (is_string($stringPrimary)) { - return $this->_conn->quote($stringPrimary); - } else { - return $stringPrimary->dispatch($this); - } + return (is_string($stringPrimary)) + ? $this->_conn->quote($stringPrimary) + : $stringPrimary->dispatch($this); } /** @@ -1168,6 +1288,7 @@ class SqlWalker implements TreeWalker $sql = ''; $primary = $factor->getArithmeticPrimary(); + if (is_numeric($primary)) { $sql .= $primary; //TODO: quote() ? } else if (is_string($primary)) { @@ -1189,8 +1310,9 @@ class SqlWalker implements TreeWalker */ public function walkSimpleArithmeticExpression($simpleArithmeticExpr) { - return implode(' ', array_map(array($this, 'walkArithmeticTerm'), - $simpleArithmeticExpr->getArithmeticTerms())); + return implode( + ' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->getArithmeticTerms()) + ); } /** @@ -1202,6 +1324,7 @@ class SqlWalker implements TreeWalker public function walkPathExpression($pathExpr) { $sql = ''; + if ($pathExpr->getType() == AST\PathExpression::TYPE_STATE_FIELD) { $parts = $pathExpr->getParts(); $numParts = count($parts); @@ -1226,7 +1349,6 @@ class SqlWalker implements TreeWalker } else { $sql .= $this->_conn->quoteIdentifier($class->getColumnName($fieldName)); } - } else if ($pathExpr->isSimpleStateFieldAssociationPathExpression()) { throw DoctrineException::updateMe("Not yet implemented."); } else { @@ -1245,9 +1367,11 @@ class SqlWalker implements TreeWalker public function getSqlTableAlias($tableName, $dqlAlias = '') { $tableName .= $dqlAlias; + if ( ! isset($this->_dqlToSqlAliasMap[$tableName])) { $this->_dqlToSqlAliasMap[$tableName] = strtolower(substr($tableName, 0, 1)) . $this->_tableAliasCounter++ . '_'; } + return $this->_dqlToSqlAliasMap[$tableName]; } @@ -1295,8 +1419,10 @@ class SqlWalker implements TreeWalker $tableAlias = $this->getSqlTableAlias($parentClass->primaryTable['name'], $dqlAlias); $sql .= ' INNER JOIN ' . $parentClass->primaryTable['name'] . ' ' . $tableAlias . ' ON '; $first = true; + foreach ($idColumns as $idColumn) { if ($first) $first = false; else $sql .= ' AND '; + $sql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } } @@ -1307,8 +1433,10 @@ class SqlWalker implements TreeWalker $tableAlias = $this->getSqlTableAlias($subClass->primaryTable['name'], $dqlAlias); $sql .= ' LEFT JOIN ' . $subClass->primaryTable['name'] . ' ' . $tableAlias . ' ON '; $first = true; + foreach ($idColumns as $idColumn) { if ($first) $first = false; else $sql .= ' AND '; + $sql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } } @@ -1330,6 +1458,7 @@ class SqlWalker implements TreeWalker $primaryClass = $this->_em->getClassMetadata( $AST->getDeleteClause()->getAbstractSchemaName() ); + if ($primaryClass->isInheritanceTypeJoined()) { return new Exec\MultiTableDeleteExecutor($AST, $this); } else { @@ -1339,6 +1468,7 @@ class SqlWalker implements TreeWalker $primaryClass = $this->_em->getClassMetadata( $AST->getUpdateClause()->getAbstractSchemaName() ); + if ($primaryClass->isInheritanceTypeJoined()) { return new Exec\MultiTableUpdateExecutor($AST, $this); } else { diff --git a/lib/Doctrine/ORM/Query/TreeWalker.php b/lib/Doctrine/ORM/Query/TreeWalker.php index 8773ffdb6..51c32e50f 100644 --- a/lib/Doctrine/ORM/Query/TreeWalker.php +++ b/lib/Doctrine/ORM/Query/TreeWalker.php @@ -249,6 +249,14 @@ interface TreeWalker * @return string The SQL. */ function walkCollectionMemberExpression($collMemberExpr); + + /** + * Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL. + * + * @param EmptyCollectionComparisonExpression + * @return string The SQL. + */ + function walkEmptyCollectionComparisonExpression($emptyCollCompExpr); /** * Walks down a NullComparisonExpression AST node, thereby generating the appropriate SQL. diff --git a/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php b/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php index 940225645..54e165c1b 100644 --- a/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php +++ b/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php @@ -247,6 +247,14 @@ abstract class TreeWalkerAdapter implements TreeWalker */ public function walkCollectionMemberExpression($collMemberExpr) {} + /** + * Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL. + * + * @param EmptyCollectionComparisonExpression + * @return string The SQL. + */ + public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr) {} + /** * Walks down a NullComparisonExpression AST node, thereby generating the appropriate SQL. *