DQL: Preliminary support for HAVING + GROUP BY, New component Doctrine_Cache_Query_Sqlite
This commit is contained in:
parent
1e0d675759
commit
aacb279505
@ -245,6 +245,7 @@ final class Doctrine {
|
||||
case "DQL":
|
||||
case "Sensei":
|
||||
case "Iterator":
|
||||
case "View":
|
||||
$a[] = $path.DIRECTORY_SEPARATOR.$entry;
|
||||
break;
|
||||
default:
|
||||
|
118
Doctrine/Cache/Query/Sqlite.php
Normal file
118
Doctrine/Cache/Query/Sqlite.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?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
|
||||
*
|
||||
|
@ -1,5 +0,0 @@
|
||||
<?php
|
||||
require_once(Doctrine::getPath().DIRECTORY_SEPARATOR."Exception.class.php");
|
||||
|
||||
class Doctrine_Query_Exception extends Doctrine_Exception { }
|
||||
?>
|
5
Doctrine/Query/Exception.php
Normal file
5
Doctrine/Query/Exception.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?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());
|
||||
$this->dbh->query($sql);
|
||||
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() {
|
||||
$this->dbh->query(sprintf(self::DROP, $this->name));
|
||||
try {
|
||||
$this->dbh->query(sprintf(self::DROP, $this->name));
|
||||
} catch(Exception $e) {
|
||||
throw new Doctrine_View_Exception($e->__toString());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* executes the view
|
||||
|
3
Doctrine/View/Exception.php
Normal file
3
Doctrine/View/Exception.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?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_QueryTestCase());
|
||||
|
||||
$test->addTestCase(new Doctrine_ViewTestCase());
|
||||
|
||||
//$test->addTestCase(new Doctrine_Cache_Query_SqliteTestCase());
|
||||
$test->addTestCase(new Doctrine_QueryTestCase());
|
||||
|
||||
$test->addTestCase(new Doctrine_Cache_Query_SqliteTestCase());
|
||||
|
||||
//$test->addTestCase(new Doctrine_Cache_FileTestCase());
|
||||
//$test->addTestCase(new Doctrine_Cache_SqliteTestCase());
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user