Index: tests/Ticket/DC910TestCase.php =================================================================== --- tests/Ticket/DC910TestCase.php (revision 0) +++ tests/Ticket/DC910TestCase.php (revision 0) @@ -0,0 +1,58 @@ +. + */ + +/** + * Doctrine_Ticket_1488_TestCase + * + * @package Doctrine + * @author Will Ferrer + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @category Object Relational Mapping + * @link www.doctrine-project.org + * @since 1.0 + * @version $Revision$ + */ +class Doctrine_Ticket_DC910_TestCase extends Doctrine_UnitTestCase +{ + public function test2SubqueriesInJoin() + { + $q = Doctrine_Query::create() + ->from('T1488_Class1 c1') + ->leftJoin('c1.Classes2 c2 WITH c2.value BETWEEN (SELECT c3.min FROM T1488_Class1 c3) AND (SELECT c4.max FROM T1488_Class1 c4)'); + $this->assertEqual($q->getSqlQuery(), 'SELECT t.id AS t__id, t.min AS t__min, t.max AS t__max, t2.id AS t2__id, t2.value AS t2__value FROM t1488__class1 t LEFT JOIN t1488__relation t3 ON (t.id = t3.c1_id) LEFT JOIN t1488__class2 t2 ON t2.id = t3.c2_id AND (t2.value BETWEEN (SELECT t4.min AS t4__min FROM t1488__class1 t4) AND (SELECT t5.max AS t5__max FROM t1488__class1 t5))'); + } + + public function test2SQLSubqueriesInJoin() + { + $q = Doctrine_Query::create() + ->from('T1488_Class1 c1') + ->leftJoin('c1.Classes2 c2 WITH c2.value BETWEEN (SQL:SELECT t4.min AS t4__min FROM t1488__class1 t4) AND (SQL:SELECT t5.max AS t5__max FROM t1488__class1 t5)'); + $this->assertEqual($q->getSqlQuery(), 'SELECT t.id AS t__id, t.min AS t__min, t.max AS t__max, t2.id AS t2__id, t2.value AS t2__value FROM t1488__class1 t LEFT JOIN t1488__relation t3 ON (t.id = t3.c1_id) LEFT JOIN t1488__class2 t2 ON t2.id = t3.c2_id AND (t2.value BETWEEN (SELECT t4.min AS t4__min FROM t1488__class1 t4) AND (SELECT t5.max AS t5__max FROM t1488__class1 t5))'); + } + + public function testNotInJoin() + { + $q = Doctrine_Query::create() + ->from('T1488_Class1 c1') + ->leftJoin('c1.Classes2 c2 WITH c2.value NOT IN (1, 2, 3)'); + $this->assertEqual($q->getSqlQuery(), 'SELECT t.id AS t__id, t.min AS t__min, t.max AS t__max, t2.id AS t2__id, t2.value AS t2__value FROM t1488__class1 t LEFT JOIN t1488__relation t3 ON (t.id = t3.c1_id) LEFT JOIN t1488__class2 t2 ON t2.id = t3.c2_id AND (t2.value NOT IN (1, 2, 3))'); + } +} Index: lib/Doctrine/Query/JoinCondition.php =================================================================== --- lib/Doctrine/Query/JoinCondition.php (revision 7690) +++ lib/Doctrine/Query/JoinCondition.php (working copy) @@ -32,6 +32,12 @@ */ class Doctrine_Query_JoinCondition extends Doctrine_Query_Condition { + /** + * Parses join conditions found in the dql. + * + * @param string $condition dql join condition + * @return string + */ public function load($condition) { $condition = trim($condition); @@ -47,91 +53,100 @@ if (($l = count($e)) > 2) { $leftExpr = $this->query->parseClause($e[0]); $operator = $e[1]; - + //Fix for: http://www.doctrine-project.org/jira/browse/DC-910 if ($l == 4) { // FIX: "field NOT IN (XXX)" issue // Related to ticket #1329 $operator .= ' ' . $e[2]; // Glue "NOT" and "IN" $e[2] = $e[3]; // Move "(XXX)" to previous index - - unset($e[3]); // Remove unused index + return $leftExpr . ' ' . $operator . ' ' . $this->parseConditionPiece($e[2]); + //unset($e[3]); // Remove unused index } else if ($l >= 5) { // FIX: "field BETWEEN field2 AND field3" issue // Related to ticket #1488 - $e[2] .= ' ' . $e[3] . ' ' . $e[4]; - - unset($e[3], $e[4]); // Remove unused indexes - } + return $leftExpr . ' ' . $operator . ' ' . $this->parseConditionPiece($e[2]) . ' ' . $e[3] . ' ' . $this->parseConditionPiece($e[4]); - if (substr(trim($e[2]), 0, 1) != '(') { - $expr = new Doctrine_Expression($e[2], $this->query->getConnection()); - $e[2] = $expr->getSql(); + //unset($e[3], $e[4]); // Remove unused indexes + } else { + return $leftExpr . ' ' . $operator . ' ' . $this->parseConditionPiece($e[2]); } - // We need to check for agg functions here - $rightMatches = array(); - $hasRightAggExpression = $this->_processPossibleAggExpression($e[2], $rightMatches); - - // Defining needed information - $value = $e[2]; - - if (substr($value, 0, 1) == '(') { - // trim brackets - $trimmed = $this->_tokenizer->bracketTrim($value); - $trimmed_upper = strtoupper($trimmed); - - if (substr($trimmed_upper, 0, 4) == 'FROM' || substr($trimmed_upper, 0, 6) == 'SELECT') { - // subquery found - $q = $this->query->createSubquery() - ->parseDqlQuery($trimmed, false); - $value = '(' . $q->getSqlQuery() . ')'; - $q->free(); - } elseif (substr($trimmed_upper, 0, 4) == 'SQL:') { - // Change due to bug "(" XXX ")" - //$value = '(' . substr($trimmed, 4) . ')'; - $value = substr($trimmed, 4); - } else { - // simple in expression found - $e = $this->_tokenizer->sqlExplode($trimmed, ','); - $value = array(); + } - foreach ($e as $part) { - $value[] = $this->parseLiteralValue($part); - } + $parser = new Doctrine_Query_Where($this->query, $this->_tokenizer); - $value = '(' . implode(', ', $value) . ')'; - } - } elseif ( ! $hasRightAggExpression) { - // Possible expression found (field1 AND field2) - // In relation to ticket #1488 - $e = $this->_tokenizer->bracketExplode($value, array(' AND ', ' \&\& '), '(', ')'); + return $parser->parse($condition); + } + //Fix for: http://www.doctrine-project.org/jira/browse/DC-910 + /** + * Parses the indevidual pieces of the on clause of the join condition. + * @param string $value + * @return string + */ + protected function parseConditionPiece($value) { + if (substr(trim($value), 0, 1) != '(') { + $expr = new Doctrine_Expression($value, $this->query->getConnection()); + $value = $expr->getSql(); + } + // We need to check for agg functions here + $rightMatches = array(); + $hasRightAggExpression = $this->_processPossibleAggExpression($value, $rightMatches); + + if (substr($value, 0, 1) == '(') { + // trim brackets + $trimmed = $this->_tokenizer->bracketTrim($value); + $trimmed_upper = strtoupper($trimmed); + if (substr($trimmed_upper, 0, 4) == 'FROM' || substr($trimmed_upper, 0, 6) == 'SELECT') { + // subquery found + //Fix for: http://www.doctrine-project.org/jira/browse/DC-910 + $q = $this->query->createSubquery() + ->parseDqlQuery($trimmed, false); + $value = '(' . $q->getSqlQuery() . ')'; + $q->free(); + } elseif (substr($trimmed_upper, 0, 4) == 'SQL:') { + // Change due to bug "(" XXX ")" + $value = '(' . substr($trimmed, 4) . ')'; + //$value = substr($trimmed, 4); + } else { + // simple in expression found + $e = $this->_tokenizer->sqlExplode($trimmed, ','); $value = array(); - + foreach ($e as $part) { $value[] = $this->parseLiteralValue($part); } - - $value = implode(' AND ', $value); + + $value = '(' . implode(', ', $value) . ')'; } + } elseif ( ! $hasRightAggExpression) { + // Possible expression found (field1 AND field2) + // In relation to ticket #1488 + $e = $this->_tokenizer->bracketExplode($value, array(' AND ', ' \&\& '), '(', ')'); + $value = array(); - if ($hasRightAggExpression) { - $rightExpr = $rightMatches[1] . '(' . $value . ')' . $rightMatches[3]; - $rightExpr = $this->query->parseClause($rightExpr); - } else { - $rightExpr = $value; + foreach ($e as $part) { + $value[] = $this->parseLiteralValue($part); } - $condition = $leftExpr . ' ' . $operator . ' ' . $rightExpr; - - return $condition; + $value = implode(' AND ', $value); } - - $parser = new Doctrine_Query_Where($this->query, $this->_tokenizer); - - return $parser->parse($condition); + + if ($hasRightAggExpression) { + $rightExpr = $rightMatches[1] . '(' . $value . ')' . $rightMatches[3]; + $rightExpr = $this->query->parseClause($rightExpr); + } else { + $rightExpr = $value; + } + + $condition = $rightExpr; + return $condition; } - - + /** + * Processes the agg expressions in the piece of a join clause. + * @param string $expr -- the expresion to process + * @param string $matches the matches gathered by the regex -- used by the method that called this one + * @return boolean + */ protected function _processPossibleAggExpression(& $expr, & $matches = array()) { $hasAggExpr = preg_match('/(.*[^\s\(\=])\(([^\)]*)\)(.*)/', $expr, $matches);