We have been discussing how to apply an IdentityMap to provide a per-object-cache. I have been toying a bit with a query-level cache too.
The idea is that you may fire off several identical queries during the same request. This ofcause depends alot on the context, you use the library in. If you pull big amounts of data, it's not benificiary, since the results will stay in-memory until the request ends, but under other circumstances it could save a few redundant queries.
I found use for such an option in relation to hieraical data, and with composite views witch pull from the same datasource.
I'm a bit in doubt whether this is a good way to solve such issues, or if a better IdentityMap could make it redundant - Any thoughts on that ?
QueryBuffer.php
PHP Code:
class QueryBuffer
{
var $connection;
var $queryBuffer;
function QueryBuffer(&$connection)
{
$this->connection =& $connection;
$this->queryBuffer = Array();
}
function & getConnection()
{
return $this->connection;
}
/**
* Clears the buffer
*/
function clearBuffer()
{
$this->queryBuffer = Array();
} // end function clearBuffer
/**
* Executes a sql-query against the connection.
* @access public
* @param string $sql The sql query
* @see MysqlConnection::execute
*/
function execute($sql) {
$this->clearBuffer();
return $this->connection->execute($sql);
}
/**
* Executes a sql-query against the connection and returns an iterator on the result set if any.
* @access public
* @param string $sql The sql query
* @return BufferedIterator A buffered iterator for a result set.
* @see MysqlConnection::select
*/
function & select($sql) {
$hash = md5($sql);
if (!isset($this->queryBuffer[$hash])) {
$result =& $this->connection->select($sql);
if (is_a($result, 'MysqlIterator')) {
$this->queryBuffer[$hash] =& new ResultBuffer();
$this->queryBuffer[$hash]->load($result);
}
}
return new BufferedIterator($this->queryBuffer[$hash]);
}
}
/**
* Provides a container for a MysqlIterator
*/
class ResultBuffer
{
var $rows;
function ResultBuffer()
{
$this->rows = Array();
}
function load(&$mysqlIterator)
{
while (!is_null($row = $mysqlIterator->next())) {
$this->rows[] = $row;
}
}
function get($index)
{
return $this->rows[$index];
}
function count()
{
return count($this->rows);
}
}
/**
* BufferedIterator behaves exactly as MysqlIterator
*/
class BufferedIterator
{
var $resultBuffer;
var $_count;
var $_position;
function BufferedIterator(&$resultBuffer)
{
$this->resultBuffer =& $resultBuffer;
$this->_position = 0;
$this->_count = $this->resultBuffer->count();
}
/**
* Returns a hash of the next row.
* @return hash/null A hash corresponding to one result row or null on empty.
* @access public
*/
function next() {
if ($this->isEnd()) {
return null;
}
$row = $this->resultBuffer->get($this->_position);
++$this->_position;
return $row;
}
/**
* Returns the total number of rows in the result set
* @return integer Total number of rows.
* @access public
*/
function count() {
return $this->_count;
}
/**
* Just a dummy, in order to honor the MysqlIterator's interface
* @access public
*/
function close() { ; }
/**
* Accessor for end of input state.
* @return boolean True if file finished.
* @access public
*/
function isEnd() {
return $this->_position >= $this->_count;
}
}
Sample
PHP Code:
require_once(dirname(__FILE__) . '/classes/change_class.php');
require_once(dirname(__FILE__) . '/classes/QueryBuffer.php');
$queryBuffer =& new QueryBuffer(new MysqlConnection('localhost', 'root', 'secret', 'changes'));
$sql = "SELECT * FROM insects";
$result = $queryBuffer->select($sql);
while ($row = $result->next()) {
echo "<p>";
foreach ($row as $key => $value) {
echo "<b>$key:</b> $value<br>\n";
}
echo "</p>\n";
}
As you see, QueryBuffer is implemented as a decorator to Connection, rather than just extending Connection. This way it's the programmers choice to run through the buffer or directly against the connection.
Bookmarks