/**
* Native oci class representing moodle database interface.
+ *
+ * One complete reference for PHP + OCI:
+ * http://www.oracle.com/technology/tech/php/underground-php-oracle-manual.html
*/
class oci_native_moodle_database extends moodle_database {
protected $oci = null;
- protected $bytea_oid = null;
private $last_stmt_error = null; // To store stmt errors and enable get_last_error() to detect them
+ private $commit_status = OCI_COMMIT_ON_SUCCESS; // Autocommit ON by default. Switching to OFF (OCI_DEFAULT)
+ // when playing with transactions
/**
* Detects if all needed PHP stuff installed.
ob_start();
if (empty($this->dboptions['dbpersit'])) {
- $this->oci = oci_connect($this->dbuser, $this->dbpass, $this->dbname, 'UTF-8');
+ $this->oci = oci_connect($this->dbuser, $this->dbpass, $this->dbname, 'AL32UTF8');
} else {
- $this->oci = oci_pconnect($this->dbuser, $this->dbpass, $this->dbname, 'UTF-8');
+ $this->oci = oci_pconnect($this->dbuser, $this->dbpass, $this->dbname, 'AL32UTF8');
}
$dberr = ob_get_contents();
ob_end_clean();
protected function parse_query($sql) {
$stmt = oci_parse($this->oci, $sql);
- if ($stmt === false) {
+ if ($stmt == false) {
throw new dml_connection_exception('Can not parse sql query'); //TODO: maybe add better info
}
return $stmt;
AND TABLE_NAME LIKE '$prefix%' ESCAPE '\\'";
$this->query_start($sql, null, SQL_QUERY_AUX);
$stmt = $this->parse_query($sql);
- $result = oci_execute($stmt);
+ oci_num_fields($stmt);
+ $result = oci_execute($stmt, $this->commit_status);
$this->query_end($result, $stmt);
$records = null;
oci_fetch_all($stmt, $records, 0, -1, OCI_ASSOC);
ORDER BY i.INDEX_NAME, c.COLUMN_POSITION";
$stmt = $this->parse_query($sql);
- $result = oci_execute($stmt);
+ $result = oci_execute($stmt, $this->commit_status);
$this->query_end($result, $stmt);
$records = null;
oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
$this->query_start($sql, null, SQL_QUERY_AUX);
$stmt = $this->parse_query($sql);
- $result = oci_execute($stmt);
+ $result = oci_execute($stmt, $this->commit_status);
$this->query_end($result, $stmt);
$records = null;
oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
return $this->columns[$table];
}
+ /**
+ * This function will handle all the records before being inserted/updated to DB for Oracle
+ * installations. This is because the "special feature" of Oracle where the empty string is
+ * equal to NULL and this presents a problem with all our currently NOT NULL default '' fields.
+ *
+ * Once Moodle DB will be free of this sort of false NOT NULLS, this hack could be removed safely
+ *
+ * Note that this function is 100% private and should be used, exclusively by DML functions
+ * in this file. Also, this is considered a DIRTY HACK to be removed when possible. (stronk7)
+ *
+ * This function is private and must not be used outside this driver at all
+ *
+ * @param $table string the table where the record is going to be inserted/updated (without prefix)
+ * @param $field string the field where the record is going to be inserted/updated
+ * @param $value mixed the value to be inserted/updated
+ */
+ private function oracle_dirty_hack ($table, $field, $value) {
+
+ /// Get metadata
+ $columns = $this->get_columns($table);
+ if (!isset($columns[$field])) {
+ return $value;
+ }
+ $column = $columns[$field];
+
+ /// For Oracle DB, empty strings are converted to NULLs in DB
+ /// and this breaks a lot of NOT NULL columns currenty Moodle. In the future it's
+ /// planned to move some of them to NULL, if they must accept empty values and this
+ /// piece of code will become less and less used. But, for now, we need it.
+ /// What we are going to do is to examine all the data being inserted and if it's
+ /// an empty string (NULL for Oracle) and the field is defined as NOT NULL, we'll modify
+ /// such data in the best form possible ("0" for booleans and numbers and " " for the
+ /// rest of strings. It isn't optimal, but the only way to do so.
+ /// In the oppsite, when retrieving records from Oracle, we'll decode " " back to
+ /// empty strings to allow everything to work properly. DIRTY HACK.
+
+ /// If the field ins't VARCHAR or CLOB, skip
+ if ($column->meta_type != 'C' and $column->meta_type != 'X') {
+ return $value;
+ }
+
+ /// If the field isn't NOT NULL, skip (it's nullable, so accept empty-null values)
+ if (!$column->not_null) {
+ return $value;
+ }
+
+ /// If the value isn't empty, skip
+ if (!empty($value)) {
+ return $value;
+ }
+
+ /// Now, we have one empty value, going to be inserted to one NOT NULL, VARCHAR2 or CLOB field
+ /// Try to get the best value to be inserted
+
+ /// The '0' string doesn't need any transformation, skip
+ if ($value === '0') {
+ return $value;
+ }
+
+ /// Transformations start
+ if (gettype($value) == 'boolean') {
+ return '0'; /// Transform false to '0' that evaluates the same for PHP
+
+ } else if (gettype($value) == 'integer') {
+ return '0'; /// Transform 0 to '0' that evaluates the same for PHP
+
+ } else if (gettype($value) == 'NULL') {
+ return '0'; /// Transform NULL to '0' that evaluates the same for PHP
+
+ } else if ($value === '') {
+ return ' '; /// Transform '' to ' ' that DONT'T EVALUATE THE SAME
+ /// (we'll transform back again on get_records_XXX functions and others)!!
+ }
+
+ /// Fail safe to original value
+ return $value;
+ }
+
/**
* Is db in unicode mode?
* @return bool
WHERE PARAMETER = 'NLS_CHARACTERSET'";
$this->query_start($sql, null, SQL_QUERY_AUX);
$stmt = $this->parse_query($sql);
- $result = oci_execute($stmt);
+ $result = oci_execute($stmt, $this->commit_status);
$this->query_end($result, $stmt);
$records = null;
oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_COLUMN);
$this->query_start($sql, null, SQL_QUERY_STRUCTURE);
$stmt = $this->parse_query($sql);
- $result = oci_execute($stmt);
+ $result = oci_execute($stmt, $this->commit_status);
$this->query_end($result, $stmt);
oci_free_statement($stmt);
return true;
}
- protected function bind_params($stmt, array $params=null, $tablename=null) {
+ protected function bind_params(&$stmt, array $params=null, $tablename=null) {
$descriptors = array();
if ($params) {
$columns = array();
$this->query_start($sql, $params, SQL_QUERY_UPDATE);
$stmt = $this->parse_query($sql);
$this->bind_params($stmt, $params);
- $result = oci_execute($stmt);
+ $result = oci_execute($stmt, $this->commit_status);
$this->query_end($result, $stmt);
oci_free_statement($stmt);
list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
if ($limitfrom and $limitnum) {
- $sql = "SELECT oracle_o.*
- FROM (SELECT oracle_i.*, rownum AS oracle_rownum
- FROM ($sql) oracle_i
- ) oracle_o
- WHERE rownum <= :oracle_max AND oracle_rownum > :oracle_min";
+ $rawsql = "SELECT oracle_o.*
+ FROM (SELECT oracle_i.*, rownum AS oracle_rownum
+ FROM ($sql) oracle_i
+ ) oracle_o
+ WHERE rownum <= :oracle_max AND oracle_rownum > :oracle_min";
$params['oracle_max'] = $limitfrom + $limitnum;
$params['oracle_min'] = $limitfrom;
} else if ($limitfrom and !$limitnum) {
- $sql = "SELECT oracle_o.*
- FROM (SELECT oracle_i.*, rownum AS oracle_rownum
- FROM ($sql) oracle_i
- ) oracle_o
- WHERE oracle_rownum > :oracle_min";
+ $rawsql = "SELECT oracle_o.*
+ FROM (SELECT oracle_i.*, rownum AS oracle_rownum
+ FROM ($sql) oracle_i
+ ) oracle_o
+ WHERE oracle_rownum > :oracle_min";
$params['oracle_min'] = $limitfrom;
} else if (!$limitfrom and $limitnum) {
- $sql = "SELECT *
- FROM ($sql)
- WHERE rownum <= :oracle_max";
+ $rawsql = "SELECT *
+ FROM ($sql)
+ WHERE rownum <= :oracle_max";
$params['oracle_max'] = $limitnum;
+
+ } else { // No limitfrom nor limitnum
+ $rawsql = $sql;
}
$this->query_start($sql, $params, SQL_QUERY_SELECT);
- $stmt = $this->parse_query($sql);
+ $stmt = $this->parse_query($rawsql);
$this->bind_params($stmt, $params);
- $result = oci_execute($stmt);
+ $result = oci_execute($stmt, $this->commit_status);
$this->query_end($result, $stmt);
return $this->create_recordset($stmt);
$this->query_start($sql, $params, SQL_QUERY_SELECT);
$stmt = $this->parse_query($sql);
$this->bind_params($stmt, $params);
- $result = oci_execute($stmt);
+ $result = oci_execute($stmt, $this->commit_status);
$this->query_end($result, $stmt);
$records = null;
$this->query_start($sql, $params, SQL_QUERY_SELECT);
$stmt = $this->parse_query($sql);
$this->bind_params($stmt, $params);
- $result = oci_execute($stmt);
+ $result = oci_execute($stmt, $this->commit_status);
$this->query_end($result, $stmt);
$records = null;
if ($returning) {
oci_bind_by_name($stmt, ":oracle_id", $id, -1, SQLT_LNG);
}
- $result = oci_execute($stmt);
+ $result = oci_execute($stmt, $this->commit_status);
$this->free_descriptors($descriptors);
$this->query_end($result, $stmt);
oci_free_statement($stmt);
$dataobject = (object)$dataobject;
}
- $columns = $this->get_columns($table);
-
unset($dataobject->id);
+
+ $columns = $this->get_columns($table);
$cleaned = array();
+ $blobs = array();
+ $clobs = array();
foreach ($dataobject as $field=>$value) {
if (!isset($columns[$field])) {
$this->query_start($sql, $params, SQL_QUERY_UPDATE);
$stmt = $this->parse_query($sql);
$descriptors = $this->bind_params($stmt, $params, $table);
- $result = oci_execute($stmt);
+ $result = oci_execute($stmt, $this->commit_status);
$this->query_end($result, $stmt);
$this->free_descriptors($descriptors);
oci_free_statement($stmt);
$this->query_start($sql, $params, SQL_QUERY_UPDATE);
$stmt = $this->parse_query($sql);
$descriptors = $this->bind_params($stmt, $params, $table);
- $result = oci_execute($stmt);
+ $result = oci_execute($stmt, $this->commit_status);
$this->query_end($result, $stmt);
$this->free_descriptors($descriptors);
oci_free_statement($stmt);
$this->query_start($sql, $params, SQL_QUERY_UPDATE);
$stmt = $this->parse_query($sql);
$this->bind_params($stmt, $params);
- $result = oci_execute($stmt);
+ $result = oci_execute($stmt, $this->commit_status);
$this->query_end($result, $stmt);
oci_free_statement($stmt);
}
}
- function sql_empty() {
+ public function sql_empty() {
return ' ';
}
$sql = "BEGIN";
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$stmt = $this->parse_query($sql);
- $result = oci_execute($stmt);
+ $result = oci_execute($stmt, $this->commit_status);
$this->query_end($result, $stmt);
oci_free_statement($stmt);
return true;
$sql = "COMMIT";
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$stmt = $this->parse_query($sql);
- $result = oci_execute($stmt);
+ $result = oci_execute($stmt, $this->commit_status);
$this->query_end($result, $stmt);
oci_free_statement($stmt);
return true;
$sql = "ROLLBACK";
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$stmt = $this->parse_query($sql);
- $result = oci_execute($stmt);
+ $result = oci_execute($stmt, $this->commit_status);
$this->query_end($result, $stmt);
oci_free_statement($stmt);
return true;