diff --git a/libraries/joomla/database/driver/oracle.php b/libraries/joomla/database/driver/oracle.php
index f920d36b30e24..aac0fbc35071b 100644
--- a/libraries/joomla/database/driver/oracle.php
+++ b/libraries/joomla/database/driver/oracle.php
@@ -15,7 +15,7 @@
* @link https://secure.php.net/pdo
* @since 12.1
*/
-class JDatabaseDriverOracle extends JDatabaseDriverPdo
+class JDatabaseDriverOracle extends JDatabaseDriver
{
/**
* The name of the database driver.
@@ -44,6 +44,14 @@ class JDatabaseDriverOracle extends JDatabaseDriverPdo
*/
protected $nameQuote = '"';
+ /**
+ * Returns the current commit mode
+ *
+ * @var int
+ * @since 12.1
+ */
+ protected $commitMode = OCI_COMMIT_ON_SUCCESS;
+
/**
* Returns the current dateformat
*
@@ -60,6 +68,50 @@ class JDatabaseDriverOracle extends JDatabaseDriverPdo
*/
protected $charset;
+ /**
+ * Saves the number of rows value
+ * before it gets reset by freeResult().
+ *
+ * @var int
+ */
+ protected $numRows = 0;
+
+ /**
+ * Is used to decide whether a result set
+ * should generate lowercase field names
+ *
+ * @var boolean
+ */
+ protected $toLower = true;
+
+ /**
+ * Contains the query type of the
+ * query about to be executed
+ *
+ * @var string
+ */
+ protected $queryType = '';
+
+ /**
+ * Is used to decide whether a result set
+ * should return the LOB values or the LOB objects
+ */
+ protected $returnLobs = true;
+
+ /**
+ * @var resource The prepared statement.
+ * @since 12.1
+ */
+ protected $prepared;
+
+ /**
+ * Contains the current query execution status
+ *
+ * @var array
+ * @since 12.1
+ */
+ protected $executed = false;
+
/**
* Constructor.
*
@@ -69,7 +121,11 @@ class JDatabaseDriverOracle extends JDatabaseDriverPdo
*/
public function __construct($options)
{
- $options['driver'] = 'oci';
+ $options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost';
+ $options['user'] = (isset($options['user'])) ? $options['user'] : '';
+ $options['password'] = (isset($options['password'])) ? $options['password'] : '';
+ $options['select'] = (isset($options['select'])) ? (bool) $options['select'] : true;
+ $options['port'] = (isset($options['port'])) ? (int) $options['port'] : 1521;
$options['charset'] = (isset($options['charset'])) ? $options['charset'] : 'AL32UTF8';
$options['dateformat'] = (isset($options['dateformat'])) ? $options['dateformat'] : 'RRRR-MM-DD HH24:MI:SS';
@@ -106,7 +162,23 @@ public function connect()
return;
}
- parent::connect();
+ // Perform a number of fatality checks, then return gracefully
+ if (!function_exists('oci_connect'))
+ {
+ throw new JDatabaseExceptionConnecting('The oci8 extension may not be available.', 1);
+ }
+
+ // Connect to the server
+ $user = $this->options['user'];
+ $password = $this->options['password'];
+ $host = $this->options['host'];
+ $port = $this->options['port'];
+ $database = $this->options['database'];
+
+ if (!($this->connection = @oci_connect($user, $password, "//$host:$port/$database")))
+ {
+ throw new JDatabaseExceptionConnecting('Could not connect to the Oracle database using the information provided', 2);
+ }
if (isset($this->options['schema']))
{
@@ -125,9 +197,112 @@ public function connect()
*/
public function disconnect()
{
+ foreach ($this->disconnectHandlers as $h)
+ {
+ call_user_func_array($h, array( &$this));
+ }
+
// Close the connection.
- $this->freeResult();
- unset($this->connection);
+ if (is_resource($this->connection))
+ {
+ oci_close($this->connection);
+ $this->connection = null;
+ }
+ }
+
+ /**
+ * Copies a table with/without it's data in the database.
+ *
+ * @param string $fromTable The name of the database table to copy from.
+ * @param string $toTable The name of the database table to create.
+ * @param boolean $withData Optionally include the data in the new table.
+ *
+ * @return JDatabaseDriverOracle Returns this object to support chaining.
+ *
+ * @since 12.1
+ */
+ public function copyTable($fromTable, $toTable, $withData = false)
+ {
+ $this->connect();
+
+ $fromTable = strtoupper($fromTable);
+ $toTable = strtoupper($toTable);
+
+ $query = $this->getQuery(true);
+
+ // Works as a flag to include/exclude the data in the copied table:
+ if ($withData)
+ {
+ $whereClause = ' where 11 = 11';
+ }
+ else
+ {
+ $whereClause = ' where 11 = 1';
+ }
+
+ $query->setQuery('CREATE TABLE ' . $this->quoteName($toTable) . ' as SELECT * FROM ' . $this->quoteName($fromTable) . $whereClause);
+
+ $this->setQuery($query);
+
+ try
+ {
+ $this->execute();
+ }
+ catch (JDatabaseExceptionExecuting $e)
+ {
+ /**
+ * Code 955 is for when the table already exists
+ * so we can safely ignore that code and catch any others.
+ */
+ if ($e->getCode() !== 955)
+ {
+ throw $e;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Drops an entire database (Use with Caution!).
+ *
+ * Note: The IF EXISTS flag is unused in the Oracle driver.
+ *
+ * @param string $databaseName The name of the database table to drop.
+ * @param boolean $ifExists Optionally specify that the table must exist before it is dropped.
+ *
+ * @return JDatabaseDriver Returns this object to support chaining.
+ *
+ * @since 12.1
+ */
+ public function dropDatabase($databaseName, $ifExists = true)
+ {
+ $this->connect();
+
+ $databaseName = strtoupper($databaseName);
+
+ $query = $this->getQuery(true)
+ ->setQuery('DROP USER ' . $this->quoteName($databaseName) . ' CASCADE');
+
+ $this->setQuery($query);
+
+ try
+ {
+ $this->execute();
+ }
+ catch (JDatabaseExceptionExecuting $e)
+ {
+ /**
+ * Code 1918 is for when the database doesn't exist
+ * so we can safely ignore that code and catch any others.
+ */
+ if ($e->getCode() !== 1918)
+ {
+ throw $e;
+ }
+ }
+
+ return $this;
}
/**
@@ -146,17 +321,44 @@ public function dropTable($tableName, $ifExists = true)
{
$this->connect();
+ $tableName = strtoupper($tableName);
+
$query = $this->getQuery(true)
- ->setQuery('DROP TABLE :tableName');
- $query->bind(':tableName', $tableName);
+ ->setQuery('DROP TABLE ' . $this->quoteName($tableName));
$this->setQuery($query);
- $this->execute();
+ try
+ {
+ $this->execute();
+ }
+ catch (JDatabaseExceptionExecuting $e)
+ {
+ /**
+ * Code 942 is for when the table doesn't exist
+ * so we can safely ignore that code and catch any others.
+ */
+ if ($e->getCode() !== 942)
+ {
+ throw $e;
+ }
+ }
return $this;
}
+ /**
+ * Get the number of affected rows by the last INSERT, UPDATE, REPLACE or DELETE for the previous executed SQL statement.
+ *
+ * @return integer The number of affected rows.
+ *
+ * @since 12.1
+ */
+ public function getAffectedRows()
+ {
+ return $this->numRows;
+ }
+
/**
* Method to get the database collation in use by sampling a text field of a table in the database.
*
@@ -180,6 +382,35 @@ public function getConnectionCollation()
return $this->charset;
}
+ /**
+ * Get the number of returned rows for the previous executed SQL statement.
+ * This command is only valid for statements like SELECT or SHOW that return an actual result set.
+ * To retrieve the number of rows affected by an INSERT, UPDATE, REPLACE or DELETE query, use getAffectedRows().
+ *
+ * @param resource $cursor An optional database cursor resource to extract the row count from.
+ *
+ * @return integer The number of returned rows.
+ *
+ * @since 12.1
+ */
+ public function getNumRows($cursor = null)
+ {
+ return $this->numRows;
+ }
+
+ /**
+ * Returns the Query Type returned by
+ * oci_statement_type() in the setQuery() call.
+ *
+ * @return string The query type
+ *
+ * @since 12.1
+ */
+ public function getQueryType()
+ {
+ return $this->queryType;
+ }
+
/**
* Get a query to run and verify the database is operational.
*
@@ -208,6 +439,33 @@ public function getDateFormat()
return $this->dateformat;
}
+ /**
+ * Get a new iterator on the current query.
+ *
+ * @param string $column An option column to use as the iterator key.
+ * @param string $class The class of object that is returned.
+ *
+ * @return JDatabaseIterator A new database iterator.
+ *
+ * @since 12.1
+ * @throws RuntimeException
+ */
+ public function getIterator($column = null, $class = 'stdClass')
+ {
+ // Derive the class name from the driver.
+ $iteratorClass = 'JDatabaseIterator' . ucfirst($this->name);
+
+ // Make sure we have an iterator class for this driver.
+ if (!class_exists($iteratorClass))
+ {
+ // If it doesn't exist we are at an impasse so throw an exception.
+ throw new JDatabaseExceptionUnsupported(sprintf('class *%s* is not defined', $iteratorClass));
+ }
+
+ // Return a new iterator
+ return new $iteratorClass($this->execute(), $column, $class, $this->toLower, $this->returnLobs);
+ }
+
/**
* Shows the table CREATE statement that creates the given tables.
*
@@ -226,17 +484,32 @@ public function getTableCreate($tables)
$this->connect();
$result = array();
+ $type = 'TABLE';
$query = $this->getQuery(true)
- ->select('dbms_metadata.get_ddl(:type, :tableName)')
+ ->select('dbms_metadata.get_ddl(:type, :tableName, :schema)')
->from('dual')
- ->bind(':type', 'TABLE');
+ ->bind(':type', $type);
// Sanitize input to an array and iterate over the list.
settype($tables, 'array');
+ $defaultSchema = strtoupper($this->options['user']);
foreach ($tables as $table)
{
- $query->bind(':tableName', $table);
+ $table = strtoupper($table);
+ $parts = explode('.', $table);
+
+ if (count($parts) === 1)
+ {
+ $query->bind(':tableName', $table);
+ $query->bind(':schema', $defaultSchema);
+ }
+ elseif (count($parts) === 2)
+ {
+ $query->bind(':tableName', $parts[1]);
+ $query->bind(':schema', $parts[0]);
+ }
+
$this->setQuery($query);
$statement = (string) $this->loadResult();
$result[$table] = $statement;
@@ -263,17 +536,11 @@ public function getTableColumns($table, $typeOnly = true)
$columns = array();
$query = $this->getQuery(true);
- $fieldCasing = $this->getOption(PDO::ATTR_CASE);
-
- $this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);
-
- $table = strtoupper($table);
-
$query->select('*');
$query->from('ALL_TAB_COLUMNS');
$query->where('table_name = :tableName');
- $prefixedTable = str_replace('#__', strtoupper($this->tablePrefix), $table);
+ $prefixedTable = strtoupper(str_replace('#__', $this->tablePrefix, $table));
$query->bind(':tableName', $prefixedTable);
$this->setQuery($query);
$fields = $this->loadObjectList();
@@ -282,20 +549,31 @@ public function getTableColumns($table, $typeOnly = true)
{
foreach ($fields as $field)
{
- $columns[$field->COLUMN_NAME] = $field->DATA_TYPE;
+ if ($this->useLowercaseFieldNames())
+ {
+ $columns[strtolower($field->column_name)] = $field->data_type;
+ }
+ else
+ {
+ $columns[$field->COLUMN_NAME] = $field->DATA_TYPE;
+ }
}
}
else
{
foreach ($fields as $field)
{
- $columns[$field->COLUMN_NAME] = $field;
- $columns[$field->COLUMN_NAME]->Default = null;
+ if ($this->useLowercaseFieldNames())
+ {
+ $columns[strtolower($field->column_name)] = $field;
+ }
+ else
+ {
+ $columns[$field->COLUMN_NAME] = $field;
+ }
}
}
- $this->setOption(PDO::ATTR_CASE, $fieldCasing);
-
return $columns;
}
@@ -315,21 +593,15 @@ public function getTableKeys($table)
$query = $this->getQuery(true);
- $fieldCasing = $this->getOption(PDO::ATTR_CASE);
-
- $this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);
-
$table = strtoupper($table);
$query->select('*')
- ->from('ALL_CONSTRAINTS')
+ ->from('ALL_CONSTRAINTS NATURAL JOIN ALL_CONS_COLUMNS')
->where('table_name = :tableName')
->bind(':tableName', $table);
$this->setQuery($query);
$keys = $this->loadObjectList();
- $this->setOption(PDO::ATTR_CASE, $fieldCasing);
-
return $keys;
}
@@ -363,6 +635,7 @@ public function getTableList($databaseName = null, $includeDatabaseName = false)
if ($databaseName)
{
+ $databaseName = strtoupper($databaseName);
$query->where('owner = :database')
->bind(':database', $databaseName);
}
@@ -399,6 +672,20 @@ public function getVersion()
return $this->loadResult();
}
+ /**
+ * Method to get the auto-incremented value from the last INSERT statement.
+ *
+ * @return mixed The value of the auto-increment field from the last inserted row.
+ * If the value is greater than maximal int value, it will return a string.
+ *
+ * @since 12.1
+ */
+ public function insertid()
+ {
+ // Not really supported on Oracle:
+ return null;
+ }
+
/**
* Select a database for use.
*
@@ -416,6 +703,66 @@ public function select($database)
return true;
}
+ /**
+ * Sets the SQL statement string for later execution.
+ *
+ * @param mixed $query The SQL statement to set either as a JDatabaseQuery object or a string.
+ * @param integer $offset The affected row offset to set.
+ * @param integer $limit The maximum affected rows to set.
+ *
+ * @return JDatabaseDriver This object to support method chaining.
+ *
+ * @since 12.1
+ */
+ public function setQuery($query, $offset = null, $limit = null)
+ {
+ $this->connect();
+
+ $this->freeResult();
+
+ if (is_string($query))
+ {
+ // Allows taking advantage of bound variables in a direct query:
+ $query = $this->getQuery(true)->setQuery($query);
+ }
+
+ if ($query instanceof JDatabaseQueryLimitable && !is_null($offset) && !is_null($limit))
+ {
+ $query = $query->processLimit($query, $limit, $offset);
+ }
+
+ // Create a stringified version of the query (with prefixes replaced):
+ $sql = $this->replacePrefix((string) $query);
+
+ // Use the stringified version in the prepare call:
+ $this->prepared = oci_parse($this->connection, $sql);
+
+ $this->queryType = oci_statement_type($this->prepared);
+
+ // Store reference to the original JDatabaseQuery instance within the class.
+ // This is important since binding variables depends on it within execute():
+ parent::setQuery($query, $offset, $limit);
+
+ return $this;
+ }
+
+ /**
+ * Sets the Oracle Commit Mode.
+ *
+ * Mainly needed when using the transaction
+ * methods within the driver.
+ *
+ * @param int $mode Oracle Commit Mode
+ *
+ * @return boolean
+ *
+ * @since 12.1
+ */
+ public function setCommitMode($mode = OCI_COMMIT_ON_SUCCESS)
+ {
+ $this->commitMode = $mode;
+ }
+
/**
* Sets the Oracle Date Format for the session
* Default date format for Oracle is = DD-MON-RR
@@ -470,59 +817,299 @@ public function setUtf()
}
/**
- * Locks a table in the database.
+ * Method to escape a string for usage in an SQL statement.
*
- * @param string $table The name of the table to unlock.
+ * Oracle escaping reference:
+ * http://www.orafaq.com/wiki/SQL_FAQ#How_does_one_escape_special_characters_when_writing_SQL_queries.3F
*
- * @return JDatabaseDriverOracle Returns this object to support chaining.
+ * SQLite escaping notes:
+ * http://www.sqlite.org/faq.html#q14
+ *
+ * Method body is as implemented by the Zend Framework
+ *
+ * Note: Using query objects with bound variables is
+ * preferable to the below.
+ *
+ * @param string $text The string to be escaped.
+ * @param boolean $extra Unused optional parameter to provide extra escaping.
+ *
+ * @return string The escaped string.
*
* @since 12.1
- * @throws RuntimeException
*/
- public function lockTable($table)
+ public function escape($text, $extra = false)
{
- $this->setQuery('LOCK TABLE ' . $this->quoteName($table) . ' IN EXCLUSIVE MODE')->execute();
+ if (is_int($text) || is_float($text))
+ {
+ return $text;
+ }
- return $this;
+ $text = str_replace("'", "''", $text);
+
+ return addcslashes($text, "\000\n\r\\\032");
}
/**
- * Renames a table in the database.
+ * Method to get an array of the result set rows from the database query where each row is an associative array
+ * of ['field_name' => 'row_value']. The array of rows can optionally be keyed by a field name, but defaults to
+ * a sequential numeric array.
*
- * @param string $oldTable The name of the table to be renamed
- * @param string $newTable The new name for the table.
- * @param string $backup Not used by Oracle.
- * @param string $prefix Not used by Oracle.
+ * NOTE: Chosing to key the result array by a non-unique field name can result in unwanted
+ * behavior and should be avoided.
*
- * @return JDatabaseDriverOracle Returns this object to support chaining.
+ * @param string $key The name of a field on which to key the result array.
+ * @param string $column An optional column name. Instead of the whole row, only this column value will be in
+ * the result array.
*
- * @since 12.1
+ * @return mixed The return value or null if the query failed.
+ *
+ * @since 11.1
* @throws RuntimeException
*/
- public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
+ public function loadAssocList($key = null, $column = null)
{
- $this->setQuery('RENAME ' . $oldTable . ' TO ' . $newTable)->execute();
+ if (!empty($key))
+ {
+ if ($this->useLowercaseFieldNames())
+ {
+ $key = strtolower($key);
+ }
+ else
+ {
+ $key = strtoupper($key);
+ }
+ }
- return $this;
+ if (!empty($column))
+ {
+ if ($this->useLowercaseFieldNames())
+ {
+ $column = strtolower($column);
+ }
+ else
+ {
+ $column = strtoupper($column);
+ }
+ }
+
+ return parent::loadAssocList($key, $column);
}
/**
- * Unlocks tables in the database.
+ * Method to get an array of the result set rows from the database query where each row is an object. The array
+ * of objects can optionally be keyed by a field name, but defaults to a sequential numeric array.
*
- * @return JDatabaseDriverOracle Returns this object to support chaining.
+ * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted
+ * behavior and should be avoided.
*
- * @since 12.1
+ * @param string $key The name of a field on which to key the result array.
+ * @param string $class The class name to use for the returned row objects.
+ *
+ * @return mixed The return value or null if the query failed.
+ *
+ * @since 11.1
* @throws RuntimeException
*/
- public function unlockTables()
+ public function loadObjectList($key = '', $class = 'stdClass')
{
- $this->setQuery('COMMIT')->execute();
+ if (!empty($key))
+ {
+ if ($this->useLowercaseFieldNames())
+ {
+ $key = strtolower($key);
+ }
+ else
+ {
+ $key = strtoupper($key);
+ }
+ }
+
+ return parent::loadObjectList($key, $class);
+ }
+
+ /**
+ * Locks a table in the database.
+ *
+ * @param string $table The name of the table to unlock.
+ *
+ * @return JDatabaseDriverOracle Returns this object to support chaining.
+ *
+ * @since 12.1
+ * @throws RuntimeException
+ */
+ public function lockTable($table)
+ {
+ $table = strtoupper($table);
+
+ $this->setQuery('LOCK TABLE ' . $this->quoteName($table) . ' IN EXCLUSIVE MODE')->execute();
+
+ return $this;
+ }
+
+ /**
+ * Execute the SQL statement.
+ *
+ * @return mixed A database cursor resource on success, boolean false on failure.
+ *
+ * @since 12.1
+ * @throws RuntimeException
+ * @throws Exception
+ */
+ public function execute()
+ {
+ $this->connect();
+
+ // Take a local copy so that we don't modify the original query and cause issues later
+ $query = $this->replacePrefix((string) $this->sql);
+
+ if (!is_resource($this->connection))
+ {
+ JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database');
+ throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum);
+ }
+
+ // Increment the query counter.
+ $this->count++;
+
+ // Reset the error values.
+ $this->errorNum = 0;
+ $this->errorMsg = '';
+
+ // If debugging is enabled then let's log the query.
+ if ($this->debug)
+ {
+ // Add the query to the object queue.
+ $this->log[] = $query;
+
+ JLog::add($query, JLog::DEBUG, 'databasequery');
+
+ $this->timings[] = microtime(true);
+ }
+
+ // Execute the query.
+ $this->executed = false;
+
+ if (is_resource($this->prepared))
+ {
+ // Bind the variables:
+ if ($this->sql instanceof JDatabaseQueryPreparable)
+ {
+ $bounded = $this->sql->getBounded();
+
+ foreach ($bounded as $key => $obj)
+ {
+ oci_bind_by_name($this->prepared, $key, $obj->value, $obj->length, $obj->dataType);
+ }
+ }
+
+ $this->executed = @oci_execute($this->prepared, $this->commitMode);
+ }
+
+ if ($this->debug)
+ {
+ $this->timings[] = microtime(true);
+
+ if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
+ {
+ $this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+ }
+ else
+ {
+ $this->callStacks[] = debug_backtrace();
+ }
+ }
+
+ // If an error occurred handle it.
+ if (!$this->executed)
+ {
+ // Get the error number and message before we execute any more queries.
+ $errorNum = $this->getErrorNumber();
+ $errorMsg = $this->getErrorMessage($query);
+
+ // Check if the server was disconnected.
+ if (!$this->connected())
+ {
+ try
+ {
+ // Attempt to reconnect.
+ $this->connection = null;
+ $this->connect();
+ }
+ // If connect fails, ignore that exception and throw the normal exception.
+ catch (RuntimeException $e)
+ {
+ // Get the error number and message.
+ $this->errorNum = $this->getErrorNumber();
+ $this->errorMsg = $this->getErrorMessage($query);
+
+ // Throw the normal query exception.
+ JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');
+
+ throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum, $e);
+ }
+
+ // Since we were able to reconnect, run the query again.
+ return $this->execute();
+ }
+ // The server was not disconnected.
+ else
+ {
+ // Get the error number and message from before we tried to reconnect.
+ $this->errorNum = $errorNum;
+ $this->errorMsg = $errorMsg;
+
+ // Throw the normal query exception.
+ JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');
+
+ throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum);
+ }
+ }
+
+ $this->numRows = (int) oci_num_rows($this->prepared);
+
+ return $this->prepared;
+ }
+
+ /**
+ * Renames a table in the database.
+ *
+ * @param string $oldTable The name of the table to be renamed
+ * @param string $newTable The new name for the table.
+ * @param string $backup Not used by Oracle.
+ * @param string $prefix Not used by Oracle.
+ *
+ * @return JDatabaseDriverOracle Returns this object to support chaining.
+ *
+ * @since 12.1
+ * @throws RuntimeException
+ */
+ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
+ {
+ $oldTable = strtoupper($oldTable);
+ $newTable = strtoupper($newTable);
+
+ $this->setQuery('RENAME ' . $this->quoteName($oldTable) . ' TO ' . $this->quoteName($newTable))->execute();
+
+ return $this;
+ }
+
+ /**
+ * Unlocks tables in the database.
+ *
+ * @return JDatabaseDriverOracle Returns this object to support chaining.
+ *
+ * @since 12.1
+ * @throws RuntimeException
+ */
+ public function unlockTables()
+ {
+ $this->setQuery('COMMIT')->execute();
return $this;
}
/**
- * Test to see if the PDO ODBC connector is available.
+ * Test to see if the oci8 functions are available.
*
* @return boolean True on success, false otherwise.
*
@@ -530,7 +1117,121 @@ public function unlockTables()
*/
public static function isSupported()
{
- return class_exists('PDO') && in_array('oci', PDO::getAvailableDrivers());
+ return function_exists('oci_connect');
+ }
+
+ /**
+ * Determines if the connection to the server is active.
+ *
+ * @return boolean True if connected to the database engine.
+ *
+ * @since 12.1
+ */
+ public function connected()
+ {
+ // Flag to prevent recursion into this function.
+ static $checkingConnected = false;
+
+ if ($checkingConnected)
+ {
+ // Reset this flag and throw an exception.
+ $checkingConnected = true;
+ die('Recursion trying to check if connected.');
+ }
+
+ // Backup the query state.
+ $query = $this->sql;
+ $limit = $this->limit;
+ $offset = $this->offset;
+ $prepared = $this->prepared;
+
+ try
+ {
+ // Set the checking connection flag.
+ $checkingConnected = true;
+
+ // Run a simple query to check the connection.
+ $this->setQuery($this->getConnectedQuery());
+ $status = (bool) $this->loadResult();
+ }
+ // If we catch an exception here, we must not be connected.
+ catch (Exception $e)
+ {
+ $status = false;
+ }
+
+ // Restore the query state.
+ $this->sql = $query;
+ $this->limit = $limit;
+ $this->offset = $offset;
+ $this->prepared = $prepared;
+ $checkingConnected = false;
+
+ return $status;
+ }
+
+ /**
+ * Create a new database using information from $options object, obtaining query string
+ * from protected member.
+ *
+ * For Oracle, it differs compared to MySQL. Instead of creating new databases within
+ * the overall MySQL RDBMS, in Oracle the RDBMS = Database. Within that Database Instance
+ * you can have multiple "schemas" which are equivalent to Oracle Users within the system.
+ * These schemas are basically the same as the different databases that can be created in
+ * MySQL. So here, the db_name provided will be used as the new Oracle USER and db_user
+ * will more or less be ignored. An additional parameter named db_password must be included
+ * in order for the new user to have a password set upon creation.
+ *
+ * @param stdClass $options Object used to pass user and database name to database driver.
+ * This object must have "db_name" and "db_password" set for Oracle.
+ * @param boolean $utf True if the database supports the UTF-8 character set.
+ *
+ * @return JDatabaseDriver Returns this object to support chaining.
+ *
+ * @since 12.2
+ * @throws RuntimeException
+ */
+ public function createDatabase($options, $utf = true)
+ {
+ if (is_null($options))
+ {
+ throw new RuntimeException('$options object must not be null.');
+ }
+ elseif (empty($options->db_name))
+ {
+ throw new RuntimeException('$options object must have db_name set.');
+ }
+ elseif (empty($options->db_password))
+ {
+ throw new RuntimeException('$options object must have db_password set.');
+ }
+
+ $options->db_user = $options->db_name;
+
+ try
+ {
+ $this->setQuery($this->getCreateDatabaseQuery($options, $utf))->execute();
+
+ $this->setQuery('GRANT create session TO ' . $this->quoteName($options->db_name))->execute();
+ $this->setQuery('GRANT create table TO ' . $this->quoteName($options->db_name))->execute();
+ $this->setQuery('GRANT create view TO ' . $this->quoteName($options->db_name))->execute();
+ $this->setQuery('GRANT create any trigger TO ' . $this->quoteName($options->db_name))->execute();
+ $this->setQuery('GRANT create any procedure TO ' . $this->quoteName($options->db_name))->execute();
+ $this->setQuery('GRANT create sequence TO ' . $this->quoteName($options->db_name))->execute();
+ $this->setQuery('GRANT create synonym TO ' . $this->quoteName($options->db_name))->execute();
+ }
+ catch (JDatabaseExceptionExecuting $e)
+ {
+ /**
+ * Error 1920 gets thrown when the user already exists:
+ */
+ if ($e->getCode() !== 1920)
+ {
+ throw $e;
+ }
+ }
+
+ return $this;
}
/**
@@ -632,7 +1333,7 @@ public function replacePrefix($query, $prefix = '#__')
*
* @return void
*
- * @since 12.3
+ * @since 12.2
* @throws RuntimeException
*/
public function transactionCommit($toSavepoint = false)
@@ -641,12 +1342,17 @@ public function transactionCommit($toSavepoint = false)
if (!$toSavepoint || $this->transactionDepth <= 1)
{
- parent::transactionCommit($toSavepoint);
- }
- else
- {
- $this->transactionDepth--;
+ if (oci_commit($this->connection))
+ {
+ // Reset internal values:
+ $this->transactionDepth = 0;
+ $this->setCommitMode(OCI_COMMIT_ON_SUCCESS);
+ }
+
+ return;
}
+
+ $this->transactionDepth--;
}
/**
@@ -656,7 +1362,7 @@ public function transactionCommit($toSavepoint = false)
*
* @return void
*
- * @since 12.3
+ * @since 12.2
* @throws RuntimeException
*/
public function transactionRollback($toSavepoint = false)
@@ -665,17 +1371,22 @@ public function transactionRollback($toSavepoint = false)
if (!$toSavepoint || $this->transactionDepth <= 1)
{
- parent::transactionRollback($toSavepoint);
- }
- else
- {
- $savepoint = 'SP_' . ($this->transactionDepth - 1);
- $this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint));
-
- if ($this->execute())
+ if (oci_rollback($this->connection))
{
- $this->transactionDepth--;
+ // Reset internal values:
+ $this->transactionDepth = 0;
+ $this->setCommitMode(OCI_COMMIT_ON_SUCCESS);
}
+
+ return;
+ }
+
+ $savepoint = 'SP_' . ($this->transactionDepth - 1);
+ $this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint));
+
+ if ($this->execute())
+ {
+ $this->transactionDepth--;
}
}
@@ -686,7 +1397,7 @@ public function transactionRollback($toSavepoint = false)
*
* @return void
*
- * @since 12.3
+ * @since 12.2
* @throws RuntimeException
*/
public function transactionStart($asSavepoint = false)
@@ -695,7 +1406,10 @@ public function transactionStart($asSavepoint = false)
if (!$asSavepoint || !$this->transactionDepth)
{
- return parent::transactionStart($asSavepoint);
+ $this->setCommitMode(OCI_NO_AUTO_COMMIT);
+ $this->transactionDepth = 1;
+
+ return;
}
$savepoint = 'SP_' . $this->transactionDepth;
@@ -707,6 +1421,119 @@ public function transactionStart($asSavepoint = false)
}
}
+ /**
+ * Indicates whether to use lowercase
+ * field names throughout the class or not.
+ *
+ * @return bool
+ */
+ public function useLowercaseFieldNames()
+ {
+ return $this->toLower;
+ }
+
+ /**
+ * Method to fetch a row from the result set cursor as an array.
+ *
+ * @param mixed $cursor The optional result set cursor from which to fetch the row.
+ *
+ * @return mixed Either the next row from the result set or false if there are no more rows.
+ *
+ * @since 12.1
+ */
+ protected function fetchArray($cursor = null)
+ {
+ $mode = $this->getMode(true);
+
+ $row = oci_fetch_array($cursor ? $cursor : $this->prepared, $mode);
+
+ // Update Number of Rows Value:
+ $this->numRows = (int) oci_num_rows($cursor ? $cursor : $this->prepared);
+
+ return $row;
+ }
+
+ /**
+ * Method to fetch a row from the result set cursor as an associative array.
+ *
+ * @param mixed $cursor The optional result set cursor from which to fetch the row.
+ *
+ * @return mixed Either the next row from the result set or false if there are no more rows.
+ *
+ * @since 12.1
+ */
+ protected function fetchAssoc($cursor = null)
+ {
+ $mode = $this->getMode();
+
+ $row = oci_fetch_array($cursor ? $cursor : $this->prepared, $mode);
+
+ if ($row && $this->useLowercaseFieldNames())
+ {
+ $row = array_change_key_case($row);
+ }
+
+ // Update Number of Rows Value:
+ $this->numRows = (int) oci_num_rows($cursor ? $cursor : $this->prepared);
+
+ return $row;
+ }
+
+ /**
+ * Method to fetch a row from the result set cursor as an object.
+ *
+ * @param mixed $cursor The optional result set cursor from which to fetch the row.
+ * @param string $class The class name to use for the returned row object.
+ *
+ * @return mixed Either the next row from the result set or false if there are no more rows.
+ *
+ * @since 12.1
+ */
+ protected function fetchObject($cursor = null, $class = 'stdClass')
+ {
+ $row = $this->fetchAssoc($cursor);
+
+ if ($row)
+ {
+ if ($class !== 'stdClass')
+ {
+ $row = new $class($row);
+ }
+ else
+ {
+ $row = (object) $row;
+ }
+ }
+
+ return $row;
+ }
+
+ /**
+ * Method to free up the memory used for the result set.
+ *
+ * @param mixed $cursor The optional result set cursor from which to fetch the row.
+ *
+ * @return void
+ *
+ * @since 12.1
+ */
+ protected function freeResult($cursor = null)
+ {
+ $this->executed = false;
+
+ if (is_resource($cursor))
+ {
+ oci_free_statement($cursor);
+ $cursor = null;
+ }
+
+ if (is_resource($this->prepared))
+ {
+ oci_free_statement($this->prepared);
+ $this->prepared = null;
+ }
+ }
+
/**
* Get the query strings to alter the character set and collation of a table.
*
@@ -722,8 +1549,100 @@ public function getAlterTableCharacterSet($tableName)
}
/**
- * Return the query string to create new Database.
- * Each database driver, other than MySQL, need to override this member to return correct string.
+ * Sets the $toLower variable to true
+ * so that field names will be created
+ * using lowercase values.
+ *
+ * @return void
+ */
+ public function toLower()
+ {
+ $this->toLower = true;
+ }
+
+ /**
+ * Sets the $toLower variable to false
+ * so that field names will be created
+ * using uppercase values.
+ *
+ * @return void
+ */
+ public function toUpper()
+ {
+ $this->toLower = false;
+ }
+
+ /**
+ * Sets the $returnLobs variable to true
+ * so that LOB object values will be
+ * returned rather than an OCI-Lob Object.
+ *
+ * @return void
+ */
+ public function returnLobValues()
+ {
+ $this->returnLobs = true;
+ }
+
+ /**
+ * Sets the $returnLobs variable to false
+ * so that OCI-Lob Objects will be returned.
+ *
+ * @return void
+ */
+ public function returnLobObjects()
+ {
+ $this->returnLobs = false;
+ }
+
+ /**
+ * Depending on the value for $returnLobs,
+ * this method returns the proper constant
+ * combinations to be passed to the oci* functions
+ *
+ * @param bool $numeric Assoc or Numeric Mode
+ *
+ * @return int
+ */
+ public function getMode($numeric = false)
+ {
+ if ($numeric === false)
+ {
+ if ($this->returnLobs)
+ {
+ $mode = OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS;
+ }
+ else
+ {
+ $mode = OCI_ASSOC+OCI_RETURN_NULLS;
+ }
+ }
+ else
+ {
+ if ($this->returnLobs)
+ {
+ $mode = OCI_NUM+OCI_RETURN_NULLS+OCI_RETURN_LOBS;
+ }
+ else
+ {
+ $mode = OCI_NUM+OCI_RETURN_NULLS;
+ }
+ }
+
+ return $mode;
+ }
+
+ /**
+ * Return the query string to create new User/Database in Oracle.
+ *
+ * For the Oracle drivers, db_user is ignored and db_name is the main field
+ * that is used. Simply set db_user to be the same as db_name when passing in
+ * the $options object.
+ *
+ * Optionally, you may also include the "db_default_tablespace" and "db_temporary_tablespace"
+ * attributes and those will be used when creating the user (these must already be created in
+ * the Oracle RDBMS before being used!). A quota for the permanent tablespace may also be optionally set
+ * using "db_default_tablespace_quota".
*
* @param stdClass $options Object used to pass user and database name to database driver.
* This object must have "db_name" and "db_user" set.
@@ -735,6 +1654,83 @@ public function getAlterTableCharacterSet($tableName)
*/
protected function getCreateDatabaseQuery($options, $utf)
{
- return 'CREATE DATABASE ' . $this->quoteName($options->db_name);
+ $options->db_name = strtoupper($options->db_name);
+ $options->db_user = $options->db_name;
+
+ $defaultPermanentTablespaceQuery = "select PROPERTY_VALUE
+ from database_properties
+ where property_name = 'DEFAULT_PERMANENT_TABLESPACE'";
+
+ $defaultTemporaryTablespaceQuery = "select PROPERTY_VALUE
+ from database_properties
+ where property_name = 'DEFAULT_TEMP_TABLESPACE'";
+
+ $defaultPermanentTablespace = $this->setQuery($defaultPermanentTablespaceQuery)->loadResult();
+ $defaultTemporaryTablespace = $this->setQuery($defaultTemporaryTablespaceQuery)->loadResult();
+
+ // Set Tablespace Options with defaults if needed:
+ $options->db_default_tablespace = (isset($options->db_default_tablespace)) ? $options->db_default_tablespace : $defaultPermanentTablespace;
+ $options->db_temporary_tablespace = (isset($options->db_temporary_tablespace)) ? $options->db_temporary_tablespace : $defaultTemporaryTablespace;
+
+ // Set Tablespace Quota Options with defaults if needed:
+ $options->db_default_tablespace_quota = (isset($options->db_default_tablespace_quota)) ? $options->db_default_tablespace_quota : 'UNLIMITED';
+
+ // Setup the clauses to be added into the query:
+ $defaultTablespaceClause = ' DEFAULT TABLESPACE ' . $this->quoteName($options->db_default_tablespace);
+ $temporaryTablespaceClause = ' TEMPORARY TABLESPACE ' . $this->quoteName($options->db_temporary_tablespace);
+ $defaultTablespaceQuotaClause = ' QUOTA ' . $options->db_default_tablespace_quota . ' ON ' . $this->quoteName($options->db_default_tablespace);
+
+ return 'CREATE USER ' . $this->quoteName($options->db_name) .
+ ' IDENTIFIED BY ' . $this->quoteName($options->db_password) .
+ $defaultTablespaceClause .
+ $temporaryTablespaceClause .
+ $defaultTablespaceQuotaClause;
+ }
+
+ /**
+ * Return the actual SQL Error number
+ *
+ * @return integer The SQL Error number
+ *
+ * @since 3.4.6
+ */
+ protected function getErrorNumber()
+ {
+ $error = oci_error($this->prepared);
+
+ if ($error !== false)
+ {
+ return $error['code'];
+ }
+
+ return 0;
+ }
+
+ /**
+ * Return the actual SQL Error message
+ *
+ * @param string $query The SQL Query that fails
+ *
+ * @return string The SQL Error message
+ *
+ * @since 3.4.6
+ */
+ protected function getErrorMessage($query)
+ {
+ $error = oci_error($this->prepared);
+
+ if ($error !== false)
+ {
+ // Replace the Databaseprefix with `#__` if we are not in Debug
+ if (!$this->debug)
+ {
+ $errorMessage = str_replace($this->tablePrefix, '#__', $error['message']);
+ $query = str_replace($this->tablePrefix, '#__', $query);
+ }
+
+ return $error['message'] . ' SQL=' . $query;
+ }
+
+ return '';
}
}
diff --git a/libraries/joomla/database/driver/pdo.php b/libraries/joomla/database/driver/pdo.php
index 4dacafa9c0fde..e575f47ccf10e 100644
--- a/libraries/joomla/database/driver/pdo.php
+++ b/libraries/joomla/database/driver/pdo.php
@@ -647,6 +647,35 @@ public function getNumRows($cursor = null)
}
}
+ /**
+ * Returns the Query Type
+ *
+ * Currently only a rough implementation
+ * and may not always be accurate.
+ *
+ * @return string The query type
+ *
+ * @since 12.1
+ */
+ public function getQueryType()
+ {
+ $query = $this->getQuery();
+
+ // Create a stringified version of the query (with prefixes replaced):
+ $sql = $this->replacePrefix((string) $query);
+
+ $firstSpace = strpos($sql, ' ');
+ $queryType = strtoupper(substr($sql, 0, $firstSpace));
+
+ if ($queryType === 'WITH')
+ {
+ // Fudge things a bit here and assume a SELECT:
+ $queryType = 'SELECT';
+ }
+
+ return $queryType;
+ }
+
/**
* Method to get the auto-incremented value from the last INSERT statement.
*
diff --git a/libraries/joomla/database/driver/pdooracle.php b/libraries/joomla/database/driver/pdooracle.php
new file mode 100644
index 0000000000000..2a4360aa98578
--- /dev/null
+++ b/libraries/joomla/database/driver/pdooracle.php
@@ -0,0 +1,1122 @@
+ PDO::CASE_LOWER
+ );
+ }
+
+ $this->charset = $options['charset'];
+ $this->dateformat = $options['dateformat'];
+
+ // Finalize initialisation
+ parent::__construct($options);
+ }
+
+ /**
+ * Destructor.
+ *
+ * @since 12.1
+ */
+ public function __destruct()
+ {
+ $this->freeResult();
+ unset($this->connection);
+ }
+
+ /**
+ * Connects to the database if needed.
+ *
+ * @return void Returns void if the database connected successfully.
+ *
+ * @since 12.1
+ * @throws RuntimeException
+ */
+ public function connect()
+ {
+ if ($this->connection)
+ {
+ return;
+ }
+
+ parent::connect();
+
+ if (isset($this->options['schema']))
+ {
+ $this->setQuery('ALTER SESSION SET CURRENT_SCHEMA = ' . $this->quoteName($this->options['schema']))->execute();
+ }
+
+ $this->setDateFormat($this->dateformat);
+ }
+
+ /**
+ * Disconnects the database.
+ *
+ * @return void
+ *
+ * @since 12.1
+ */
+ public function disconnect()
+ {
+ // Close the connection.
+ $this->freeResult();
+ unset($this->connection);
+ }
+
+ /**
+ * Copies a table with/without it's data in the database.
+ *
+ * @param string $fromTable The name of the database table to copy from.
+ * @param string $toTable The name of the database table to create.
+ * @param boolean $withData Optionally include the data in the new table.
+ *
+ * @return JDatabaseDriverOracle Returns this object to support chaining.
+ *
+ * @since 12.1
+ */
+ public function copyTable($fromTable, $toTable, $withData = false)
+ {
+ $this->connect();
+
+ $fromTable = strtoupper($fromTable);
+ $toTable = strtoupper($toTable);
+
+ $query = $this->getQuery(true);
+
+ // Works as a flag to include/exclude the data in the copied table:
+ if ($withData)
+ {
+ $whereClause = ' where 11 = 11';
+ }
+ else
+ {
+ $whereClause = ' where 11 = 1';
+ }
+
+ $query->setQuery('CREATE TABLE ' . $this->quoteName($toTable) . ' as SELECT * FROM ' . $this->quoteName($fromTable) . $whereClause);
+
+ $this->setQuery($query);
+
+ try
+ {
+ $this->execute();
+ }
+ catch (JDatabaseExceptionExecuting $e)
+ {
+ /**
+ * Code 955 is for when the table already exists
+ * so we can safely ignore that code and catch any others.
+ */
+ if ($e->getCode() !== 955)
+ {
+ throw $e;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Drops an entire database (Use with Caution!).
+ *
+ * Note: The IF EXISTS flag is unused in the Oracle driver.
+ *
+ * @param string $databaseName The name of the database table to drop.
+ * @param boolean $ifExists Optionally specify that the table must exist before it is dropped.
+ *
+ * @return JDatabaseDriver Returns this object to support chaining.
+ *
+ * @since 12.1
+ */
+ public function dropDatabase($databaseName, $ifExists = true)
+ {
+ $this->connect();
+
+ $databaseName = strtoupper($databaseName);
+
+ $query = $this->getQuery(true)
+ ->setQuery('DROP USER ' . $this->quoteName($databaseName) . ' CASCADE');
+
+ $this->setQuery($query);
+
+ try
+ {
+ $this->execute();
+ }
+ catch (JDatabaseExceptionExecuting $e)
+ {
+ /**
+ * Code 1918 is for when the database doesn't exist
+ * so we can safely ignore that code and catch any others.
+ */
+ if ($e->getCode() !== 1918)
+ {
+ throw $e;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Drops a table from the database.
+ *
+ * Note: The IF EXISTS flag is unused in the Oracle driver.
+ *
+ * @param string $tableName The name of the database table to drop.
+ * @param boolean $ifExists Optionally specify that the table must exist before it is dropped.
+ *
+ * @return JDatabaseDriverOracle Returns this object to support chaining.
+ *
+ * @since 12.1
+ */
+ public function dropTable($tableName, $ifExists = true)
+ {
+ $this->connect();
+
+ $tableName = strtoupper($tableName);
+
+ $query = $this->getQuery(true)
+ ->setQuery('DROP TABLE ' . $this->quoteName($tableName));
+
+ $this->setQuery($query);
+
+ try
+ {
+ $this->execute();
+ }
+ catch (JDatabaseExceptionExecuting $e)
+ {
+ /**
+ * Code 942 is for when the table doesn't exist
+ * so we can safely ignore that code and catch any others.
+ */
+ if ($e->getCode() !== 942)
+ {
+ throw $e;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Method to get the database collation in use by sampling a text field of a table in the database.
+ *
+ * @return mixed The collation in use by the database or boolean false if not supported.
+ *
+ * @since 12.1
+ */
+ public function getCollation()
+ {
+ return $this->charset;
+ }
+
+ /**
+ * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
+ * reporting this value please return an empty string.
+ *
+ * @return string
+ */
+ public function getConnectionCollation()
+ {
+ return $this->charset;
+ }
+
+ /**
+ * Get a query to run and verify the database is operational.
+ *
+ * @return string The query to check the health of the DB.
+ *
+ * @since 12.2
+ */
+ public function getConnectedQuery()
+ {
+ return 'SELECT 1 FROM dual';
+ }
+
+ /**
+ * Returns the current date format
+ * This method should be useful in the case that
+ * somebody actually wants to use a different
+ * date format and needs to check what the current
+ * one is to see if it needs to be changed.
+ *
+ * @return string The current date format
+ *
+ * @since 12.1
+ */
+ public function getDateFormat()
+ {
+ return $this->dateformat;
+ }
+
+ /**
+ * Shows the table CREATE statement that creates the given tables.
+ *
+ * Note: You must have the correct privileges before this method
+ * will return usable results!
+ *
+ * @param mixed $tables A table name or a list of table names.
+ *
+ * @return array A list of the create SQL for the tables.
+ *
+ * @since 12.1
+ * @throws RuntimeException
+ */
+ public function getTableCreate($tables)
+ {
+ $this->connect();
+
+ $result = array();
+ $type = 'TABLE';
+ $query = $this->getQuery(true)
+ ->select('dbms_metadata.get_ddl(:type, :tableName, :schema)')
+ ->from('dual')
+ ->bind(':type', $type);
+
+ // Sanitize input to an array and iterate over the list.
+ settype($tables, 'array');
+
+ $defaultSchema = strtoupper($this->options['user']);
+ foreach ($tables as $table)
+ {
+ $table = strtoupper($table);
+ $parts = explode('.', $table);
+
+ if (count($parts) === 1)
+ {
+ $query->bind(':tableName', $table);
+ $query->bind(':schema', $defaultSchema);
+ }
+ elseif (count($parts) === 2)
+ {
+ $query->bind(':tableName', $parts[1]);
+ $query->bind(':schema', $parts[0]);
+ }
+
+ $this->setQuery($query);
+ $statement = $this->loadResult();
+
+ if (is_resource($statement))
+ {
+ $statement = stream_get_contents($statement);
+ }
+
+ $result[$table] = $statement;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Retrieves field information about a given table.
+ *
+ * @param string $table The name of the database table.
+ * @param boolean $typeOnly True to only return field types.
+ *
+ * @return array An array of fields for the database table.
+ *
+ * @since 12.1
+ * @throws RuntimeException
+ */
+ public function getTableColumns($table, $typeOnly = true)
+ {
+ $this->connect();
+
+ $columns = array();
+ $query = $this->getQuery(true);
+
+ $query->select('*');
+ $query->from('ALL_TAB_COLUMNS');
+ $query->where('table_name = :tableName');
+
+ $prefixedTable = strtoupper(str_replace('#__', $this->tablePrefix, $table));
+ $query->bind(':tableName', $prefixedTable);
+ $this->setQuery($query);
+ $fields = $this->loadObjectList();
+
+ if ($typeOnly)
+ {
+ foreach ($fields as $field)
+ {
+ if ($this->useLowercaseFieldNames())
+ {
+ $columns[strtolower($field->column_name)] = $field->data_type;
+ }
+ else
+ {
+ $columns[$field->COLUMN_NAME] = $field->DATA_TYPE;
+ }
+ }
+ }
+ else
+ {
+ foreach ($fields as $field)
+ {
+ if ($this->useLowercaseFieldNames())
+ {
+ $columns[strtolower($field->column_name)] = $field;
+ }
+ else
+ {
+ $columns[$field->COLUMN_NAME] = $field;
+ }
+ }
+ }
+
+ return $columns;
+ }
+
+ /**
+ * Get the details list of keys for a table.
+ *
+ * @param string $table The name of the table.
+ *
+ * @return array An array of the column specification for the table.
+ *
+ * @since 12.1
+ * @throws RuntimeException
+ */
+ public function getTableKeys($table)
+ {
+ $this->connect();
+
+ $query = $this->getQuery(true);
+
+ $table = strtoupper($table);
+ $query->select('*')
+ ->from('ALL_CONSTRAINTS NATURAL JOIN ALL_CONS_COLUMNS')
+ ->where('table_name = :tableName')
+ ->bind(':tableName', $table);
+
+ $this->setQuery($query);
+ $keys = $this->loadObjectList();
+
+ return $keys;
+ }
+
+ /**
+ * Method to get an array of all tables in the database (schema).
+ *
+ * @param string $databaseName The database (schema) name
+ * @param boolean $includeDatabaseName Whether to include the schema name in the results
+ *
+ * @return array An array of all the tables in the database.
+ *
+ * @since 12.1
+ * @throws RuntimeException
+ */
+ public function getTableList($databaseName = null, $includeDatabaseName = false)
+ {
+ $this->connect();
+
+ $query = $this->getQuery(true);
+
+ if ($includeDatabaseName)
+ {
+ $query->select('owner, table_name');
+ }
+ else
+ {
+ $query->select('table_name');
+ }
+
+ $query->from('all_tables');
+
+ if ($databaseName)
+ {
+ $databaseName = strtoupper($databaseName);
+ $query->where('owner = :database')
+ ->bind(':database', $databaseName);
+ }
+
+ $query->order('table_name');
+
+ $this->setQuery($query);
+
+ if ($includeDatabaseName)
+ {
+ $tables = $this->loadAssocList();
+ }
+ else
+ {
+ $tables = $this->loadColumn();
+ }
+
+ return $tables;
+ }
+
+ /**
+ * Get the version of the database connector.
+ *
+ * @return string The database connector version.
+ *
+ * @since 12.1
+ */
+ public function getVersion()
+ {
+ $this->connect();
+
+ $this->setQuery("select value from nls_database_parameters where parameter = 'NLS_RDBMS_VERSION'");
+
+ return $this->loadResult();
+ }
+
+ /**
+ * Method to get an array of the result set rows from the database query where each row is an associative array
+ * of ['field_name' => 'row_value']. The array of rows can optionally be keyed by a field name, but defaults to
+ * a sequential numeric array.
+ *
+ * NOTE: Chosing to key the result array by a non-unique field name can result in unwanted
+ * behavior and should be avoided.
+ *
+ * @param string $key The name of a field on which to key the result array.
+ * @param string $column An optional column name. Instead of the whole row, only this column value will be in
+ * the result array.
+ *
+ * @return mixed The return value or null if the query failed.
+ *
+ * @since 11.1
+ * @throws RuntimeException
+ */
+ public function loadAssocList($key = null, $column = null)
+ {
+ if (!empty($key))
+ {
+ if ($this->useLowercaseFieldNames())
+ {
+ $key = strtolower($key);
+ }
+ else
+ {
+ $key = strtoupper($key);
+ }
+ }
+
+ if (!empty($column))
+ {
+ if ($this->useLowercaseFieldNames())
+ {
+ $column = strtolower($column);
+ }
+ else
+ {
+ $column = strtoupper($column);
+ }
+ }
+
+ return parent::loadAssocList($key, $column);
+ }
+
+ /**
+ * Method to get an array of the result set rows from the database query where each row is an object. The array
+ * of objects can optionally be keyed by a field name, but defaults to a sequential numeric array.
+ *
+ * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted
+ * behavior and should be avoided.
+ *
+ * @param string $key The name of a field on which to key the result array.
+ * @param string $class The class name to use for the returned row objects.
+ *
+ * @return mixed The return value or null if the query failed.
+ *
+ * @since 11.1
+ * @throws RuntimeException
+ */
+ public function loadObjectList($key = '', $class = 'stdClass')
+ {
+ if (!empty($key))
+ {
+ if ($this->useLowercaseFieldNames())
+ {
+ $key = strtolower($key);
+ }
+ else
+ {
+ $key = strtoupper($key);
+ }
+ }
+
+ return parent::loadObjectList($key, $class);
+ }
+
+ /**
+ * Select a database for use.
+ *
+ * @param string $database The name of the database to select for use.
+ *
+ * @return boolean True if the database was successfully selected.
+ *
+ * @since 12.1
+ * @throws RuntimeException
+ */
+ public function select($database)
+ {
+ $this->connect();
+
+ return true;
+ }
+
+ /**
+ * Sets the Oracle Date Format for the session
+ * Default date format for Oracle is = DD-MON-RR
+ * The default date format for this driver is:
+ * 'RRRR-MM-DD HH24:MI:SS' since it is the format
+ * that matches the MySQL one used within most Joomla
+ * tables.
+ *
+ * @param string $dateFormat Oracle Date Format String
+ *
+ * @return boolean
+ *
+ * @since 12.1
+ */
+ public function setDateFormat($dateFormat = 'DD-MON-RR')
+ {
+ $this->connect();
+
+ $this->setQuery("ALTER SESSION SET NLS_DATE_FORMAT = '$dateFormat'");
+
+ if (!$this->execute())
+ {
+ return false;
+ }
+
+ $this->setQuery("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = '$dateFormat'");
+
+ if (!$this->execute())
+ {
+ return false;
+ }
+
+ $this->dateformat = $dateFormat;
+
+ return true;
+ }
+
+ /**
+ * Set the connection to use UTF-8 character encoding.
+ *
+ * Returns false automatically for the Oracle driver since
+ * you can only set the character set when the connection
+ * is created.
+ *
+ * @return boolean True on success.
+ *
+ * @since 12.1
+ */
+ public function setUtf()
+ {
+ return false;
+ }
+
+ /**
+ * Locks a table in the database.
+ *
+ * @param string $table The name of the table to unlock.
+ *
+ * @return JDatabaseDriverOracle Returns this object to support chaining.
+ *
+ * @since 12.1
+ * @throws RuntimeException
+ */
+ public function lockTable($table)
+ {
+ $table = strtoupper($table);
+
+ $this->setQuery('LOCK TABLE ' . $this->quoteName($table) . ' IN EXCLUSIVE MODE')->execute();
+
+ return $this;
+ }
+
+ /**
+ * Renames a table in the database.
+ *
+ * @param string $oldTable The name of the table to be renamed
+ * @param string $newTable The new name for the table.
+ * @param string $backup Not used by Oracle.
+ * @param string $prefix Not used by Oracle.
+ *
+ * @return JDatabaseDriverOracle Returns this object to support chaining.
+ *
+ * @since 12.1
+ * @throws RuntimeException
+ */
+ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
+ {
+ $this->setQuery('RENAME ' . $oldTable . ' TO ' . $newTable)->execute();
+
+ return $this;
+ }
+
+ /**
+ * Unlocks tables in the database.
+ *
+ * @return JDatabaseDriverOracle Returns this object to support chaining.
+ *
+ * @since 12.1
+ * @throws RuntimeException
+ */
+ public function unlockTables()
+ {
+ $this->setQuery('COMMIT')->execute();
+
+ return $this;
+ }
+
+ /**
+ * Test to see if the PDO ODBC connector is available.
+ *
+ * @return boolean True on success, false otherwise.
+ *
+ * @since 12.1
+ */
+ public static function isSupported()
+ {
+ return class_exists('PDO') && in_array('oci', PDO::getAvailableDrivers());
+ }
+
+ /**
+ * Create a new database using information from $options object, obtaining query string
+ * from protected member.
+ *
+ * For Oracle, it differs compared to MySQL. Instead of creating new databases within
+ * the overall MySQL RDBMS, in Oracle the RDBMS = Database. Within that Database Instance
+ * you can have multiple "schemas" which are equivalent to Oracle Users within the system.
+ * These schemas are basically the same as the different databases that can be created in
+ * MySQL. So here, the db_name provided will be used as the new Oracle USER and db_user
+ * will more or less be ignored. An additional parameter named db_password must be included
+ * in order for the new user to have a password set upon creation.
+ *
+ * @param stdClass $options Object used to pass user and database name to database driver.
+ * This object must have "db_name" and "db_password" set for Oracle.
+ * @param boolean $utf True if the database supports the UTF-8 character set.
+ *
+ * @return JDatabaseDriver Returns this object to support chaining.
+ *
+ * @since 12.2
+ * @throws RuntimeException
+ */
+ public function createDatabase($options, $utf = true)
+ {
+ if (is_null($options))
+ {
+ throw new RuntimeException('$options object must not be null.');
+ }
+ elseif (empty($options->db_name))
+ {
+ throw new RuntimeException('$options object must have db_name set.');
+ }
+ elseif (empty($options->db_password))
+ {
+ throw new RuntimeException('$options object must have db_password set.');
+ }
+
+ $options->db_user = $options->db_name;
+
+ try
+ {
+ $this->setQuery($this->getCreateDatabaseQuery($options, $utf))->execute();
+
+ $this->setQuery('GRANT create session TO ' . $this->quoteName($options->db_name))->execute();
+ $this->setQuery('GRANT create table TO ' . $this->quoteName($options->db_name))->execute();
+ $this->setQuery('GRANT create view TO ' . $this->quoteName($options->db_name))->execute();
+ $this->setQuery('GRANT create any trigger TO ' . $this->quoteName($options->db_name))->execute();
+ $this->setQuery('GRANT create any procedure TO ' . $this->quoteName($options->db_name))->execute();
+ $this->setQuery('GRANT create sequence TO ' . $this->quoteName($options->db_name))->execute();
+ $this->setQuery('GRANT create synonym TO ' . $this->quoteName($options->db_name))->execute();
+ }
+ catch (JDatabaseExceptionExecuting $e)
+ {
+ /**
+ * Error 1920 gets thrown when the user already exists:
+ */
+ if ($e->getCode() !== 1920)
+ {
+ throw $e;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * This function replaces a string identifier $prefix with the string held is the
+ * tablePrefix class variable.
+ *
+ * @param string $query The SQL statement to prepare.
+ * @param string $prefix The common table prefix.
+ *
+ * @return string The processed SQL statement.
+ *
+ * @since 11.1
+ */
+ public function replacePrefix($query, $prefix = '#__')
+ {
+ $startPos = 0;
+ $quoteChar = "'";
+ $literal = '';
+
+ $query = trim($query);
+ $n = strlen($query);
+
+ while ($startPos < $n)
+ {
+ $ip = strpos($query, $prefix, $startPos);
+
+ if ($ip === false)
+ {
+ break;
+ }
+
+ $j = strpos($query, "'", $startPos);
+
+ if ($j === false)
+ {
+ $j = $n;
+ }
+
+ $literal .= str_replace($prefix, $this->tablePrefix, substr($query, $startPos, $j - $startPos));
+ $startPos = $j;
+
+ $j = $startPos + 1;
+
+ if ($j >= $n)
+ {
+ break;
+ }
+
+ // Quote comes first, find end of quote
+ while (true)
+ {
+ $k = strpos($query, $quoteChar, $j);
+ $escaped = false;
+
+ if ($k === false)
+ {
+ break;
+ }
+
+ $l = $k - 1;
+
+ while ($l >= 0 && $query{$l} == '\\')
+ {
+ $l--;
+ $escaped = !$escaped;
+ }
+
+ if ($escaped)
+ {
+ $j = $k + 1;
+ continue;
+ }
+
+ break;
+ }
+
+ if ($k === false)
+ {
+ // Error in the query - no end quote; ignore it
+ break;
+ }
+
+ $literal .= substr($query, $startPos, $k - $startPos + 1);
+ $startPos = $k + 1;
+ }
+
+ if ($startPos < $n)
+ {
+ $literal .= substr($query, $startPos, $n - $startPos);
+ }
+
+ return $literal;
+ }
+
+ /**
+ * Sets the $tolower variable to true
+ * so that field names will be created
+ * using lowercase values.
+ *
+ * @return void
+ */
+ public function toLower()
+ {
+ $this->setOption(PDO::ATTR_CASE, PDO::CASE_LOWER);
+ }
+
+ /**
+ * Sets the $tolower variable to false
+ * so that field names will be created
+ * using uppercase values.
+ *
+ * @return void
+ */
+ public function toUpper()
+ {
+ $this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);
+ }
+
+ /**
+ * Method to commit a transaction.
+ *
+ * @param boolean $toSavepoint If true, commit to the last savepoint.
+ *
+ * @return void
+ *
+ * @since 12.3
+ * @throws RuntimeException
+ */
+ public function transactionCommit($toSavepoint = false)
+ {
+ $this->connect();
+
+ if (!$toSavepoint || $this->transactionDepth <= 1)
+ {
+ parent::transactionCommit($toSavepoint);
+ }
+ else
+ {
+ $this->transactionDepth--;
+ }
+ }
+
+ /**
+ * Method to roll back a transaction.
+ *
+ * @param boolean $toSavepoint If true, rollback to the last savepoint.
+ *
+ * @return void
+ *
+ * @since 12.3
+ * @throws RuntimeException
+ */
+ public function transactionRollback($toSavepoint = false)
+ {
+ $this->connect();
+
+ if (!$toSavepoint || $this->transactionDepth <= 1)
+ {
+ parent::transactionRollback($toSavepoint);
+ }
+ else
+ {
+ $savepoint = 'SP_' . ($this->transactionDepth - 1);
+ $this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint));
+
+ if ($this->execute())
+ {
+ $this->transactionDepth--;
+ }
+ }
+ }
+
+ /**
+ * Method to initialize a transaction.
+ *
+ * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created.
+ *
+ * @return void
+ *
+ * @since 12.3
+ * @throws RuntimeException
+ */
+ public function transactionStart($asSavepoint = false)
+ {
+ $this->connect();
+
+ if (!$asSavepoint || !$this->transactionDepth)
+ {
+ return parent::transactionStart($asSavepoint);
+ }
+
+ $savepoint = 'SP_' . $this->transactionDepth;
+ $this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint));
+
+ if ($this->execute())
+ {
+ $this->transactionDepth++;
+ }
+ }
+
+ /**
+ * Indicates whether to use lowercase
+ * field names throughout the class or not.
+ *
+ * @return bool
+ */
+ public function useLowercaseFieldNames()
+ {
+ $this->connect();
+
+ $mode = $this->connection->getAttribute(PDO::ATTR_CASE);
+
+ return ($mode === PDO::CASE_LOWER);
+ }
+
+ /**
+ * Get the query strings to alter the character set and collation of a table.
+ *
+ * @param string $tableName The name of the table
+ *
+ * @return string[] The queries required to alter the table's character set and collation
+ *
+ * @since CMS 3.5.0
+ */
+ public function getAlterTableCharacterSet($tableName)
+ {
+ return array();
+ }
+
+ /**
+ * Return the query string to create new User/Database in Oracle.
+ *
+ * For the Oracle drivers, db_user is ignored and db_name is the main field
+ * that is used. Simply set db_user to be the same as db_name when passing in
+ * the $options object.
+ *
+ * Optionally, you may also include the "db_default_tablespace" and "db_temporary_tablespace"
+ * attributes and those will be used when creating the user (these must already be created in
+ * the Oracle RDBMS before being used!). A quota for the permanent tablespace may also be optionally set
+ * using "db_default_tablespace_quota".
+ *
+ * @param stdClass $options Object used to pass user and database name to database driver.
+ * This object must have "db_name" and "db_user" set.
+ * @param boolean $utf True if the database supports the UTF-8 character set.
+ *
+ * @return string The query that creates database
+ *
+ * @since 12.2
+ */
+ protected function getCreateDatabaseQuery($options, $utf)
+ {
+ $options->db_name = strtoupper($options->db_name);
+ $options->db_user = $options->db_name;
+
+ $defaultPermanentTablespaceQuery = "select PROPERTY_VALUE
+ from database_properties
+ where property_name = 'DEFAULT_PERMANENT_TABLESPACE'";
+
+ $defaultTemporaryTablespaceQuery = "select PROPERTY_VALUE
+ from database_properties
+ where property_name = 'DEFAULT_TEMP_TABLESPACE'";
+
+ $defaultPermanentTablespace = $this->setQuery($defaultPermanentTablespaceQuery)->loadResult();
+ $defaultTemporaryTablespace = $this->setQuery($defaultTemporaryTablespaceQuery)->loadResult();
+
+ // Set Tablespace Options with defaults if needed:
+ $options->db_default_tablespace = (isset($options->db_default_tablespace)) ? $options->db_default_tablespace : $defaultPermanentTablespace;
+ $options->db_temporary_tablespace = (isset($options->db_temporary_tablespace)) ? $options->db_temporary_tablespace : $defaultTemporaryTablespace;
+
+ // Set Tablespace Quota Options with defaults if needed:
+ $options->db_default_tablespace_quota = (isset($options->db_default_tablespace_quota)) ? $options->db_default_tablespace_quota : 'UNLIMITED';
+
+ // Setup the clauses to be added into the query:
+ $defaultTablespaceClause = ' DEFAULT TABLESPACE ' . $this->quoteName($options->db_default_tablespace);
+ $temporaryTablespaceClause = ' TEMPORARY TABLESPACE ' . $this->quoteName($options->db_temporary_tablespace);
+ $defaultTablespaceQuotaClause = ' QUOTA ' . $options->db_default_tablespace_quota . ' ON ' . $this->quoteName($options->db_default_tablespace);
+
+ return 'CREATE USER ' . $this->quoteName($options->db_name) .
+ ' IDENTIFIED BY ' . $this->quoteName($options->db_password) .
+ $defaultTablespaceClause .
+ $temporaryTablespaceClause .
+ $defaultTablespaceQuotaClause;
+ }
+
+ /**
+ * Return the actual SQL Error number
+ *
+ * @return integer The SQL Error number
+ *
+ * @since 3.4.6
+ */
+ protected function getErrorNumber()
+ {
+ // The SQL Error Information
+ $errorInfo = $this->connection->errorInfo();
+
+ // Error Number Info is Actually Contained Here:
+ if (isset($errorInfo[1]) && is_int($errorInfo[1]))
+ {
+ return $errorInfo[1];
+ }
+
+ // Fallback option (less reliable info):
+ return (int) $this->connection->errorCode();
+ }
+}
diff --git a/libraries/joomla/database/iterator/oracle.php b/libraries/joomla/database/iterator/oracle.php
index 9e9c1d274340c..99f38535b5de0 100644
--- a/libraries/joomla/database/iterator/oracle.php
+++ b/libraries/joomla/database/iterator/oracle.php
@@ -14,6 +14,143 @@
*
* @since 12.1
*/
-class JDatabaseIteratorOracle extends JDatabaseIteratorPdo
+class JDatabaseIteratorOracle extends JDatabaseIterator
{
+ /**
+ * Is used to decide whether a result set
+ * should generate lowercase field names
+ *
+ * @var boolean
+ */
+ protected $toLower = true;
+
+ /**
+ * Is used to decide whether a result set
+ * should return the LOB values or the LOB objects
+ *
+ * @var boolean
+ */
+ protected $returnLobs = true;
+
+ /**
+ * Database iterator constructor.
+ *
+ * @param mixed $cursor The database cursor.
+ * @param string $column An option column to use as the iterator key.
+ * @param string $class The class of object that is returned.
+ * @param bool $toLower The class of object that is returned.
+ * @param bool $returnLobs The class of object that is returned.
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($cursor, $column = null, $class = 'stdClass', $toLower = true, $returnLobs = true)
+ {
+ if (!class_exists($class))
+ {
+ throw new InvalidArgumentException(sprintf('new %s(*%s*, cursor)', get_class($this), gettype($class)));
+ }
+
+ $this->cursor = $cursor;
+ $this->class = $class;
+ $this->toLower = (bool) $toLower;
+ $this->returnLobs = (bool) $returnLobs;
+ $this->_column = $column;
+ $this->_fetched = 0;
+ $this->next();
+ }
+
+ /**
+ * Get the number of rows in the result set for the executed SQL given by the cursor.
+ *
+ * @return integer The number of rows in the result set.
+ *
+ * @since 12.1
+ * @see Countable::count()
+ */
+ public function count()
+ {
+ return oci_num_rows($this->cursor);
+ }
+
+ /**
+ * Method to fetch a row from the result set cursor as an object.
+ *
+ * @return mixed Either the next row from the result set or false if there are no more rows.
+ *
+ * @since 12.1
+ */
+ protected function fetchObject()
+ {
+ $mode = $this->getMode();
+
+ $row = oci_fetch_array($this->cursor, $mode);
+
+ if ($row && $this->toLower)
+ {
+ $row = array_change_key_case($row);
+ }
+
+ if ($row)
+ {
+ if ($this->class !== 'stdClass')
+ {
+ $row = new $this->class($row);
+ }
+ else
+ {
+ $row = (object) $row;
+ }
+ }
+
+ return $row;
+ }
+
+ /**
+ * Method to free up the memory used for the result set.
+ *
+ * @return void
+ *
+ * @since 12.1
+ */
+ protected function freeResult()
+ {
+ @oci_free_statement($this->cursor);
+ }
+
+ /**
+ * Depending on the value for $returnLobs,
+ * this method returns the proper constant
+ * combinations to be passed to the oci* functions
+ *
+ * @param bool $numeric Assoc or Numeric Mode
+ *
+ * @return int
+ */
+ public function getMode($numeric = false)
+ {
+ if ($numeric === false)
+ {
+ if ($this->returnLobs)
+ {
+ $mode = OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS;
+ }
+ else
+ {
+ $mode = OCI_ASSOC+OCI_RETURN_NULLS;
+ }
+ }
+ else
+ {
+ if ($this->returnLobs)
+ {
+ $mode = OCI_NUM+OCI_RETURN_NULLS+OCI_RETURN_LOBS;
+ }
+ else
+ {
+ $mode = OCI_NUM+OCI_RETURN_NULLS;
+ }
+ }
+
+ return $mode;
+ }
}
diff --git a/libraries/joomla/database/iterator/pdooracle.php b/libraries/joomla/database/iterator/pdooracle.php
new file mode 100644
index 0000000000000..71a676d1d33b0
--- /dev/null
+++ b/libraries/joomla/database/iterator/pdooracle.php
@@ -0,0 +1,19 @@
+bounded = array();
+
+ return $this;
+ }
+
+ // Case 2: Key Provided, null value (unset key from $bounded array)
+ if (is_null($value))
+ {
+ if (isset($this->bounded[$key]))
+ {
+ unset($this->bounded[$key]);
+ }
+
+ return $this;
+ }
+
+ $obj = new stdClass;
+
+ $obj->value = &$value;
+ $obj->dataType = $dataType;
+ $obj->length = $length;
+ $obj->driverOptions = $driverOptions;
+
+ // Case 3: Simply add the Key/Value into the bounded array
+ $this->bounded[$key] = $obj;
+
+ return $this;
+ }
+
+ /**
+ * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is
+ * returned.
+ *
+ * @param mixed $key The bounded variable key to retrieve.
+ *
+ * @return mixed
+ *
+ * @since 12.1
+ */
+ public function &getBounded($key = null)
+ {
+ if (empty($key))
+ {
+ return $this->bounded;
+ }
+ else
+ {
+ if (isset($this->bounded[$key]))
+ {
+ return $this->bounded[$key];
+ }
+ }
+ }
+
+ /**
+ * Clear data from the query or a specific clause of the query.
+ *
+ * @param string $clause Optionally, the name of the clause to clear, or nothing to clear the whole query.
+ *
+ * @return JDatabaseQueryOracle Returns this object to allow chaining.
+ *
+ * @since 12.1
+ */
+ public function clear($clause = null)
+ {
+ switch ($clause)
+ {
+ case null:
+ $this->bounded = array();
+ break;
+ }
+
+ parent::clear($clause);
+
+ return $this;
+ }
+
+ /**
+ * Method to modify a query already in string format with the needed
+ * additions to make the query limited to a particular number of
+ * results, or start at a particular offset. This method is used
+ * automatically by the __toString() method if it detects that the
+ * query implements the JDatabaseQueryLimitable interface.
+ *
+ * @param string $query The query in string format
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return string
+ *
+ * @since 12.1
+ */
+ public function processLimit($query, $limit, $offset = 0)
+ {
+ // Check if we need to mangle the query.
+ if ($limit || $offset)
+ {
+ $query = "SELECT joomla2.*
+ FROM (
+ SELECT joomla1.*, ROWNUM AS joomla_db_rownum
+ FROM (
+ " . $query . "
+ ) joomla1
+ ) joomla2";
+
+ // Check if the limit value is greater than zero.
+ if ($limit > 0)
+ {
+ $query .= ' WHERE joomla2.joomla_db_rownum BETWEEN ' . ($offset + 1) . ' AND ' . ($offset + $limit);
+ }
+ else
+ {
+ // Check if there is an offset and then use this.
+ if ($offset)
+ {
+ $query .= ' WHERE joomla2.joomla_db_rownum > ' . ($offset + 1);
+ }
+ }
+ }
+
+ return $query;
+ }
+
+ /**
+ * Sets the offset and limit for the result set, if the database driver supports it.
+ *
+ * Usage:
+ * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
+ * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
+ *
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return JDatabaseQueryOracle Returns this object to allow chaining.
+ *
+ * @since 12.1
+ */
+ public function setLimit($limit = 0, $offset = 0)
+ {
+ $this->limit = (int) $limit;
+ $this->offset = (int) $offset;
+
+ return $this;
+ }
+}