Added and documented Active Record caching.
Made AR fully database-prefix aware
diff --git a/system/database/DB_active_rec.php b/system/database/DB_active_rec.php
index e93b99e..b3bf02a 100644
--- a/system/database/DB_active_rec.php
+++ b/system/database/DB_active_rec.php
@@ -43,6 +43,23 @@
var $ar_set = array();
var $ar_wherein = array();
var $ar_aliased_tables = array();
+ var $ar_store_array = array();
+
+ // Active Record Caching variables
+ var $ar_caching = FALSE;
+ var $ar_cache_select = array();
+ var $ar_cache_from = array();
+ var $ar_cache_join = array();
+ var $ar_cache_where = array();
+ var $ar_cache_like = array();
+ var $ar_cache_groupby = array();
+ var $ar_cache_having = array();
+ var $ar_cache_limit = FALSE;
+ var $ar_cache_offset = FALSE;
+ var $ar_cache_order = FALSE;
+ var $ar_cache_orderby = array();
+ var $ar_cache_set = array();
+
/**
* DB Prefix
@@ -87,12 +104,23 @@
if ($val != '*' && $protect_identifiers !== FALSE)
{
- $val = $this->_protect_identifiers($val);
+ if (strpos($val, '.') !== FALSE)
+ {
+ $val = $this->dbprefix.$val;
+ }
+ else
+ {
+ $val = $this->_protect_identifiers($val);
+ }
}
if ($val != '')
{
$this->ar_select[] = $val;
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_select[] = $val;
+ }
}
}
return $this;
@@ -122,6 +150,10 @@
$sql = 'MAX('.$this->_protect_identifiers(trim($select)).') AS '.$this->_protect_identifiers(trim($alias));
$this->ar_select[] = $sql;
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_select[] = $sql;
+ }
return $this;
}
@@ -150,7 +182,11 @@
$sql = 'MIN('.$this->_protect_identifiers(trim($select)).') AS '.$this->_protect_identifiers(trim($alias));
$this->ar_select[] = $sql;
-
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_select[] = $sql;
+ }
+
return $this;
}
@@ -178,7 +214,11 @@
$sql = 'AVG('.$this->_protect_identifiers(trim($select)).') AS '.$this->_protect_identifiers(trim($alias));
$this->ar_select[] = $sql;
-
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_select[] = $sql;
+ }
+
return $this;
}
@@ -206,7 +246,11 @@
$sql = 'SUM('.$this->_protect_identifiers(trim($select)).') AS '.$this->_protect_identifiers(trim($alias));
$this->ar_select[] = $sql;
-
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_select[] = $sql;
+ }
+
return $this;
}
@@ -243,6 +287,10 @@
foreach ((array)$from as $val)
{
$this->ar_from[] = $this->_protect_identifiers($this->_track_aliases($val));
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_from[] = $this->_protect_identifiers($val);
+ }
}
return $this;
@@ -289,7 +337,14 @@
$cond = preg_replace('|([\w\.]+)([\W\s]+)(.+)|', $this->dbprefix . "$1$2" . $this->dbprefix . "$3", $cond);
}
- $this->ar_join[] = $type.'JOIN '.$this->_protect_identifiers($this->dbprefix.$table, TRUE).' ON '.$cond;
+ $join = $type.'JOIN '.$this->_protect_identifiers($this->dbprefix.$table, TRUE).' ON '.$cond;
+
+ $this->ar_join[] = $join;
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_join[] = $join;
+ }
+
return $this;
}
@@ -360,7 +415,7 @@
{
$key = array($key => $value);
}
-
+
foreach ($key as $k => $v)
{
$prefix = (count($this->ar_where) == 0) ? '' : $type;
@@ -393,10 +448,18 @@
}
$v = ' '.$this->escape($v);
-
}
-
+ else
+ {
+ $k = $this->_protect_identifiers($k, TRUE);
+ }
+
$this->ar_where[] = $prefix.$k.$v;
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_where[] = $prefix.$k.$v;
+ }
+
}
return $this;
}
@@ -507,7 +570,13 @@
$prefix = (count($this->ar_where) == 0) ? '' : $type;
- $this->ar_where[] = $prefix . $this->_protect_identifiers($key) . $not . " IN (" . implode(", ", $this->ar_wherein) . ") ";
+ $where_in = $prefix . $this->_protect_identifiers($key) . $not . " IN (" . implode(", ", $this->ar_wherein) . ") ";
+
+ $this->ar_where[] = $where_in;
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_where[] = $where_in;
+ }
// reset the array for multiple calls
$this->ar_wherein = array();
@@ -629,16 +698,23 @@
if ($side == 'before')
{
- $this->ar_like[] = $prefix." $k $not LIKE '%{$v}'";
+ $like_statement = $prefix." $k $not LIKE '%{$v}'";
}
elseif ($side == 'after')
{
- $this->ar_like[] = $prefix." $k $not LIKE '{$v}%'";
+ $like_statement = $prefix." $k $not LIKE '{$v}%'";
}
else
{
- $this->ar_like[] = $prefix." $k $not LIKE '%{$v}%'";
+ $like_statement = $prefix." $k $not LIKE '%{$v}%'";
}
+
+ $this->ar_like[] = $like_statement;
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_like[] = $like_statement;
+ }
+
}
return $this;
}
@@ -664,7 +740,13 @@
$val = trim($val);
if ($val != '')
+ {
$this->ar_groupby[] = $this->_protect_identifiers($val);
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_groupby[] = $this->_protect_identifiers($val);
+ }
+ }
}
return $this;
}
@@ -724,6 +806,7 @@
*
* @access private
* @param string
+
* @param string
* @return object
*/
@@ -737,14 +820,20 @@
foreach ($key as $k => $v)
{
$prefix = (count($this->ar_having) == 0) ? '' : $type;
+ $k = $this->_protect_identifiers($k);
if ($v != '')
{
- $v = ' '.$this->escape($v);
+ $v = ' '.$this->escape_str($v);
}
$this->ar_having[] = $prefix.$k.$v;
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_having[] = $prefix.$k.$v;
+ }
}
+
return $this;
}
@@ -770,7 +859,14 @@
$direction = (in_array(strtoupper(trim($direction)), array('ASC', 'DESC'), TRUE)) ? ' '.$direction : ' ASC';
}
- $this->ar_orderby[] = $this->_protect_identifiers($orderby, TRUE).$direction;
+ $orderby_statement = $this->_protect_identifiers($orderby, TRUE).$direction;
+
+ $this->ar_orderby[] = $orderby_statement;
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_orderby[] = $orderby_statement;
+ }
+
return $this;
}
@@ -799,9 +895,19 @@
function limit($value, $offset = '')
{
$this->ar_limit = $value;
-
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_limit[] = $value;
+ }
+
if ($offset != '')
+ {
$this->ar_offset = $offset;
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_offset[] = $offset;
+ }
+ }
return $this;
}
@@ -815,9 +921,14 @@
* @param integer the offset value
* @return object
*/
- function offset($value)
+ function offset($offset)
{
- $this->ar_offset = $value;
+ $this->ar_offset = $offset;
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_offset[] = $offset;
+ }
+
return $this;
}
@@ -846,12 +957,19 @@
if ($escape === FALSE)
{
$this->ar_set[$this->_protect_identifiers($k)] = $v;
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_offset[$this->_protect_identifiers($k)] = $v;
+ }
}
else
{
$this->ar_set[$this->_protect_identifiers($k)] = $this->escape($v);
+ if ($this->ar_caching === TRUE)
+ {
+ $this->ar_cache_offset[$this->_protect_identifiers($k)] = $this->escape($v);
+ }
}
-
}
return $this;
@@ -1331,6 +1449,11 @@
*/
function _compile_select($select_override = FALSE)
{
+ if ($this->ar_caching === TRUE)
+ {
+ $this->_merge_cache();
+ }
+
$sql = ( ! $this->ar_distinct) ? 'SELECT ' : 'SELECT DISTINCT ';
$sql .= (count($this->ar_select) == 0) ? '*' : implode(', ', $this->ar_select);
@@ -1456,6 +1579,107 @@
// --------------------------------------------------------------------
/**
+ * Start Cache
+ *
+ * Starts AR caching
+ *
+ * @access public
+ * @return void
+ */
+ function start_cache()
+ {
+ $this->ar_caching = TRUE;
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Stop Cache
+ *
+ * Stops AR caching
+ *
+ * @access public
+ * @return void
+ */
+ function stop_cache()
+ {
+ $this->ar_caching = FALSE;
+ }
+
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Flush Cache
+ *
+ * Empties the AR cache
+ *
+ * @access public
+ * @return void
+ */
+ function flush_cache()
+ {
+ $ar_reset_items = array(
+ 'ar_cache_select' => array(),
+ 'ar_cache_from' => array(),
+ 'ar_cache_join' => array(),
+ 'ar_cache_where' => array(),
+ 'ar_cache_like' => array(),
+ 'ar_cache_groupby' => array(),
+ 'ar_cache_having' =>array(),
+ 'ar_cache_orderby' => array(),
+ 'ar_cache_set' => array()
+ );
+
+ $this->_reset_run($ar_reset_items);
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Merge Cache
+ *
+ * When called, this function merges any cached AR arrays with
+ * locally called ones.
+ *
+ * @access private
+ * @return void
+ */
+ function _merge_cache()
+ {
+ $ar_items = array('select', 'from', 'join', 'where', 'like', 'groupby', 'having', 'orderby', 'set');
+
+ foreach ($ar_items as $ar_item)
+ {
+ $ar_cache_item = 'ar_cache_'.$ar_item;
+ $ar_item = 'ar_'.$ar_item;
+ $this->$ar_item = array_unique(array_merge($this->$ar_item, $this->$ar_cache_item));
+ }
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Resets the active record values. Called by the get() function
+ *
+ * @access private
+ * @param array An array of fields to reset
+ * @return void
+ */
+ function _reset_run($ar_reset_items)
+ {
+ foreach ($ar_reset_items as $item => $default_value)
+ {
+ if (!in_array($item, $this->ar_store_array))
+ {
+ $this->$item = $default_value;
+ }
+ }
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
* Resets the active record values. Called by the get() function
*
* @access private
@@ -1463,20 +1687,24 @@
*/
function _reset_select()
{
- $this->ar_select = array();
- $this->ar_distinct = FALSE;
- $this->ar_from = array();
- $this->ar_join = array();
- $this->ar_where = array();
- $this->ar_like = array();
- $this->ar_groupby = array();
- $this->ar_having = array();
- $this->ar_limit = FALSE;
- $this->ar_offset = FALSE;
- $this->ar_order = FALSE;
- $this->ar_orderby = array();
- $this->ar_wherein = array();
- $this->ar_aliased_tables = array();
+ $ar_reset_items = array(
+ 'ar_select' => array(),
+ 'ar_from' => array(),
+ 'ar_join' => array(),
+ 'ar_where' => array(),
+ 'ar_like' => array(),
+ 'ar_groupby' => array(),
+ 'ar_having' => array(),
+ 'ar_orderby' => array(),
+ 'ar_wherein' => array(),
+ 'ar_aliased_tables' => array(),
+ 'ar_distinct' => FALSE,
+ 'ar_limit' => FALSE,
+ 'ar_offset' => FALSE,
+ 'ar_order' => FALSE,
+ );
+
+ $this->_reset_run($ar_reset_items);
}
// --------------------------------------------------------------------
@@ -1490,16 +1718,19 @@
* @return void
*/
function _reset_write()
- {
- $this->ar_set = array();
- $this->ar_from = array();
- $this->ar_where = array();
- $this->ar_like = array();
- $this->ar_limit = FALSE;
- $this->ar_order = FALSE;
- $this->ar_orderby = array();
+ {
+ $ar_reset_items = array(
+ 'ar_set' => array(),
+ 'ar_from' => array(),
+ 'ar_where' => array(),
+ 'ar_like' => array(),
+ 'ar_orderby' => array(),
+ 'ar_limit' => FALSE,
+ 'ar_order' => FALSE
+ );
+
+ $this->_reset_run($ar_reset_items);
}
}
-
?>
\ No newline at end of file
diff --git a/system/database/drivers/mssql/mssql_driver.php b/system/database/drivers/mssql/mssql_driver.php
index ad747d4..ecd404b 100644
--- a/system/database/drivers/mssql/mssql_driver.php
+++ b/system/database/drivers/mssql/mssql_driver.php
@@ -433,6 +433,13 @@
function _protect_identifiers($item, $affect_spaces = TRUE, $first_word_only = FALSE)
{
// MSSQL doesn't use backticks
+ if (strpos($item, '.') !== FALSE)
+ {
+ $aliased_tables = implode(".",$this->ar_aliased_tables).'.';
+ $table_name = substr($item, 0, strpos($item, '.')+1);
+ $item = (strpos($aliased_tables, $table_name) !== FALSE) ? $item = $item : $this->dbprefix.$item;
+ }
+
return $item;
}
diff --git a/system/database/drivers/mysql/mysql_driver.php b/system/database/drivers/mysql/mysql_driver.php
index f002572..a5082d1 100644
--- a/system/database/drivers/mysql/mysql_driver.php
+++ b/system/database/drivers/mysql/mysql_driver.php
@@ -454,6 +454,13 @@
// we may need "`item1` `item2`" and not "`item1 item2`"
if (ctype_alnum($item) === FALSE)
{
+ if (strpos($item, '.') !== FALSE)
+ {
+ $aliased_tables = implode(".",$this->ar_aliased_tables).'.';
+ $table_name = substr($item, 0, strpos($item, '.')+1);
+ $item = (strpos($aliased_tables, $table_name) !== FALSE) ? $item = $item : $this->dbprefix.$item;
+ }
+
// This function may get "field >= 1", and need it to return "`field` >= 1"
$lbound = ($first_word_only === TRUE) ? '' : '|\s|\(';
diff --git a/system/database/drivers/mysqli/mysqli_driver.php b/system/database/drivers/mysqli/mysqli_driver.php
index b2e97f9..9e7cc0c 100644
--- a/system/database/drivers/mysqli/mysqli_driver.php
+++ b/system/database/drivers/mysqli/mysqli_driver.php
@@ -448,6 +448,13 @@
// we may need "`item1` `item2`" and not "`item1 item2`"
if (ctype_alnum($item) === FALSE)
{
+ if (strpos($item, '.') !== FALSE)
+ {
+ $aliased_tables = implode(".",$this->ar_aliased_tables).'.';
+ $table_name = substr($item, 0, strpos($item, '.')+1);
+ $item = (strpos($aliased_tables, $table_name) !== FALSE) ? $item = $item : $this->dbprefix.$item;
+ }
+
// This function may get "field >= 1", and need it to return "`field` >= 1"
$lbound = ($first_word_only === TRUE) ? '' : '|\s|\(';
diff --git a/system/database/drivers/oci8/oci8_driver.php b/system/database/drivers/oci8/oci8_driver.php
index ddc0fba..aa2aeca 100644
--- a/system/database/drivers/oci8/oci8_driver.php
+++ b/system/database/drivers/oci8/oci8_driver.php
@@ -559,6 +559,13 @@
// we may need "`item1` `item2`" and not "`item1 item2`"
if (ctype_alnum($item) === FALSE)
{
+ if (strpos($item, '.') !== FALSE)
+ {
+ $aliased_tables = implode(".",$this->ar_aliased_tables).'.';
+ $table_name = substr($item, 0, strpos($item, '.')+1);
+ $item = (strpos($aliased_tables, $table_name) !== FALSE) ? $item = $item : $this->dbprefix.$item;
+ }
+
// This function may get "field >= 1", and need it to return "`field` >= 1"
$lbound = ($first_word_only === TRUE) ? '' : '|\s|\(';
diff --git a/system/database/drivers/odbc/odbc_driver.php b/system/database/drivers/odbc/odbc_driver.php
index 03c0e6a..88fff43 100644
--- a/system/database/drivers/odbc/odbc_driver.php
+++ b/system/database/drivers/odbc/odbc_driver.php
@@ -424,6 +424,13 @@
// we may need "`item1` `item2`" and not "`item1 item2`"
if (ctype_alnum($item) === FALSE)
{
+ if (strpos($item, '.') !== FALSE)
+ {
+ $aliased_tables = implode(".",$this->ar_aliased_tables).'.';
+ $table_name = substr($item, 0, strpos($item, '.')+1);
+ $item = (strpos($aliased_tables, $table_name) !== FALSE) ? $item = $item : $this->dbprefix.$item;
+ }
+
// This function may get "field >= 1", and need it to return "`field` >= 1"
$lbound = ($first_word_only === TRUE) ? '' : '|\s|\(';
diff --git a/system/database/drivers/postgre/postgre_driver.php b/system/database/drivers/postgre/postgre_driver.php
index e729813..ae8bd86 100644
--- a/system/database/drivers/postgre/postgre_driver.php
+++ b/system/database/drivers/postgre/postgre_driver.php
@@ -444,6 +444,13 @@
// we may need ""item1" "item2"" and not ""item1 item2""
if (ctype_alnum($item) === FALSE)
{
+ if (strpos($item, '.') !== FALSE)
+ {
+ $aliased_tables = implode(".",$this->ar_aliased_tables).'.';
+ $table_name = substr($item, 0, strpos($item, '.')+1);
+ $item = (strpos($aliased_tables, $table_name) !== FALSE) ? $item = $item : $this->dbprefix.$item;
+ }
+
// This function may get "field >= 1", and need it to return ""field" >= 1"
$lbound = ($first_word_only === TRUE) ? '' : '|\s|\(';
@@ -486,7 +493,7 @@
$tables = array($tables);
}
- return implode(', ', $tables);
+ return '('.implode(', ', $tables).')';
}
// --------------------------------------------------------------------
diff --git a/system/database/drivers/sqlite/sqlite_driver.php b/system/database/drivers/sqlite/sqlite_driver.php
index 38febca..dad5eee 100644
--- a/system/database/drivers/sqlite/sqlite_driver.php
+++ b/system/database/drivers/sqlite/sqlite_driver.php
@@ -440,6 +440,13 @@
// we may need "`item1` `item2`" and not "`item1 item2`"
if (ctype_alnum($item) === FALSE)
{
+ if (strpos($item, '.') !== FALSE)
+ {
+ $aliased_tables = implode(".",$this->ar_aliased_tables).'.';
+ $table_name = substr($item, 0, strpos($item, '.')+1);
+ $item = (strpos($aliased_tables, $table_name) !== FALSE) ? $item = $item : $this->dbprefix.$item;
+ }
+
// This function may get "field >= 1", and need it to return "`field` >= 1"
$lbound = ($first_word_only === TRUE) ? '' : '|\s|\(';
diff --git a/user_guide/changelog.html b/user_guide/changelog.html
index 1732e50..573db86 100644
--- a/user_guide/changelog.html
+++ b/user_guide/changelog.html
@@ -62,10 +62,24 @@
<p>Release Date: -- still in development</p>
<ul>
- <li>Modified xss_clean() to be more intelligent with its handling of URL encoded strings.</li>
+ <li>Active Record
+ <ul>
+ <li>Added <a href="./database/active_record.html#caching">Active Record Caching</a>.</li>
+ <li>Made Active Record fully database-prefix aware</li>
+ </ul>
+ </li>
+ <li>Core Changes
+ <ul>
+ <li>Modified xss_clean() to be more intelligent with its handling of URL encoded strings.</li>
+ </ul>
+ </li>
+ </ul>
+
+
+<h3>Bugfixes for 1.6.1</h3>
+<ul>
+ <li> Made Active Record fully database prefix aware (#3384)</li>
</ul>
-
-
<h2>Version 1.6.0</h2>
<p>Release Date: January 30, 2008 </p>
<ul>
diff --git a/user_guide/database/active_record.html b/user_guide/database/active_record.html
index 12c59e1..9a75992 100644
--- a/user_guide/database/active_record.html
+++ b/user_guide/database/active_record.html
@@ -78,6 +78,7 @@
<li><a href="#update">Updating Data</a></li>
<li><a href="#delete">Deleting Data</a></li>
<li><a href="#chaining">Method Chaining</a></li>
+<li><a href="#caching">Active Record Caching</a></li>
</ul>
@@ -698,8 +699,8 @@
// TRUNCATE mytable <br />
</code>
<p class="important"><strong>Note:</strong> If the TRUNCATE command isn't available, truncate() will execute as "DELETE FROM table".</p>
-<a name="chaining"> </a>
-<h1>Method Chaining</h1>
+
+<h1><a name="chaining"> </a>Method Chaining</h1>
<p>Method chaining allows you to simplify your syntax by connecting multiple functions. Consider this example:</p>
@@ -710,8 +711,38 @@
<p class="important"><strong>Note:</strong> Method chaining only works with PHP 5.</p>
+<p> </p>
-
+<h1><a name="caching"> </a>Active Record Caching</h1>
+<p>While not "true" caching, Active Record enables you to save (or "cache") certain parts of your queries for reuse later. Normally, when an Active Record call is completed, all stored information is reset for the next call. With caching, you can prevent this reset, and reuse information easily.</p>
+<p>Cached calls are cumulative. If you makes 2 cached select() calls, and then 2 uncached select() calls, this will result in 4 select() calls. There are three Caching functions available:</p>
+<h2>$this->db->start_cache()</h2>
+<p>This function must be called to begin caching. All Active Record queries of the correct type (see below for supported queries) are stored for later use.</p>
+<h2>$this->db->stop_cache()</h2>
+<p>This function can be called to stop caching.</p>
+<h2>$this->db->flush_cache()</h2>
+<p>This function deletes all items from the Active Record cache.</p>
+<p>Here's a usage example:</p>
+<p><code> $this->db->start_cache();<br />
+ $this->db->select('field1');<br />
+ $this->db->stop_cache();<br />
+ $this->db->get('tablename');<br />
+ // Results in:<br />
+ // SELECT `field1` FROM (`tablename`)<br />
+ <br />
+ this->db->select('field2');<br />
+ $this->db->get('tablename');<br />
+ // Results in:<br />
+ // SELECT `field1`, `field2` FROM (`tablename`)<br />
+ <br />
+ $this->db->flush_cache();<br />
+ <br />
+ this->db->select('field2');<br />
+ $this->db->get('tablename');<br />
+ // Results in:<br />
+ // SELECT `field2` FROM (`tablename`)</code></p>
+<p class="important"> <strong>Note:</strong> The following fields can be cached: ‘select’, ‘from’, ‘join’, ‘where’, ‘like’, ‘groupby’, ‘having’, ‘orderby’, ‘set’</p>
+<p> </p>
</div>
<!-- END CONTENT -->