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 {
case "DQL":
case "Sensei":
case "Iterator":
case "View":
$a[] = $path.DIRECTORY_SEPARATOR.$entry;
break;
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
/**
* count
* this class implements interface countable
*
* @return integer number of records in this collection
*/
public function count() {
......
......@@ -62,7 +62,7 @@ class Doctrine_Query extends Doctrine_Access {
"from" => array(),
"join" => array(),
"where" => array(),
"group" => array(),
"groupby" => array(),
"having" => array(),
"orderby" => array(),
"limit" => false,
......@@ -76,7 +76,7 @@ class Doctrine_Query extends Doctrine_Access {
"from" => array(),
"join" => array(),
"where" => array(),
"group" => array(),
"groupby" => array(),
"having" => array(),
"orderby" => array(),
"limit" => false,
......@@ -131,7 +131,7 @@ class Doctrine_Query extends Doctrine_Access {
"from" => array(),
"join" => array(),
"where" => array(),
"group" => array(),
"groupby" => array(),
"having" => array(),
"orderby" => array(),
"limit" => false,
......@@ -207,6 +207,7 @@ class Doctrine_Query extends Doctrine_Access {
$method = "parse".ucwords($name);
switch($name):
case "where":
case "having":
$this->parts[$name] = array($this->$method($args[0]));
break;
case "limit":
......@@ -226,7 +227,8 @@ class Doctrine_Query extends Doctrine_Access {
$this->parts[$name] = array();
$this->$method($args[0]);
endswitch;
}
} else
throw new Doctrine_Query_Exception("Unknown overload method");
return $this;
}
......@@ -255,6 +257,7 @@ class Doctrine_Query extends Doctrine_Access {
$method = "parse".ucwords($name);
switch($name):
case "where":
case "having":
$this->parts[$name] = array($this->$method($value));
break;
case "limit":
......@@ -307,6 +310,12 @@ class Doctrine_Query extends Doctrine_Access {
if( ! empty($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"]))
$q .= " ORDER BY ".implode(", ",$this->parts["orderby"]);
......@@ -339,6 +348,8 @@ class Doctrine_Query extends Doctrine_Access {
if( ! empty($this->parts["where"]))
$q .= " WHERE ".implode(" ",$this->parts["where"]);
if( ! empty($this->parts["orderby"]))
$q .= " ORDER BY ".implode(", ",$this->parts["orderby"]);
......@@ -403,6 +414,21 @@ class Doctrine_Query extends Doctrine_Access {
}
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
* @param $key the component name
......@@ -435,11 +461,8 @@ class Doctrine_Query extends Doctrine_Access {
throw new DQLException();
break;
case 1:
$keys = array_keys($this->tables);
$name = $this->tables[$keys[0]]->getComponentName();
$stmt = $this->session->execute($query,$params);
......@@ -474,7 +497,6 @@ class Doctrine_Query extends Doctrine_Access {
$colls = array();
foreach($array as $data) {
/**
* remove duplicated data rows and map data into objects
......@@ -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
* parses the from part of the query string
......@@ -793,8 +832,10 @@ class Doctrine_Query extends Doctrine_Access {
* @return void
*/
private function parseFrom($str) {
foreach(self::bracketExplode(trim($str),",","(",")") as $reference) {
foreach(self::bracketExplode(trim($str),",", "(",")") as $reference) {
$reference = trim($reference);
$a = explode(".",$reference);
$field = array_pop($a);
$table = $this->load($reference);
}
}
......@@ -831,18 +872,21 @@ class Doctrine_Query extends Doctrine_Access {
return $fetchmode;
}
/**
* DQL WHERE PARSER
* parses the where part of the query string
* DQL CONDITION PARSER
* parses the where/having part of the query string
*
*
* @param string $str
* @return string
*/
private function parseWhere($str) {
private function parseCondition($str, $type = 'Where') {
$tmp = trim($str);
$str = self::bracketTrim($tmp,"(",")");
$brackets = false;
$loadMethod = "load".$type;
while($tmp != $str) {
$brackets = true;
$tmp = $str;
......@@ -853,7 +897,7 @@ class Doctrine_Query extends Doctrine_Access {
if(count($parts) > 1) {
$ret = array();
foreach($parts as $part) {
$ret[] = $this->parseWhere($part);
$ret[] = $this->parseCondition($part, $type);
}
$r = implode(" AND ",$ret);
} else {
......@@ -861,11 +905,11 @@ class Doctrine_Query extends Doctrine_Access {
if(count($parts) > 1) {
$ret = array();
foreach($parts as $part) {
$ret[] = $this->parseWhere($part);
$ret[] = $this->parseCondition($part, $type);
}
$r = implode(" OR ",$ret);
} else {
return $this->loadWhere($parts[0]);
return $this->$loadMethod($parts[0]);
}
}
if($brackets)
......@@ -873,6 +917,28 @@ class Doctrine_Query extends Doctrine_Access {
else
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
*
......@@ -919,6 +985,66 @@ class Doctrine_Query extends Doctrine_Access {
}
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
*
......
<?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 { }
?>
......@@ -75,7 +75,11 @@ class Doctrine_View {
*/
public function create() {
$sql = sprintf(self::CREATE, $this->name, $this->query->getQuery());
try {
$this->dbh->query($sql);
} catch(Exception $e) {
throw new Doctrine_View_Exception($e->__toString());
}
}
/**
* drops this view
......@@ -83,7 +87,11 @@ class Doctrine_View {
* @return void
*/
public function drop() {
try {
$this->dbh->query(sprintf(self::DROP, $this->name));
} catch(Exception $e) {
throw new Doctrine_View_Exception($e->__toString());
}
}
/**
* executes the view
......
<?php
class Doctrine_View_Exception extends Doctrine_Exception { }
?>
......@@ -13,8 +13,28 @@ class Doctrine_Cache_Query_SqliteTestCase extends Doctrine_UnitTestCase {
$this->cache = new Doctrine_Cache_Query_Sqlite($this->objTable);
$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 {
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() {
$query = new Doctrine_Query($this->session);
......
......@@ -48,11 +48,12 @@ $test->addTestCase(new Doctrine_CollectionTestCase());
$test->addTestCase(new Doctrine_PessimisticLockingTestCase());
$test->addTestCase(new Doctrine_ViewTestCase());
$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_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