From 8b4bc0f41cf2e5e696b697f831b141910368eb17 Mon Sep 17 00:00:00 2001 From: zYne Date: Wed, 11 Oct 2006 20:44:21 +0000 Subject: [PATCH] Fixes #159, #160, added EXISTS expression support as well as correlated subquery support --- lib/Doctrine/Hydrate.php | 53 +++++++++++++++++++++++++- lib/Doctrine/Query.php | 6 ++- lib/Doctrine/Query/Where.php | 48 ++++++++++++++++++++---- tests/QueryWhereTestCase.php | 72 +++++++++++++++++++++++++++++++++--- tests/run.php | 16 ++++---- 5 files changed, 172 insertions(+), 23 deletions(-) diff --git a/lib/Doctrine/Hydrate.php b/lib/Doctrine/Hydrate.php index 52494423c..a713bf8be 100644 --- a/lib/Doctrine/Hydrate.php +++ b/lib/Doctrine/Hydrate.php @@ -109,11 +109,54 @@ abstract class Doctrine_Hydrate extends Doctrine_Access { } /** * getComponentAliases + * + * @return array */ public function getComponentAliases() { - return $this->compAliases; + return $this->compAliases; } + /** + * getTableAliases + * + * @return array + */ + public function getTableAliases() { + return $this->tableAliases; + } + /** + * getTableIndexes + * + * @return array + */ + public function getTableIndexes() { + return $this->tableIndexes; + } + /** + * copyAliases + * + * @return void + */ + public function copyAliases(Doctrine_Hydrate $query) { + $this->compAliases = $query->getComponentAliases(); + $this->tableAliases = $query->getTableAliases(); + $this->tableIndexes = $query->getTableIndexes(); + + return $this; + } + /** + * createSubquery + * + * @return Doctrine_Hydrate + */ + public function createSubquery() { + $class = get_class($this); + $obj = new $class(); + + // copy the aliases to the subquery + $obj->copyAliases($this); + return $obj; + } /** * getQuery * @@ -204,7 +247,7 @@ abstract class Doctrine_Hydrate extends Doctrine_Access { * @param string $path * @return string */ - final public function getTableAlias($path) { + final public function getTableAlias($path) { if(isset($this->compAliases[$path])) $path = $this->compAliases[$path]; @@ -524,5 +567,11 @@ abstract class Doctrine_Hydrate extends Doctrine_Access { public function getTable($name) { return $this->tables[$name]; } + /** + * @return string returns a string representation of this object + */ + public function __toString() { + return Doctrine_Lib::formatSql($this->getQuery()); + } } diff --git a/lib/Doctrine/Query.php b/lib/Doctrine/Query.php index 78e70a98a..0cf25638d 100644 --- a/lib/Doctrine/Query.php +++ b/lib/Doctrine/Query.php @@ -424,11 +424,13 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { * parsers for each part * * @param string $query DQL query + * @param boolean $clear whether or not to clear the aliases * @throws Doctrine_Query_Exception if some generic parsing error occurs * @return Doctrine_Query */ - public function parseQuery($query) { - $this->clear(); + public function parseQuery($query, $clear = true) { + if($clear) + $this->clear(); $query = trim($query); $query = str_replace("\n"," ",$query); diff --git a/lib/Doctrine/Query/Where.php b/lib/Doctrine/Query/Where.php index f03f71a6a..6494b47cc 100644 --- a/lib/Doctrine/Query/Where.php +++ b/lib/Doctrine/Query/Where.php @@ -10,9 +10,19 @@ class Doctrine_Query_Where extends Doctrine_Query_Condition { * @return string */ public function load($where) { + $where = trim($where); + + $e = Doctrine_Query::sqlExplode($where); + + if(count($e) > 1) { + $tmp = $e[0].' '.$e[1]; + + if(substr($tmp, 0, 6) == 'EXISTS') + return $this->parseExists($where, true); + elseif(substr($where, 0, 10) == 'NOT EXISTS') + return $this->parseExists($where, false); + } - $e = Doctrine_Query::sqlExplode($where); - if(count($e) < 3) { $e = Doctrine_Query::sqlExplode($where, array('=', '<', '>', '!=')); } @@ -84,9 +94,24 @@ class Doctrine_Query_Where extends Doctrine_Query_Condition { elseif($value == 'false') $value = 0; elseif(substr($value,0,5) == '(FROM') { + // subquery $sub = Doctrine_Query::bracketTrim($value); $q = new Doctrine_Query(); $value = '(' . $q->parseQuery($sub)->getQuery() . ')'; + } else { + // check that value isn't a string + if(strpos($value, '\'') === false) { + $a = explode('.', $value); + + if(count($a) > 1) { + // either a float or a component.. + + if( ! is_numeric($a[0])) { + // a component found + $value = $this->query->getTableAlias($a[0]). '.' . $a[1]; + } + } + } } switch($operator) { @@ -95,18 +120,27 @@ class Doctrine_Query_Where extends Doctrine_Query_Condition { case '=': if($enumIndex !== false) $value = $enumIndex; - - $where = $alias.'.'.$field.' '.$operator.' '.$value; - break; default: - $where = $this->query->getTableAlias($reference).'.'.$field.' '.$operator.' '.$value; + $where = $alias.'.'.$field.' '.$operator.' '.$value; } } } - return $where; } + public function parseExists($where, $negation) { + $operator = ($negation) ? 'EXISTS' : 'NOT EXISTS'; + + $pos = strpos($where, '('); + + if($pos == false) + throw new Doctrine_Query_Exception("Unknown expression, expected '('"); + + $sub = Doctrine_Query::bracketTrim(substr($where, $pos)); + + return $operator . ' ('.$this->query->createSubquery()->parseQuery($sub, false)->getQuery() . ')'; + } + public function getOperator($func) { switch($func) { case 'contains': diff --git a/tests/QueryWhereTestCase.php b/tests/QueryWhereTestCase.php index c4ad36a01..df62e0c43 100644 --- a/tests/QueryWhereTestCase.php +++ b/tests/QueryWhereTestCase.php @@ -29,7 +29,7 @@ class Doctrine_Query_Where_TestCase extends Doctrine_UnitTestCase { $q = new Doctrine_Query(); - $q->from('User(id)')->addWhere('User.id IN (?, ?)',array(1,2)); + $q->from('User(id)')->addWhere('User.id IN (?, ?)', array(1,2)); $users = $q->execute(); @@ -37,17 +37,78 @@ class Doctrine_Query_Where_TestCase extends Doctrine_UnitTestCase { $this->assertEqual($users[0]->name, 'someone'); $this->assertEqual($users[1]->name, 'someone.2'); } + + public function testNotInExpression() { + $q = new Doctrine_Query(); + + $q->from('User u')->addWhere('u.id NOT IN (?)', array(1)); + $users = $q->execute(); + + $this->assertEqual($users->count(), 1); + $this->assertEqual($users[0]->name, 'someone.2'); + } + public function testExistsExpression() { + $q = new Doctrine_Query(); + + $user = new User(); + $user->name = 'someone with a group'; + $user->Group[0]->name = 'some group'; + $user->save(); + + // find all users which have groups + try { + $q->from('User u')->where('EXISTS (FROM Groupuser(id) WHERE Groupuser.user_id = u.id)'); + $this->pass(); + } catch(Doctrine_Exception $e) { + $this->fail(); + } + $users = $q->execute(); + $this->assertEqual($users->count(), 1); + $this->assertEqual($users[0]->name, 'someone with a group'); + } + + public function testNotExistsExpression() { + $q = new Doctrine_Query(); + + // find all users which don't have groups + try { + $q->from('User u')->where('NOT EXISTS (FROM Groupuser(id) WHERE Groupuser.user_id = u.id)'); + $this->pass(); + } catch(Doctrine_Exception $e) { + $this->fail(); + } + $users = $q->execute(); + $this->assertEqual($users->count(), 2); + $this->assertEqual($users[0]->name, 'someone'); + $this->assertEqual($users[1]->name, 'someone.2'); + } public function testComponentAliases() { $q = new Doctrine_Query(); - $q = new Doctrine_Query(); - - $q->from('User(id) u')->addWhere('u.id IN (?, ?)',array(1,2)); + $q->from('User(id) u')->addWhere('u.id IN (?, ?)', array(1,2)); $users = $q->execute(); $this->assertEqual($users->count(), 2); $this->assertEqual($users[0]->name, 'someone'); - $this->assertEqual($users[1]->name, 'someone.2'); + $this->assertEqual($users[1]->name, 'someone.2'); + + } + public function testComponentAliases2() { + $q = new Doctrine_Query(); + + $q->from('User u')->addWhere('u.name = ?', array('someone')); + + $users = $q->execute(); + + $this->assertEqual($users->count(), 1); + $this->assertEqual($users[0]->name, 'someone'); + } + public function testComponentAliases3() { + + $users = $this->connection->query("FROM User u WHERE u.name = ?", array('someone')); + + $this->assertEqual($users->count(), 1); + $this->assertEqual($users[0]->name, 'someone'); } public function testOperatorWithNoTrailingSpaces() { $q = new Doctrine_Query(); @@ -89,5 +150,6 @@ class Doctrine_Query_Where_TestCase extends Doctrine_UnitTestCase { $this->assertEqual($q->getQuery(), "SELECT entity.id AS entity__id FROM entity WHERE entity.name = 'foo.bar' AND (entity.type = 0)"); } + } ?> diff --git a/tests/run.php b/tests/run.php index 6fbc2c877..3a8ddfdae 100644 --- a/tests/run.php +++ b/tests/run.php @@ -110,21 +110,23 @@ $test->addTestCase(new Doctrine_RelationAccessTestCase()); $test->addTestCase(new Doctrine_CustomResultSetOrderTestCase()); -$test->addTestCase(new Doctrine_QueryTestCase()); - -$test->addTestCase(new Doctrine_Query_Where_TestCase()); - -$test->addTestCase(new Doctrine_Query_Condition_TestCase()); - $test->addTestCase(new Doctrine_BooleanTestCase()); $test->addTestCase(new Doctrine_EnumTestCase()); +$test->addTestCase(new Doctrine_Record_Filter_TestCase()); + +$test->addTestCase(new Doctrine_Query_Condition_TestCase()); + $test->addTestCase(new Doctrine_Query_ComponentAlias_TestCase()); $test->addTestCase(new Doctrine_Query_Subquery_TestCase()); -$test->addTestCase(new Doctrine_Record_Filter_TestCase()); +$test->addTestCase(new Doctrine_QueryTestCase()); + +$test->addTestCase(new Doctrine_Query_Where_TestCase()); + + //$test->addTestCase(new Doctrine_Cache_FileTestCase()); //$test->addTestCase(new Doctrine_Cache_SqliteTestCase());