Commit aacb2795 authored by doctrine's avatar doctrine

DQL: Preliminary support for HAVING + GROUP BY, New component Doctrine_Cache_Query_Sqlite

parent 1e0d6757
...@@ -245,6 +245,7 @@ final class Doctrine { ...@@ -245,6 +245,7 @@ final class Doctrine {
case "DQL": case "DQL":
case "Sensei": case "Sensei":
case "Iterator": case "Iterator":
case "View":
$a[] = $path.DIRECTORY_SEPARATOR.$entry; $a[] = $path.DIRECTORY_SEPARATOR.$entry;
break; break;
default: default:
......
<?php
class Doctrine_Cache_Query_Sqlite implements Countable {
/**
* doctrine cache
*/
const CACHE_TABLE = 'doctrine_query_cache';
/**
* @var Doctrine_Table $table the table object this cache container belongs to
*/
private $table;
/**
* @var PDO $dbh database handler
*/
private $dbh;
/**
* @var array $fetched an array of fetched primary keys
*/
private $fetched = array();
/**
* constructor
*
* Doctrine_Table $table
*/
public function __construct(Doctrine_Table $table) {
$this->table = $table;
$dir = $this->table->getSession()->getAttribute(Doctrine::ATTR_CACHE_DIR);
if( ! is_dir($dir))
mkdir($dir, 0777);
$this->path = $dir.DIRECTORY_SEPARATOR;
$this->dbh = $this->table->getSession()->getCacheHandler();
$this->dbh = new PDO("sqlite::memory:");
try {
if($this->table->getAttribute(Doctrine::ATTR_CREATE_TABLES) === true)
{
$columns = array();
$columns['query_md5'] = array('string', 32, 'notnull');
$columns['query_result'] = array('array', 100000, 'notnull');
$columns['expires'] = array('integer', 11, 'notnull');
$dataDict = new Doctrine_DataDict($this->dbh);
$dataDict->createTable(self::CACHE_TABLE, $columns);
}
} catch(PDOException $e) {
}
}
/**
* store
* stores a query in cache
*
* @param string $query
* @param array $result
* @param integer $lifespan
* @return void
*/
public function store($query, array $result, $lifespan) {
$sql = "INSERT INTO ".self::CACHE_TABLE." (query_md5, query_result, expires) VALUES (?,?,?)";
$stmt = $this->dbh->prepare($sql);
$params = array(md5($query), serialize($result), (time() + $lifespan));
$stmt->execute($params);
}
/**
* fetch
*
* @param string $md5
* @return array
*/
public function fetch($md5) {
$sql = "SELECT query_result, expires FROM ".self::CACHE_TABLE." WHERE query_md5 = ?";
$stmt = $this->dbh->prepare($sql);
$params = array($md5);
$stmt->execute($params);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return unserialize($result['query_result']);
}
/**
* deleteAll
* returns the number of deleted rows
*
* @return integer
*/
public function deleteAll() {
$sql = "DELETE FROM ".self::CACHE_TABLE;
$stmt = $this->dbh->query($sql);
return $stmt->rowCount();
}
/**
* delete
* returns whether or not the given
* query was succesfully deleted
*
* @param string $md5
* @return boolean
*/
public function delete($md5) {
$sql = "DELETE FROM ".self::CACHE_TABLE." WHERE query_md5 = ?";
$stmt = $this->dbh->prepare($sql);
$params = array($md5);
$stmt->execute($params);
return $stmt->rowCount();
}
/**
* count
*
* @return integer
*/
public function count() {
$stmt = $this->dbh->query("SELECT COUNT(*) FROM ".self::CACHE_TABLE);
$data = $stmt->fetch(PDO::FETCH_NUM);
// table has three columns so we have to divide the count by two
return $data[0];
}
}
?>
...@@ -378,6 +378,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator ...@@ -378,6 +378,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
/** /**
* count * count
* this class implements interface countable * this class implements interface countable
*
* @return integer number of records in this collection * @return integer number of records in this collection
*/ */
public function count() { public function count() {
......
...@@ -62,7 +62,7 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -62,7 +62,7 @@ class Doctrine_Query extends Doctrine_Access {
"from" => array(), "from" => array(),
"join" => array(), "join" => array(),
"where" => array(), "where" => array(),
"group" => array(), "groupby" => array(),
"having" => array(), "having" => array(),
"orderby" => array(), "orderby" => array(),
"limit" => false, "limit" => false,
...@@ -76,7 +76,7 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -76,7 +76,7 @@ class Doctrine_Query extends Doctrine_Access {
"from" => array(), "from" => array(),
"join" => array(), "join" => array(),
"where" => array(), "where" => array(),
"group" => array(), "groupby" => array(),
"having" => array(), "having" => array(),
"orderby" => array(), "orderby" => array(),
"limit" => false, "limit" => false,
...@@ -131,7 +131,7 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -131,7 +131,7 @@ class Doctrine_Query extends Doctrine_Access {
"from" => array(), "from" => array(),
"join" => array(), "join" => array(),
"where" => array(), "where" => array(),
"group" => array(), "groupby" => array(),
"having" => array(), "having" => array(),
"orderby" => array(), "orderby" => array(),
"limit" => false, "limit" => false,
...@@ -207,6 +207,7 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -207,6 +207,7 @@ class Doctrine_Query extends Doctrine_Access {
$method = "parse".ucwords($name); $method = "parse".ucwords($name);
switch($name): switch($name):
case "where": case "where":
case "having":
$this->parts[$name] = array($this->$method($args[0])); $this->parts[$name] = array($this->$method($args[0]));
break; break;
case "limit": case "limit":
...@@ -226,7 +227,8 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -226,7 +227,8 @@ class Doctrine_Query extends Doctrine_Access {
$this->parts[$name] = array(); $this->parts[$name] = array();
$this->$method($args[0]); $this->$method($args[0]);
endswitch; endswitch;
} } else
throw new Doctrine_Query_Exception("Unknown overload method");
return $this; return $this;
} }
...@@ -255,6 +257,7 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -255,6 +257,7 @@ class Doctrine_Query extends Doctrine_Access {
$method = "parse".ucwords($name); $method = "parse".ucwords($name);
switch($name): switch($name):
case "where": case "where":
case "having":
$this->parts[$name] = array($this->$method($value)); $this->parts[$name] = array($this->$method($value));
break; break;
case "limit": case "limit":
...@@ -307,6 +310,12 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -307,6 +310,12 @@ class Doctrine_Query extends Doctrine_Access {
if( ! empty($this->parts["where"])) if( ! empty($this->parts["where"]))
$q .= " WHERE ".implode(" ",$this->parts["where"]); $q .= " WHERE ".implode(" ",$this->parts["where"]);
if( ! empty($this->parts["groupby"]))
$q .= " GROUP BY ".implode(", ",$this->parts["groupby"]);
if( ! empty($this->parts["having"]))
$q .= " HAVING ".implode(" ",$this->parts["having"]);
if( ! empty($this->parts["orderby"])) if( ! empty($this->parts["orderby"]))
$q .= " ORDER BY ".implode(", ",$this->parts["orderby"]); $q .= " ORDER BY ".implode(", ",$this->parts["orderby"]);
...@@ -339,6 +348,8 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -339,6 +348,8 @@ class Doctrine_Query extends Doctrine_Access {
if( ! empty($this->parts["where"])) if( ! empty($this->parts["where"]))
$q .= " WHERE ".implode(" ",$this->parts["where"]); $q .= " WHERE ".implode(" ",$this->parts["where"]);
if( ! empty($this->parts["orderby"])) if( ! empty($this->parts["orderby"]))
$q .= " ORDER BY ".implode(", ",$this->parts["orderby"]); $q .= " ORDER BY ".implode(", ",$this->parts["orderby"]);
...@@ -403,6 +414,21 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -403,6 +414,21 @@ class Doctrine_Query extends Doctrine_Access {
} }
return true; return true;
} }
/**
* @param string $having
* @return boolean
*/
final public function addHaving($having) {
if(empty($having))
return false;
if($this->parts["having"]) {
$this->parts["having"][] = "AND (".$having.")";
} else {
$this->parts["having"][] = "(".$having.")";
}
return true;
}
/** /**
* getData * getData
* @param $key the component name * @param $key the component name
...@@ -435,11 +461,8 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -435,11 +461,8 @@ class Doctrine_Query extends Doctrine_Access {
throw new DQLException(); throw new DQLException();
break; break;
case 1: case 1:
$keys = array_keys($this->tables); $keys = array_keys($this->tables);
$name = $this->tables[$keys[0]]->getComponentName(); $name = $this->tables[$keys[0]]->getComponentName();
$stmt = $this->session->execute($query,$params); $stmt = $this->session->execute($query,$params);
...@@ -474,7 +497,6 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -474,7 +497,6 @@ class Doctrine_Query extends Doctrine_Access {
$colls = array(); $colls = array();
foreach($array as $data) { foreach($array as $data) {
/** /**
* remove duplicated data rows and map data into objects * remove duplicated data rows and map data into objects
...@@ -785,6 +807,23 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -785,6 +807,23 @@ class Doctrine_Query extends Doctrine_Access {
} }
} }
/**
* DQL GROUP BY PARSER
* parses the group by part of the query string
* @param string $str
* @return void
*/
private function parseGroupBy($str) {
foreach(explode(",", $str) as $reference) {
$reference = trim($reference);
$e = explode(".",$reference);
$field = array_pop($e);
$table = $this->load(implode(".",$e));
$component = $table->getComponentName();
$this->parts["groupby"][] = $this->tableAliases[$component].".".$field;
}
}
/** /**
* DQL FROM PARSER * DQL FROM PARSER
* parses the from part of the query string * parses the from part of the query string
...@@ -793,8 +832,10 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -793,8 +832,10 @@ class Doctrine_Query extends Doctrine_Access {
* @return void * @return void
*/ */
private function parseFrom($str) { private function parseFrom($str) {
foreach(self::bracketExplode(trim($str),",","(",")") as $reference) { foreach(self::bracketExplode(trim($str),",", "(",")") as $reference) {
$reference = trim($reference); $reference = trim($reference);
$a = explode(".",$reference);
$field = array_pop($a);
$table = $this->load($reference); $table = $this->load($reference);
} }
} }
...@@ -831,18 +872,21 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -831,18 +872,21 @@ class Doctrine_Query extends Doctrine_Access {
return $fetchmode; return $fetchmode;
} }
/** /**
* DQL WHERE PARSER * DQL CONDITION PARSER
* parses the where part of the query string * parses the where/having part of the query string
* *
* *
* @param string $str * @param string $str
* @return string * @return string
*/ */
private function parseWhere($str) { private function parseCondition($str, $type = 'Where') {
$tmp = trim($str); $tmp = trim($str);
$str = self::bracketTrim($tmp,"(",")"); $str = self::bracketTrim($tmp,"(",")");
$brackets = false; $brackets = false;
$loadMethod = "load".$type;
while($tmp != $str) { while($tmp != $str) {
$brackets = true; $brackets = true;
$tmp = $str; $tmp = $str;
...@@ -853,7 +897,7 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -853,7 +897,7 @@ class Doctrine_Query extends Doctrine_Access {
if(count($parts) > 1) { if(count($parts) > 1) {
$ret = array(); $ret = array();
foreach($parts as $part) { foreach($parts as $part) {
$ret[] = $this->parseWhere($part); $ret[] = $this->parseCondition($part, $type);
} }
$r = implode(" AND ",$ret); $r = implode(" AND ",$ret);
} else { } else {
...@@ -861,11 +905,11 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -861,11 +905,11 @@ class Doctrine_Query extends Doctrine_Access {
if(count($parts) > 1) { if(count($parts) > 1) {
$ret = array(); $ret = array();
foreach($parts as $part) { foreach($parts as $part) {
$ret[] = $this->parseWhere($part); $ret[] = $this->parseCondition($part, $type);
} }
$r = implode(" OR ",$ret); $r = implode(" OR ",$ret);
} else { } else {
return $this->loadWhere($parts[0]); return $this->$loadMethod($parts[0]);
} }
} }
if($brackets) if($brackets)
...@@ -873,6 +917,28 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -873,6 +917,28 @@ class Doctrine_Query extends Doctrine_Access {
else else
return $r; return $r;
} }
/**
* DQL WHERE PARSER
* parses the where part of the query string
*
*
* @param string $str
* @return string
*/
private function parseWhere($str) {
return $this->parseCondition($str,'Where');
}
/**
* DQL HAVING PARSER
* parses the having part of the query string
*
*
* @param string $str
* @return string
*/
private function parseHaving($str) {
return $this->parseCondition($str,'Having');
}
/** /**
* trims brackets * trims brackets
* *
...@@ -919,6 +985,66 @@ class Doctrine_Query extends Doctrine_Access { ...@@ -919,6 +985,66 @@ class Doctrine_Query extends Doctrine_Access {
} }
return $term; return $term;
} }
/**
* DQL Aggregate Function parser
*
* @param string $func
* @return mixed
*/
private function parseAggregateFunction($func) {
$pos = strpos($func,"(");
if($pos !== false) {
$funcs = array();
$name = substr($func, 0, $pos);
$func = substr($func, ($pos + 1), -1);
$params = self::bracketExplode($func, ",", "(", ")");
foreach($params as $k => $param) {
$params[$k] = $this->parseAggregateFunction($param);
}
$funcs = $name."(".implode(", ", $params).")";
return $funcs;
} else {
if( ! is_numeric($func)) {
$a = explode(".",$func);
$field = array_pop($a);
$reference = implode(".",$a);
$table = $this->load($reference, false);
$component = $table->getComponentName();
$func = $this->tableAliases[$component].".".$field;
return $func;
} else {
return $func;
}
}
}
/**
* loadHaving
*
* @param string $having
*/
private function loadHaving($having) {
$e = self::bracketExplode($having," ","(",")");
$r = array_shift($e);
$t = explode("(",$r);
$count = count($t);
$r = $this->parseAggregateFunction($r);
$operator = array_shift($e);
$value = implode(" ",$e);
$r .= " ".$operator." ".$value;
return $r;
}
/** /**
* loadWhere * loadWhere
* *
......
<?php <?php
require_once(Doctrine::getPath().DIRECTORY_SEPARATOR."Exception.class.php"); require_once(Doctrine::getPath().DIRECTORY_SEPARATOR."Doctrine".DIRECTORY_SEPARATOR."Exception.php");
class Doctrine_Query_Exception extends Doctrine_Exception { } class Doctrine_Query_Exception extends Doctrine_Exception { }
?> ?>
...@@ -75,7 +75,11 @@ class Doctrine_View { ...@@ -75,7 +75,11 @@ class Doctrine_View {
*/ */
public function create() { public function create() {
$sql = sprintf(self::CREATE, $this->name, $this->query->getQuery()); $sql = sprintf(self::CREATE, $this->name, $this->query->getQuery());
try {
$this->dbh->query($sql); $this->dbh->query($sql);
} catch(Exception $e) {
throw new Doctrine_View_Exception($e->__toString());
}
} }
/** /**
* drops this view * drops this view
...@@ -83,7 +87,11 @@ class Doctrine_View { ...@@ -83,7 +87,11 @@ class Doctrine_View {
* @return void * @return void
*/ */
public function drop() { public function drop() {
try {
$this->dbh->query(sprintf(self::DROP, $this->name)); $this->dbh->query(sprintf(self::DROP, $this->name));
} catch(Exception $e) {
throw new Doctrine_View_Exception($e->__toString());
}
} }
/** /**
* executes the view * executes the view
......
<?php
class Doctrine_View_Exception extends Doctrine_Exception { }
?>
...@@ -13,8 +13,28 @@ class Doctrine_Cache_Query_SqliteTestCase extends Doctrine_UnitTestCase { ...@@ -13,8 +13,28 @@ class Doctrine_Cache_Query_SqliteTestCase extends Doctrine_UnitTestCase {
$this->cache = new Doctrine_Cache_Query_Sqlite($this->objTable); $this->cache = new Doctrine_Cache_Query_Sqlite($this->objTable);
$this->cache->deleteAll(); $this->cache->deleteAll();
} }
public function testConstructor() { public function testStore() {
$this->cache->store("SELECT * FROM user", array(array('name' => 'Jack Daniels')), 60);
$this->assertEqual($this->cache->count(), 1);
$this->cache->store("SELECT * FROM group", array(array('name' => 'Drinkers club')), 60);
$md5 = md5("SELECT * FROM user");
$result = $this->cache->fetch($md5);
$this->assertEqual($result, array(array('name' => 'Jack Daniels')));
$md5 = md5("SELECT * FROM group");
$result = $this->cache->fetch($md5);
$this->assertEqual($result, array(array('name' => 'Drinkers club')));
$this->assertEqual($this->cache->count(), 2);
$this->cache->delete($md5);
$this->assertEqual($this->cache->count(), 1);
$this->cache->deleteAll();
$this->assertEqual($this->cache->count(), 0);
} }
} }
?> ?>
...@@ -15,6 +15,44 @@ class Doctrine_QueryTestCase extends Doctrine_UnitTestCase { ...@@ -15,6 +15,44 @@ class Doctrine_QueryTestCase extends Doctrine_UnitTestCase {
parent::prepareTables(); parent::prepareTables();
} }
public function testHaving() {
$this->session->clear();
$query = new Doctrine_Query($this->session);
$query->from('User-l.Phonenumber-l');
$query->having("COUNT(User-l.Phonenumber-l.phonenumber) > 2");
$query->groupby('User.id');
$users = $query->execute();
$this->assertEqual($users->count(), 3);
// test that users are in right order
$this->assertEqual($users[0]->id, 5);
$this->assertEqual($users[1]->id, 8);
$this->assertEqual($users[2]->id, 10);
// test expanding
$count = $this->dbh->count();
$this->assertEqual($users[0]->Phonenumber->count(), 1);
$this->assertEqual($users[1]->Phonenumber->count(), 1);
$this->assertEqual($users[2]->Phonenumber->count(), 1);
$users[0]->Phonenumber[1];
$this->assertEqual(++$count, $this->dbh->count());
$this->assertEqual($users[0]->Phonenumber->count(), 3);
$users[1]->Phonenumber[1];
$this->assertEqual(++$count, $this->dbh->count());
$this->assertEqual($users[1]->Phonenumber->count(), 3);
$users[2]->Phonenumber[1];
$this->assertEqual(++$count, $this->dbh->count());
$this->assertEqual($users[2]->Phonenumber->count(), 3);
}
public function testManyToManyFetchingWithColumnAggregationInheritance() { public function testManyToManyFetchingWithColumnAggregationInheritance() {
$query = new Doctrine_Query($this->session); $query = new Doctrine_Query($this->session);
......
...@@ -48,11 +48,12 @@ $test->addTestCase(new Doctrine_CollectionTestCase()); ...@@ -48,11 +48,12 @@ $test->addTestCase(new Doctrine_CollectionTestCase());
$test->addTestCase(new Doctrine_PessimisticLockingTestCase()); $test->addTestCase(new Doctrine_PessimisticLockingTestCase());
$test->addTestCase(new Doctrine_ViewTestCase());
$test->addTestCase(new Doctrine_QueryTestCase()); $test->addTestCase(new Doctrine_QueryTestCase());
$test->addTestCase(new Doctrine_ViewTestCase()); $test->addTestCase(new Doctrine_Cache_Query_SqliteTestCase());
//$test->addTestCase(new Doctrine_Cache_Query_SqliteTestCase());
//$test->addTestCase(new Doctrine_Cache_FileTestCase()); //$test->addTestCase(new Doctrine_Cache_FileTestCase());
//$test->addTestCase(new Doctrine_Cache_SqliteTestCase()); //$test->addTestCase(new Doctrine_Cache_SqliteTestCase());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment