Numerous improvements to the Oracle (oci8) driver and DB_driver
diff --git a/system/database/drivers/oci8/oci8_driver.php b/system/database/drivers/oci8/oci8_driver.php
index c662190..cc9557e 100644
--- a/system/database/drivers/oci8/oci8_driver.php
+++ b/system/database/drivers/oci8/oci8_driver.php
@@ -1,13 +1,13 @@
-<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
 /**
  * CodeIgniter
  *
  * An open source application development framework for PHP 5.1.6 or newer
  *
  * NOTICE OF LICENSE
- * 
+ *
  * Licensed under the Open Software License version 3.0
- * 
+ *
  * This source file is subject to the Open Software License (OSL 3.0) that is
  * bundled with this package in the files license.txt / license.rst.  It is
  * also available through the world wide web at this URL:
@@ -53,38 +53,37 @@
 
 class CI_DB_oci8_driver extends CI_DB {
 
-	var $dbdriver = 'oci8';
+	public $dbdriver = 'oci8';
 
 	// The character used for excaping
-	var $_escape_char = '"';
+	protected $_escape_char = '"';
 
 	// clause and character used for LIKE escape sequences
-	var $_like_escape_str = " escape '%s' ";
-	var $_like_escape_chr = '!';
+	protected $_like_escape_str = " escape '%s' ";
+	protected $_like_escape_chr = '!';
 
 	/**
 	 * The syntax to count rows is slightly different across different
 	 * database engines, so this string appears in each driver and is
 	 * used for the count_all() and count_all_results() functions.
 	 */
-	var $_count_string = "SELECT COUNT(1) AS ";
-	var $_random_keyword = ' ASC'; // not currently supported
+	protected $_count_string = 'SELECT COUNT(1) AS ';
+	protected $_random_keyword = ' ASC'; // not currently supported
 
 	// Set "auto commit" by default
-	var $_commit = OCI_COMMIT_ON_SUCCESS;
+	protected $_commit = OCI_COMMIT_ON_SUCCESS;
 
 	// need to track statement id and cursor id
-	var $stmt_id;
-	var $curs_id;
+	public $stmt_id;
+	public $curs_id;
 
 	// if we use a limit, we will add a field that will
 	// throw off num_fields later
-	var $limit_used;
+	public $limit_used;
 
 	/**
 	 * Non-persistent database connection
 	 *
-	 * @access  private called by the base class
 	 * @return  resource
 	 */
 	public function db_connect()
@@ -97,7 +96,6 @@
 	/**
 	 * Persistent database connection
 	 *
-	 * @access  private called by the base class
 	 * @return  resource
 	 */
 	public function db_pconnect()
@@ -113,7 +111,6 @@
 	 * Keep / reestablish the db connection if no queries have been
 	 * sent for a length of time exceeding the server's idle timeout
 	 *
-	 * @access	public
 	 * @return	void
 	 */
 	public function reconnect()
@@ -127,7 +124,6 @@
 	/**
 	 * Select the database
 	 *
-	 * @access  private called by the base class
 	 * @return  resource
 	 */
 	public function db_select()
@@ -141,7 +137,6 @@
 	/**
 	 * Set client character set
 	 *
-	 * @access	public
 	 * @param	string
 	 * @param	string
 	 * @return	resource
@@ -157,7 +152,6 @@
 	/**
 	 * Version number query string
 	 *
-	 * @access  protected
 	 * @return  string
 	 */
 	protected function _version()
@@ -170,14 +164,14 @@
 	/**
 	 * Execute the query
 	 *
-	 * @access  protected  called by the base class
 	 * @param   string  an SQL query
 	 * @return  resource
 	 */
 	protected function _execute($sql)
 	{
-		// oracle must parse the query before it is run. All of the actions with
-		// the query are based on the statement id returned by ociparse
+		/* Oracle must parse the query before it is run. All of the actions with
+		 * the query are based on the statement id returned by oci_parse().
+		 */
 		$this->stmt_id = FALSE;
 		$this->_set_stmt_id($sql);
 		oci_set_prefetch($this->stmt_id, 1000);
@@ -187,7 +181,6 @@
 	/**
 	 * Generate a statement ID
 	 *
-	 * @access  private
 	 * @param   string  an SQL query
 	 * @return  none
 	 */
@@ -206,7 +199,6 @@
 	 *
 	 * If needed, each database adapter can prep the query string
 	 *
-	 * @access  private called by execute()
 	 * @param   string  an SQL query
 	 * @return  string
 	 */
@@ -220,7 +212,6 @@
 	/**
 	 * getCursor.  Returns a cursor from the datbase
 	 *
-	 * @access  public
 	 * @return  cursor id
 	 */
 	public function get_cursor()
@@ -234,7 +225,6 @@
 	/**
 	 * Stored Procedure.  Executes a stored procedure
 	 *
-	 * @access  public
 	 * @param   package	 package stored procedure is in
 	 * @param   procedure   stored procedure to execute
 	 * @param   params	  array of parameters
@@ -287,7 +277,6 @@
 	/**
 	 * Bind parameters
 	 *
-	 * @access  private
 	 * @return  none
 	 */
 	private function _bind_params($params)
@@ -316,7 +305,6 @@
 	/**
 	 * Begin Transaction
 	 *
-	 * @access	public
 	 * @return	bool
 	 */
 	public function trans_begin($test_mode = FALSE)
@@ -337,7 +325,7 @@
 		// even if the queries produce a successful result.
 		$this->_trans_failure = ($test_mode === TRUE) ? TRUE : FALSE;
 
-		$this->_commit = OCI_DEFAULT;
+		$this->_commit = (is_php('5.3.2')) ? OCI_NO_AUTO_COMMIT : OCI_DEFAULT;
 		return TRUE;
 	}
 
@@ -346,7 +334,6 @@
 	/**
 	 * Commit Transaction
 	 *
-	 * @access	public
 	 * @return	bool
 	 */
 	public function trans_commit()
@@ -362,9 +349,8 @@
 			return TRUE;
 		}
 
-		$ret = oci_commit($this->conn_id);
 		$this->_commit = OCI_COMMIT_ON_SUCCESS;
-		return $ret;
+		return oci_commit($this->conn_id);
 	}
 
 	// --------------------------------------------------------------------
@@ -372,7 +358,6 @@
 	/**
 	 * Rollback Transaction
 	 *
-	 * @access	public
 	 * @return	bool
 	 */
 	public function trans_rollback()
@@ -388,9 +373,8 @@
 			return TRUE;
 		}
 
-		$ret = oci_rollback($this->conn_id);
 		$this->_commit = OCI_COMMIT_ON_SUCCESS;
-		return $ret;
+		return oci_rollback($this->conn_id);
 	}
 
 	// --------------------------------------------------------------------
@@ -398,7 +382,6 @@
 	/**
 	 * Escape String
 	 *
-	 * @access  public
 	 * @param   string
 	 * @param	bool	whether or not the string will be used in a LIKE condition
 	 * @return  string
@@ -434,7 +417,6 @@
 	/**
 	 * Affected Rows
 	 *
-	 * @access  public
 	 * @return  integer
 	 */
 	public function affected_rows()
@@ -447,7 +429,6 @@
 	/**
 	 * Insert ID
 	 *
-	 * @access  public
 	 * @return  integer
 	 */
 	public function insert_id()
@@ -464,7 +445,6 @@
 	 * Generates a platform-specific query string that counts all records in
 	 * the specified database
 	 *
-	 * @access  public
 	 * @param   string
 	 * @return  string
 	 */
@@ -494,7 +474,6 @@
 	 *
 	 * Generates a platform-specific query string so that the table names can be fetched
 	 *
-	 * @access	protected
 	 * @param	boolean
 	 * @return	string
 	 */
@@ -517,7 +496,6 @@
 	 *
 	 * Generates a platform-specific query string so that the column names can be fetched
 	 *
-	 * @access  protected
 	 * @param   string  the table name
 	 * @return  string
 	 */
@@ -533,7 +511,6 @@
 	 *
 	 * Generates a platform-specific query so that the column data can be retrieved
 	 *
-	 * @access  public
 	 * @param   string  the table name
 	 * @return  object
 	 */
@@ -547,7 +524,6 @@
 	/**
 	 * The error message string
 	 *
-	 * @access  protected
 	 * @return  string
 	 */
 	protected function _error_message()
@@ -562,7 +538,6 @@
 	/**
 	 * The error message number
 	 *
-	 * @access  protected
 	 * @return  integer
 	 */
 	protected function _error_number()
@@ -579,7 +554,6 @@
 	 *
 	 * This function escapes column and table names
 	 *
-	 * @access	protected
 	 * @param	string
 	 * @return	string
 	 */
@@ -622,7 +596,6 @@
 	 * This function implicitly groups FROM tables so there is no confusion
 	 * about operator precedence in harmony with SQL standards
 	 *
-	 * @access	protected
 	 * @param	type
 	 * @return	type
 	 */
@@ -643,7 +616,6 @@
 	 *
 	 * Generates a platform-specific insert string from the supplied data
 	 *
-	 * @access  public
 	 * @param   string  the table name
 	 * @param   array   the insert keys
 	 * @param   array   the insert values
@@ -688,7 +660,6 @@
 	 *
 	 * Generates a platform-specific update string from the supplied data
 	 *
-	 * @access	protected
 	 * @param	string	the table name
 	 * @param	array	the update data
 	 * @param	array	the where clause
@@ -705,12 +676,10 @@
 
 		$limit = ( ! $limit) ? '' : ' LIMIT '.$limit;
 
-		$orderby = (count($orderby) >= 1)?' ORDER BY '.implode(", ", $orderby):'';
+		$orderby = (count($orderby) > 0) ? ' ORDER BY '.implode(', ', $orderby) : '';
 
-		$sql = "UPDATE ".$table." SET ".implode(', ', $valstr);
-
-		$sql .= ($where != '' AND count($where) >=1) ? " WHERE ".implode(" ", $where) : '';
-
+		$sql = 'UPDATE '.$table.' SET '.implode(', ', $valstr);
+		$sql .= ($where != '' AND count($where) > 0) ? ' WHERE '.implode(' ', $where) : '';
 		$sql .= $orderby.$limit;
 
 		return $sql;
@@ -725,7 +694,6 @@
 	 * If the database does not support the truncate() command
 	 * This function maps to "DELETE FROM table"
 	 *
-	 * @access	protected
 	 * @param	string	the table name
 	 * @return	string
 	 */
@@ -741,7 +709,6 @@
 	 *
 	 * Generates a platform-specific delete string from the supplied data
 	 *
-	 * @access	protected
 	 * @param	string	the table name
 	 * @param	array	the where clause
 	 * @param	string	the limit clause
@@ -775,7 +742,6 @@
 	 *
 	 * Generates a platform-specific LIMIT clause
 	 *
-	 * @access  protected
 	 * @param   string  the sql query string
 	 * @param   integer the number of rows to limit the query to
 	 * @param   integer the offset value
@@ -802,7 +768,6 @@
 	/**
 	 * Close DB Connection
 	 *
-	 * @access  protected
 	 * @param   resource
 	 * @return  void
 	 */
@@ -811,10 +776,7 @@
 		@oci_close($conn_id);
 	}
 
-
 }
 
-
-
 /* End of file oci8_driver.php */
 /* Location: ./system/database/drivers/oci8/oci8_driver.php */
diff --git a/system/database/drivers/oci8/oci8_forge.php b/system/database/drivers/oci8/oci8_forge.php
index b4a24cd..6975f2a 100644
--- a/system/database/drivers/oci8/oci8_forge.php
+++ b/system/database/drivers/oci8/oci8_forge.php
@@ -1,13 +1,13 @@
-<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
 /**
  * CodeIgniter
  *
  * An open source application development framework for PHP 5.1.6 or newer
  *
  * NOTICE OF LICENSE
- * 
+ *
  * Licensed under the Open Software License version 3.0
- * 
+ *
  * This source file is subject to the Open Software License (OSL 3.0) that is
  * bundled with this package in the files license.txt / license.rst.  It is
  * also available through the world wide web at this URL:
@@ -39,12 +39,12 @@
 	/**
 	 * Create database
 	 *
-	 * @access	public
 	 * @param	string	the database name
 	 * @return	bool
 	 */
-	function _create_database($name)
+	public function _create_database($name)
 	{
+		// Not supported - schemas in Oracle are actual usernames
 		return FALSE;
 	}
 
@@ -53,12 +53,12 @@
 	/**
 	 * Drop database
 	 *
-	 * @access	private
 	 * @param	string	the database name
 	 * @return	bool
 	 */
-	function _drop_database($name)
+	public function _drop_database($name)
 	{
+		// Not supported - schemas in Oracle are actual usernames
 		return FALSE;
 	}
 
@@ -67,15 +67,14 @@
 	/**
 	 * Create Table
 	 *
-	 * @access	private
 	 * @param	string	the table name
 	 * @param	array	the fields
 	 * @param	mixed	primary key(s)
 	 * @param	mixed	key(s)
 	 * @param	boolean	should 'IF NOT EXISTS' be added to the SQL
-	 * @return	bool
+	 * @return	string
 	 */
-	function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists)
+	public function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists)
 	{
 		$sql = 'CREATE TABLE ';
 
@@ -100,9 +99,7 @@
 			{
 				$attributes = array_change_key_case($attributes, CASE_UPPER);
 
-				$sql .= "\n\t".$this->db->_protect_identifiers($field);
-
-				$sql .=  ' '.$attributes['TYPE'];
+				$sql .= "\n\t".$this->db->_protect_identifiers($field).' '.$attributes['TYPE'];
 
 				if (array_key_exists('CONSTRAINT', $attributes))
 				{
@@ -127,11 +124,6 @@
 				{
 					$sql .= ' NOT NULL';
 				}
-
-				if (array_key_exists('AUTO_INCREMENT', $attributes) && $attributes['AUTO_INCREMENT'] === TRUE)
-				{
-					$sql .= ' AUTO_INCREMENT';
-				}
 			}
 
 			// don't add a comma on the end of the last field
@@ -174,12 +166,11 @@
 	/**
 	 * Drop Table
 	 *
-	 * @access	private
-	 * @return	bool
+	 * @return	string
 	 */
-	function _drop_table($table)
+	public function _drop_table($table)
 	{
-		return FALSE;
+		return 'DROP TABLE ' . $this->db->_protect_identifiers($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -190,7 +181,6 @@
 	 * Generates a platform-specific query so that a table can be altered
 	 * Called by add_column(), drop_column(), and column_alter(),
 	 *
-	 * @access	private
 	 * @param	string	the ALTER type (ADD, DROP, CHANGE)
 	 * @param	string	the column name
 	 * @param	string	the table name
@@ -198,9 +188,9 @@
 	 * @param	string	the default value
 	 * @param	boolean	should 'NOT NULL' be added
 	 * @param	string	the field after which we should add the new field
-	 * @return	object
+	 * @return	string
 	 */
-	function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '')
+	public function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '')
 	{
 		$sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table)." $alter_type ".$this->db->_protect_identifiers($column_name);
 
@@ -242,12 +232,11 @@
 	 *
 	 * Generates a platform-specific query so that a table can be renamed
 	 *
-	 * @access	private
 	 * @param	string	the old table name
 	 * @param	string	the new table name
 	 * @return	string
 	 */
-	function _rename_table($table_name, $new_table_name)
+	public function _rename_table($table_name, $new_table_name)
 	{
 		$sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table_name)." RENAME TO ".$this->db->_protect_identifiers($new_table_name);
 		return $sql;
@@ -257,4 +246,4 @@
 }
 
 /* End of file oci8_forge.php */
-/* Location: ./system/database/drivers/oci8/oci8_forge.php */
\ No newline at end of file
+/* Location: ./system/database/drivers/oci8/oci8_forge.php */
diff --git a/system/database/drivers/oci8/oci8_result.php b/system/database/drivers/oci8/oci8_result.php
index 0f69fa9..cbf4bec 100644
--- a/system/database/drivers/oci8/oci8_result.php
+++ b/system/database/drivers/oci8/oci8_result.php
@@ -1,13 +1,13 @@
-<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
 /**
  * CodeIgniter
  *
  * An open source application development framework for PHP 5.1.6 or newer
  *
  * NOTICE OF LICENSE
- * 
+ *
  * Licensed under the Open Software License version 3.0
- * 
+ *
  * This source file is subject to the Open Software License (OSL 3.0) that is
  * bundled with this package in the files license.txt / license.rst.  It is
  * also available through the world wide web at this URL:
@@ -38,31 +38,40 @@
  */
 class CI_DB_oci8_result extends CI_DB_result {
 
-	var $stmt_id;
-	var $curs_id;
-	var $limit_used;
+	public $stmt_id;
+	public $curs_id;
+	public $limit_used;
+
+	// This will be changed by CI_DB_driver, but it's good to have a default:
+	public $commit_mode = OCI_DEFAULT;
+
+	/* Overwriting the parent here, so we have a way to know if it's
+	 * already called or not:
+	 */
+	public $num_rows;
 
 	/**
 	 * Number of rows in the result set.
 	 *
-	 * Oracle doesn't have a graceful way to retun the number of rows
+	 * Oracle doesn't have a graceful way to return the number of rows
 	 * so we have to use what amounts to a hack.
 	 *
-	 *
-	 * @access  public
 	 * @return  integer
 	 */
 	public function num_rows()
 	{
-		if ($this->num_rows === 0 && count($this->result_array()) > 0)
+		if ( ! is_int($this->num_rows))
 		{
-			$this->num_rows = count($this->result_array());
-			@oci_execute($this->stmt_id, OCI_DEFAULT);
-
-			if ($this->curs_id)
+			if (count($this->result_array) > 0)
 			{
-				@oci_execute($this->curs_id, OCI_DEFAULT);
+				return $this->num_rows = count($this->result_array);
 			}
+			elseif (count($this->result_object) > 0)
+			{
+				return $this->num_rows = count($this->result_array);
+			}
+
+			return $this->num_rows = count($this->result_array());
 		}
 
 		return $this->num_rows;
@@ -73,7 +82,6 @@
 	/**
 	 * Number of fields in the result set
 	 *
-	 * @access  public
 	 * @return  integer
 	 */
 	public function num_fields()
@@ -83,7 +91,7 @@
 		// if we used a limit we subtract it
 		if ($this->limit_used)
 		{
-			$count = $count - 1;
+			return $count - 1;
 		}
 
 		return $count;
@@ -96,7 +104,6 @@
 	 *
 	 * Generates an array of column names
 	 *
-	 * @access	public
 	 * @return	array
 	 */
 	public function list_fields()
@@ -116,7 +123,6 @@
 	 *
 	 * Generates an array of objects containing field meta-data
 	 *
-	 * @access  public
 	 * @return  array
 	 */
 	public function field_data()
@@ -140,7 +146,7 @@
 	/**
 	 * Free the result
 	 *
-	 * @return	null
+	 * @return	void
 	 */
 	public function free_result()
 	{
@@ -149,6 +155,17 @@
 			oci_free_statement($this->result_id);
 			$this->result_id = FALSE;
 		}
+
+		if (is_resource($this->stmt_id))
+		{
+			oci_free_statement($this->stmt_id);
+		}
+
+		if (is_resource($this->curs_id))
+		{
+			oci_cancel($this->curs_id);
+			$this->curs_id = NULL;
+		}
 	}
 
 	// --------------------------------------------------------------------
@@ -158,7 +175,6 @@
 	 *
 	 * Returns the result set as an array
 	 *
-	 * @access  protected
 	 * @return  array
 	 */
 	protected function _fetch_assoc()
@@ -174,22 +190,47 @@
 	 *
 	 * Returns the result set as an object
 	 *
-	 * @access  protected
 	 * @return  object
 	 */
 	protected function _fetch_object()
 	{
 		$id = ($this->curs_id) ? $this->curs_id : $this->stmt_id;
-		return @oci_fetch_object($id);
+		return oci_fetch_object($id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/* Query result
+	 *
+	 * Acts as a wrapper for result_array(), result_object()
+	 * and custom_result_object().
+	 *
+	 * @param	string	('object', 'array' or a custom class name)
+	 * @return	array
+	 */
+
+	public function result($type = 'object')
+	{
+		if ($type === 'object')
+		{
+			return $this->result_object();
+		}
+		elseif ($type === 'array')
+		{
+			return $this->result_array();
+		}
+		else
+		{
+			return $this->custom_result_object($type);
+		}
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Query result.  "array" version.
+	 * Query result. "array" version.
 	 *
-	 * @access  public
-	 * @return  array
+	 * @return	array
 	 */
 	public function result_array()
 	{
@@ -197,14 +238,488 @@
 		{
 			return $this->result_array;
 		}
+		elseif (count($this->result_object) > 0)
+		{
+			for ($i = 0, $c = count($this->result_object); $i < $c; $i++)
+			{
+				$this->result_array[$i] = (array) $this->result_object[$i];
+			}
+
+			return $this->result_array;
+		}
+		elseif (is_array($this->row_data))
+		{
+			if (count($this->row_data) === 0)
+			{
+				return $this->result_array;
+			}
+			else
+			{
+				$row_index = count($this->row_data);
+			}
+		}
+		else
+		{
+			$row_index = 0;
+			$this->row_data = array();
+		}
 
 		$row = NULL;
 		while ($row = $this->_fetch_assoc())
 		{
-			$this->result_array[] = $row;
+			$this->row_data[$row_index++] = $row;
 		}
 
-		return $this->result_array;
+		// Un-comment the following line, in case it becomes needed
+		// $this->_data_seek();
+		return $this->result_array = $this->row_data;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Query result. "object" version.
+	 *
+	 * @return	array
+	 */
+	public function result_object()
+	{
+		if (count($this->result_object) > 0)
+		{
+			return $this->result_object;
+		}
+		elseif (count($this->result_array) > 0)
+		{
+			for ($i = 0, $c = count($this->result_array); $i < $c; $i++)
+			{
+				$this->result_object[] = (object) $this->result_array[$i];
+			}
+
+			return $this->result_object;
+		}
+		elseif (is_array($this->row_data))
+		{
+			if (count($this->row_data) === 0)
+			{
+				return $this->result_object;
+			}
+			else
+			{
+				$row_index = count($this->row_data);
+				for ($i = 0; $i < $row_index; $i++)
+				{
+					$this->result_object[$i] = (object) $this->row_data[$i];
+				}
+			}
+		}
+		else
+		{
+			$row_index = 0;
+			$this->row_data = array();
+		}
+
+		$row = NULL;
+		while ($row = $this->_fetch_object())
+		{
+			$this->row_data[$row_index] = (array) $row;
+			$this->result_object[$row_index++] = $row;
+		}
+
+		// Un-comment the following line, in case it becomes needed
+		// $this->_data_seek();
+		return $this->result_object;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Query result. Custom object version.
+	 *
+	 * @param	string	class name used to instantiate rows to
+	 * @return	array
+	 */
+	public function custom_result_object($class_name)
+	{
+		if (array_key_exists($class_name, $this->custom_result_object))
+		{
+			return $this->custom_result_object[$class_name];
+		}
+
+		if ( ! class_exists($class_name) OR $this->result_id === FALSE OR $this->num_rows() === 0)
+		{
+			return array();
+		}
+
+		// add the data to the object
+		$result_object = array();
+		$data = NULL;
+
+		/* First check if we don't already have the data.
+		 * If so - pass by reference, as we don't know how
+		 * large it might be and we don't want 1000 row
+		 * sets being copied.
+		 */
+		if (count($this->result_array) > 0)
+		{
+			$data = &$this->result_array;
+		}
+		elseif (count($this->result_object) > 0)
+		{
+			$data = &$this->result_object;
+		}
+
+		if ( ! is_null($data))
+		{
+			for ($i = 0, $c = count($data); $i < $c; $i++)
+			{
+				$result_object[$i] = new $class_name();
+				foreach ($data[$i] as $key => $value)
+				{
+					$result_object[$i]->$key = $value;
+				}
+			}
+		}
+		else
+		{
+			// No prefetched result set
+			if (is_array($this->row_data))
+			{
+				$row_index = count($this->row_data);
+				if ($row_index === 0)
+				{
+					return array();
+				}
+				else
+				{
+					for ($i = 0; $i < $row_index; $i++)
+					{
+						$result_object[$i] = new $class_name();
+						foreach ($this->row_data[$i] as $key => $value)
+						{
+							$result_object[$i]->$key = $value;
+						}
+					}
+				}
+			}
+			else
+			{
+				$row_index = 0;
+				$this->row_data = array();
+			}
+
+			while ($row = $this->_fetch_assoc())
+			{
+				$data = new $class_name();
+				foreach ($row as $key => $value)
+				{
+					$data->$key = $value;
+				}
+
+				$this->row_data[$row_index] = $row;
+				$result_object[$row_index++] = $data;
+			}
+			// Un-comment the following line, in case it becomes needed
+			// $this->_data_seek();
+		}
+
+		/* As described for the num_rows() method - there's no easy
+		 * way to get the number of rows selected. Our work-around
+		 * solution (as in here as well) first checks if result_array
+		 * or result_object exist and returns their count. It doesn't
+		 * however check for a custom_object_result, so - do it here.
+		 */
+		if ( ! is_int($this->num_rows))
+		{
+			$this->num_rows = count($result_object);
+		}
+
+		// Cache and return the array
+		return $this->custom_result_object[$class_name] = $result_object;
+        }
+
+	// --------------------------------------------------------------------
+
+	/* Single row result.
+	 *
+	 * Acts as a wrapper for row_object(), row_array()
+	 * and custom_row_object(). Also used by first_row(), next_row()
+	 * and previous_row().
+	 *
+	 * @param	int	row index
+	 * @param	string	('object', 'array' or a custom class name)
+	 * @return	mixed	whatever was passed to the second parameter
+	 */
+
+	public function row($n = 0, $type = 'object')
+	{
+		if ($type === 'object') return $this->row_object($n);
+		elseif ($type === 'array') return $this->row_array($n);
+		else return $this->custom_row_object($n, $type);
+	}
+
+	// --------------------------------------------------------------------
+
+	/* Single row result. Array version.
+	 *
+	 * @param	int	row index
+	 * @return	array
+	 */
+
+	public function row_array($n = 0)
+	{
+		// Make sure $n is not a string
+		if ( ! is_int($n))
+		{
+			$n = (int) $n;
+		}
+
+		/* If row_data is initialized, it means that we've already tried
+		 * (at least) to fetch some data, so ... check if we already have
+		 * this row.
+		*/
+		if (is_array($this->row_data))
+		{
+			/* If we already have row_data[$n] - return it.
+			 *
+			 * If we enter the elseif, there's a number of reasons to
+			 * return an empty array:
+			 *
+			 *	- count($this->row_data) === 0 means there are no results
+			 *	- num_rows being set, result_array and/or result_object
+			 *	  having count() > 0 means that we've already fetched all
+			 *	  data and $n is greater than our highest row index available
+			 *	- $n < $this->current_row means that if such row existed,
+			 *	  we would've already returned it, therefore $n is an
+			 *	  invalid index
+			 */
+			if (isset($this->row_data[$n])) // We already have this row
+			{
+				$this->current_row = $n;
+				return $this->row_data[$n];
+			}
+			elseif (count($this->row_data) === 0 OR is_int($this->num_rows)
+				OR count($this->result_array) > 0 OR count($this->result_object) > 0
+				OR $n < $this->current_row)
+			{
+				// No such row exists
+				return array();
+			}
+
+			// Get the next row index that would actually need to be fetched
+			$current_row = ($this->current_row < count($this->row_data)) ? count($this->row_data) : $this->current_row + 1;
+		}
+		else
+		{
+			$current_row = $this->current_row = 0;
+			$this->row_data = array();
+		}
+
+		/* Fetch more data, if available
+		 *
+		 * NOTE: Operator precedence is important here, if you change
+		 *	 'AND' with '&&' - it WILL BREAK the results, as
+		 *	 $row will be assigned the scalar value of both
+		 *	 expressions!
+		 */
+		while ($row = $this->_fetch_assoc() AND $current_row <= $n)
+		{
+			$this->row_data[$current_row++] = $row;
+		}
+
+		// This would mean that there's no (more) data to fetch
+		if ( ! is_array($this->row_data) OR ! isset($this->row_data[$n]))
+		{
+			// Cache what we already have
+			if (is_array($this->row_data))
+			{
+				$this->num_rows = count($this->row_data);
+				/* Usually, row_data could have less elements than result_array,
+				 * but at this point - they should be exactly the same.
+				 */
+				$this->result_array = $this->row_data;
+			}
+			else
+			{
+				$this->num_rows = 0;
+			}
+
+			return array();
+		}
+
+		$this->current_row = $n;
+		return $this->row_data[$n];
+	}
+
+	// --------------------------------------------------------------------
+
+	/* Single row result. Object version.
+	 *
+	 * @param	int	row index
+	 * @return	mixed	object if row found; empty array if not
+	 */
+	public function row_object($n = 0)
+	{
+		// Make sure $n is not a string
+		if ( ! is_int($n))
+		{
+			$n = (int) $n;
+		}
+		/* Logic here is exactly the same as in row_array,
+		 * except we have to cast row_data[$n] to an object.
+		 *
+		 * If we already have result_object though - we can
+		 * directly return from it.
+		 */
+		if (isset($this->result_object[$n]))
+		{
+			$this->current_row = $n;
+			// Set this, if not already done.
+			if ( ! is_int($this->num_rows))
+			{
+				$this->num_rows = count($this->result_object);
+			}
+
+			return $this->result_object[$n];
+		}
+
+		$row = $this->row_array($n);
+		// Cast only if the row exists
+		if (count($row) > 0)
+		{
+			$this->current_row = $n;
+			return (object) $row;
+		}
+
+		return array();
+	}
+
+	// --------------------------------------------------------------------
+
+	/* Single row result. Custom object version.
+	 *
+	 * @param	int	row index
+	 * @param	string	custom class name
+	 * @return	mixed	custom object if row found; empty array otherwise
+	 */
+
+	public function custom_row_object($n = 0, $class_name)
+	{
+		// Make sure $n is not a string
+		if ( ! is_int($n))
+		{
+			$n = (int) $n;
+		}
+
+		if (array_key_exists($class_name, $this->custom_result_object))
+		{
+			/* We already have a the whole result set with this class_name,
+			 * return the specified row if it exists, and an empty array if
+			 * it doesn't.
+			 */
+			if (isset($this->custom_result_object[$class_name][$n]))
+			{
+				$this->current_row = $n;
+				return $this->custom_result_object[$class_name][$n];
+			}
+			else
+			{
+				return array();
+			}
+		}
+		elseif ( ! class_exists($class_name)) // No such class exists
+		{
+			return array();
+		}
+
+		$row = $this->row_array($n);
+		// An array would mean that the row doesn't exist
+		if (is_array($row))
+		{
+			return $row;
+		}
+
+		// Convert to the desired class and return
+		$row_object = new $class_name();
+		foreach ($row as $key => $value)
+		{
+			$row_object->$key = $value;
+		}
+
+		$this->current_row = $n;
+		return $row_object;
+	}
+
+	// --------------------------------------------------------------------
+
+	/* First row result.
+	 *
+	 * @param	string	('object', 'array' or a custom class name)
+	 * @return	mixed	whatever was passed to the second parameter
+	 */
+
+	public function first_row($type = 'object')
+	{
+		return $this->row(0, $type);
+	}
+
+	// --------------------------------------------------------------------
+
+	/* Last row result.
+	 *
+	 * @param	string	('object', 'array' or a custom class name)
+	 * @return	mixed	whatever was passed to the second parameter
+	 */
+	public function last_row($type = 'object')
+	{
+		$result = &$this->result($type);
+		if ( ! isset($this->num_rows))
+		{
+			$this->num_rows = count($result);
+		}
+		$this->current_row = $this->num_rows - 1;
+		return $result[$this->current_row];
+	}
+
+	// --------------------------------------------------------------------
+
+	/* Next row result.
+	 *
+	 * @param	string	('object', 'array' or a custom class name)
+	 * @return	mixed	whatever was passed to the second parameter
+	 */
+	public function next_row($type = 'object')
+	{
+		if (is_array($this->row_data))
+		{
+			$count = count($this->row_data);
+			if ($this->current_row > $count OR ($this->current_row === 0 && $count === 0))
+			{
+				$n = $count;
+			}
+			else
+			{
+				$n = $this->current_row + 1;
+			}
+		}
+		else
+		{
+			$n = 0;
+		}
+
+		return $this->row($n, $type);
+	}
+
+	// --------------------------------------------------------------------
+
+	/* Previous row result.
+	 *
+	 * @param	string	('object', 'array' or a custom class name)
+	 * @return	mixed	whatever was passed to the second parameter
+	 */
+	public function previous_row($type = 'object')
+	{
+		$n = ($this->current_row !== 0) ? $this->current_row - 1 : 0;
+		return $this->row($n, $type);
 	}
 
 	// --------------------------------------------------------------------
@@ -212,20 +727,59 @@
 	/**
 	 * Data Seek
 	 *
-	 * Moves the internal pointer to the desired offset.  We call
+	 * Moves the internal pointer to the desired offset. We call
 	 * this internally before fetching results to make sure the
-	 * result set starts at zero
+	 * result set starts at zero.
 	 *
-	 * @access	protected
-	 * @return	array
+	 * Oracle's PHP extension doesn't have an easy way of doing this
+	 * and the only workaround is to (re)execute the statement or cursor
+	 * in order to go to the first (zero) index of the result set.
+	 * Then, we would need to "dummy" fetch ($n - 1) rows to get to the
+	 * right one.
+	 *
+	 * This is as ridiculous as it sounds and it's the reason why every
+	 * other method that is fetching data tries to use an already "cached"
+	 * result set. Keeping this just in case it becomes needed at
+	 * some point in the future, but it will only work for resetting the
+	 * pointer to zero.
+	 *
+	 * @return	bool
 	 */
-	protected function _data_seek($n = 0)
+	protected function _data_seek()
 	{
-		return FALSE; // Not needed
+		/* The PHP manual says that if OCI_NO_AUTO_COMMIT mode
+		 * is used, and oci_rollback() and/or oci_commit() are
+		 * not subsequently called - this will cause an unnecessary
+		 * rollback to be triggered at the end of the script execution.
+		 *
+		 * Therefore we'll try to avoid using that mode flag
+		 * if we're not currently in the middle of a transaction.
+		 */
+		if ($this->commit_mode !== OCI_COMMIT_ON_SUCCESS)
+		{
+			$result = @oci_execute($this->stmt_id, $this->commit_mode);
+		}
+		else
+		{
+			$result = @oci_execute($this->stmt_id);
+		}
+
+		if ($result && $this->curs_id)
+		{
+			if ($this->commit_mode !== OCI_COMMIT_ON_SUCCESS)
+			{
+				return @oci_execute($this->curs_id, $this->commit_mode);
+			}
+			else
+			{
+				return @oci_execute($this->curs_id);
+			}
+		}
+
+		return $result;
 	}
 
 }
 
-
 /* End of file oci8_result.php */
 /* Location: ./system/database/drivers/oci8/oci8_result.php */
diff --git a/system/database/drivers/oci8/oci8_utility.php b/system/database/drivers/oci8/oci8_utility.php
index d60f98b..317ff91 100644
--- a/system/database/drivers/oci8/oci8_utility.php
+++ b/system/database/drivers/oci8/oci8_utility.php
@@ -1,13 +1,13 @@
-<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
 /**
  * CodeIgniter
  *
  * An open source application development framework for PHP 5.1.6 or newer
  *
  * NOTICE OF LICENSE
- * 
+ *
  * Licensed under the Open Software License version 3.0
- * 
+ *
  * This source file is subject to the Open Software License (OSL 3.0) that is
  * bundled with this package in the files license.txt / license.rst.  It is
  * also available through the world wide web at this URL:
@@ -39,12 +39,14 @@
 	/**
 	 * List databases
 	 *
-	 * @access	private
-	 * @return	bool
+	 * Generates a platform-specific query so that we get a list of schemas
+	 * Those are actually usernames in Oracle.
+	 *
+	 * @return	string
 	 */
-	function _list_databases()
+	public function _list_databases()
 	{
-		return FALSE;
+		return 'SELECT username FROM dba_users';
 	}
 
 	// --------------------------------------------------------------------
@@ -54,13 +56,12 @@
 	 *
 	 * Generates a platform-specific query so that a table can be optimized
 	 *
-	 * @access	private
 	 * @param	string	the table name
-	 * @return	object
+	 * @return	bool
 	 */
-	function _optimize_table($table)
+	public function _optimize_table($table)
 	{
-		return FALSE; // Is this supported in Oracle?
+		return FALSE; // Not supported in Oracle
 	}
 
 	// --------------------------------------------------------------------
@@ -70,13 +71,12 @@
 	 *
 	 * Generates a platform-specific query so that a table can be repaired
 	 *
-	 * @access	private
 	 * @param	string	the table name
-	 * @return	object
+	 * @return	bool
 	 */
-	function _repair_table($table)
+	public function _repair_table($table)
 	{
-		return FALSE; // Is this supported in Oracle?
+		return FALSE; // Not supported in Oracle
 	}
 
 	// --------------------------------------------------------------------
@@ -84,16 +84,16 @@
 	/**
 	 * Oracle Export
 	 *
-	 * @access	private
 	 * @param	array	Preferences
 	 * @return	mixed
 	 */
-	function _backup($params = array())
+	public function _backup($params = array())
 	{
 		// Currently unsupported
 		return $this->db->display_error('db_unsuported_feature');
 	}
+
 }
 
 /* End of file oci8_utility.php */
-/* Location: ./system/database/drivers/oci8/oci8_utility.php */
\ No newline at end of file
+/* Location: ./system/database/drivers/oci8/oci8_utility.php */