Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/lib/dml/oci_native_moodle_database.php
Go to the documentation of this file.
00001 <?php
00002 
00003 // This file is part of Moodle - http://moodle.org/
00004 //
00005 // Moodle is free software: you can redistribute it and/or modify
00006 // it under the terms of the GNU General Public License as published by
00007 // the Free Software Foundation, either version 3 of the License, or
00008 // (at your option) any later version.
00009 //
00010 // Moodle is distributed in the hope that it will be useful,
00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013 // GNU General Public License for more details.
00014 //
00015 // You should have received a copy of the GNU General Public License
00016 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
00017 
00018 
00028 defined('MOODLE_INTERNAL') || die();
00029 
00030 require_once($CFG->libdir.'/dml/moodle_database.php');
00031 require_once($CFG->libdir.'/dml/oci_native_moodle_recordset.php');
00032 require_once($CFG->libdir.'/dml/oci_native_moodle_temptables.php');
00033 
00040 class oci_native_moodle_database extends moodle_database {
00041 
00042     protected $oci     = null;
00043 
00044     private $last_stmt_error = null; // To store stmt errors and enable get_last_error() to detect them
00045     private $commit_status = null;   // default value initialised in connect method, we need the driver to be present
00046 
00047     private $last_error_reporting; // To handle oci driver default verbosity
00048     private $unique_session_id; // To store unique_session_id. Needed for temp tables unique naming
00049 
00050     private $dblocks_supported = null; // To cache locks support along the connection life
00051     private $bitwise_supported = null; // To cache bitwise operations support along the connection life
00052 
00058     public function driver_installed() {
00059         if (!extension_loaded('oci8')) {
00060             return get_string('ociextensionisnotpresentinphp', 'install');
00061         }
00062         return true;
00063     }
00064 
00070     public function get_dbfamily() {
00071         return 'oracle';
00072     }
00073 
00079     protected function get_dbtype() {
00080         return 'oci';
00081     }
00082 
00088     protected function get_dblibrary() {
00089         return 'native';
00090     }
00091 
00097     public function get_name() {
00098         return get_string('nativeoci', 'install');
00099     }
00100 
00106     public function get_configuration_help() {
00107         return get_string('nativeocihelp', 'install');
00108     }
00109 
00115     public function get_configuration_hints() {
00116         return get_string('databasesettingssub_oci', 'install');
00117     }
00118 
00125     public function diagnose() {
00126         if (!$this->bitwise_supported() or !$this->session_lock_supported()) {
00127             return 'Oracle PL/SQL Moodle support packages are not installed! Database administrator has to execute /lib/dml/oci_native_moodle_package.sql script.';
00128         }
00129         return null;
00130     }
00131 
00144     public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) {
00145         if ($prefix == '' and !$this->external) {
00146             //Enforce prefixes for everybody but mysql
00147             throw new dml_exception('prefixcannotbeempty', $this->get_dbfamily());
00148         }
00149         if (!$this->external and strlen($prefix) > 2) {
00150             //Max prefix length for Oracle is 2cc
00151             $a = (object)array('dbfamily'=>'oracle', 'maxlength'=>2);
00152             throw new dml_exception('prefixtoolong', $a);
00153         }
00154 
00155         $driverstatus = $this->driver_installed();
00156 
00157         if ($driverstatus !== true) {
00158             throw new dml_exception('dbdriverproblem', $driverstatus);
00159         }
00160 
00161         // Autocommit ON by default.
00162         // Switching to OFF (OCI_DEFAULT), when playing with transactions
00163         // please note this thing is not defined if oracle driver not present in PHP
00164         // which means it can not be used as default value of object property!
00165         $this->commit_status = OCI_COMMIT_ON_SUCCESS;
00166 
00167         $this->store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions);
00168         unset($this->dboptions['dbsocket']);
00169 
00170         // NOTE: use of ', ", / and \ is very problematic, even native oracle tools seem to have
00171         //       problems with these, so just forget them and do not report problems into tracker...
00172 
00173         if (empty($this->dbhost)) {
00174             // old style full address (TNS)
00175             $dbstring = $this->dbname;
00176         } else {
00177             if (empty($this->dboptions['dbport'])) {
00178                 $this->dboptions['dbport'] = 1521;
00179             }
00180             $dbstring = '//'.$this->dbhost.':'.$this->dboptions['dbport'].'/'.$this->dbname;
00181         }
00182 
00183         ob_start();
00184         if (empty($this->dboptions['dbpersist'])) {
00185             $this->oci = oci_new_connect($this->dbuser, $this->dbpass, $dbstring, 'AL32UTF8');
00186         } else {
00187             $this->oci = oci_pconnect($this->dbuser, $this->dbpass, $dbstring, 'AL32UTF8');
00188         }
00189         $dberr = ob_get_contents();
00190         ob_end_clean();
00191 
00192 
00193         if ($this->oci === false) {
00194             $this->oci = null;
00195             $e = oci_error();
00196             if (isset($e['message'])) {
00197                 $dberr = $e['message'];
00198             }
00199             throw new dml_connection_exception($dberr);
00200         }
00201 
00202         // get unique session id, to be used later for temp tables stuff
00203         $sql = 'SELECT DBMS_SESSION.UNIQUE_SESSION_ID() FROM DUAL';
00204         $this->query_start($sql, null, SQL_QUERY_AUX);
00205         $stmt = $this->parse_query($sql);
00206         $result = oci_execute($stmt, $this->commit_status);
00207         $this->query_end($result, $stmt);
00208         $records = null;
00209         oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
00210         oci_free_statement($stmt);
00211         $this->unique_session_id = reset($records[0]);
00212 
00213         //note: do not send "ALTER SESSION SET NLS_NUMERIC_CHARACTERS='.,'" !
00214         //      instead fix our PHP code to convert "," to "." properly!
00215 
00216         // Connection stabilised and configured, going to instantiate the temptables controller
00217         $this->temptables = new oci_native_moodle_temptables($this, $this->unique_session_id);
00218 
00219         return true;
00220     }
00221 
00227     public function dispose() {
00228         parent::dispose(); // Call parent dispose to write/close session and other common stuff before closing connection
00229         if ($this->oci) {
00230             oci_close($this->oci);
00231             $this->oci = null;
00232         }
00233     }
00234 
00235 
00244     protected function query_start($sql, array $params=null, $type, $extrainfo=null) {
00245         parent::query_start($sql, $params, $type, $extrainfo);
00246         // oci driver tents to send debug to output, we do not need that ;-)
00247         $this->last_error_reporting = error_reporting(0);
00248     }
00249 
00255     protected function query_end($result, $stmt=null) {
00256         // reset original debug level
00257         error_reporting($this->last_error_reporting);
00258         if ($stmt and $result === false) {
00259             // Look for stmt error and store it
00260             if (is_resource($stmt)) {
00261                 $e = oci_error($stmt);
00262                 if ($e !== false) {
00263                     $this->last_stmt_error = $e['message'];
00264                 }
00265             }
00266             oci_free_statement($stmt);
00267         }
00268         parent::query_end($result);
00269     }
00270 
00275     public function get_server_info() {
00276         static $info = null; // TODO: move to real object property
00277 
00278         if (is_null($info)) {
00279             $this->query_start("--oci_server_version()", null, SQL_QUERY_AUX);
00280             $description = oci_server_version($this->oci);
00281             $this->query_end(true);
00282             preg_match('/(\d+\.)+\d+/', $description, $matches);
00283             $info = array('description'=>$description, 'version'=>$matches[0]);
00284         }
00285 
00286         return $info;
00287     }
00288 
00289     protected function is_min_version($version) {
00290         $server = $this->get_server_info();
00291         $server = $server['version'];
00292         return version_compare($server, $version, '>=');
00293     }
00294 
00302     protected function fix_table_names($sql) {
00303         if (preg_match_all('/\{([a-z][a-z0-9_]*)\}/', $sql, $matches)) {
00304             foreach($matches[0] as $key=>$match) {
00305                 $name = $matches[1][$key];
00306                 if ($this->temptables->is_temptable($name)) {
00307                     $sql = str_replace($match, $this->temptables->get_correct_name($name), $sql);
00308                 } else {
00309                     $sql = str_replace($match, $this->prefix.$name, $sql);
00310                 }
00311             }
00312         }
00313         return $sql;
00314     }
00315 
00320     protected function allowed_param_types() {
00321         return SQL_PARAMS_NAMED;
00322     }
00323 
00328     public function get_last_error() {
00329         $error = false;
00330         // First look for any previously saved stmt error
00331         if (!empty($this->last_stmt_error)) {
00332             $error = $this->last_stmt_error;
00333             $this->last_stmt_error = null;
00334         } else { // Now try connection error
00335             $e = oci_error($this->oci);
00336             if ($e !== false) {
00337                 $error = $e['message'];
00338             }
00339         }
00340         return $error;
00341     }
00342 
00349     protected function parse_query($sql) {
00350         $stmt = oci_parse($this->oci, $sql);
00351         if ($stmt == false) {
00352             throw new dml_connection_exception('Can not parse sql query'); //TODO: maybe add better info
00353         }
00354         return $stmt;
00355     }
00356 
00363     protected function tweak_param_names($sql, array $params) {
00364         if (empty($params)) {
00365             return array($sql, $params);
00366         }
00367 
00368         $newparams = array();
00369         $searcharr = array(); // search => replace pairs
00370         foreach ($params as $name => $value) {
00371             // Keep the name within the 30 chars limit always (prefixing/replacing)
00372             if (strlen($name) <= 28) {
00373                 $newname = 'o_' . $name;
00374             } else {
00375                 $newname = 'o_' . substr($name, 2);
00376             }
00377             $newparams[$newname] = $value;
00378             $searcharr[':' . $name] = ':' . $newname;
00379         }
00380         // sort by length desc to avoid potential str_replace() overlap
00381         uksort($searcharr, array('oci_native_moodle_database', 'compare_by_length_desc'));
00382 
00383         $sql = str_replace(array_keys($searcharr), $searcharr, $sql);
00384         return array($sql, $newparams);
00385     }
00386 
00391     public function get_tables($usecache=true) {
00392         if ($usecache and $this->tables !== null) {
00393             return $this->tables;
00394         }
00395         $this->tables = array();
00396         $prefix = str_replace('_', "\\_", strtoupper($this->prefix));
00397         $sql = "SELECT TABLE_NAME
00398                   FROM CAT
00399                  WHERE TABLE_TYPE='TABLE'
00400                        AND TABLE_NAME NOT LIKE 'BIN\$%'
00401                        AND TABLE_NAME LIKE '$prefix%' ESCAPE '\\'";
00402         $this->query_start($sql, null, SQL_QUERY_AUX);
00403         $stmt = $this->parse_query($sql);
00404         $result = oci_execute($stmt, $this->commit_status);
00405         $this->query_end($result, $stmt);
00406         $records = null;
00407         oci_fetch_all($stmt, $records, 0, -1, OCI_ASSOC);
00408         oci_free_statement($stmt);
00409         $records = array_map('strtolower', $records['TABLE_NAME']);
00410         foreach ($records as $tablename) {
00411             if (strpos($tablename, $this->prefix) !== 0) {
00412                 continue;
00413             }
00414             $tablename = substr($tablename, strlen($this->prefix));
00415             $this->tables[$tablename] = $tablename;
00416         }
00417 
00418         // Add the currently available temptables
00419         $this->tables = array_merge($this->tables, $this->temptables->get_temptables());
00420 
00421         return $this->tables;
00422     }
00423 
00428     public function get_indexes($table) {
00429         $indexes = array();
00430         $tablename = strtoupper($this->prefix.$table);
00431 
00432         $sql = "SELECT i.INDEX_NAME, i.UNIQUENESS, c.COLUMN_POSITION, c.COLUMN_NAME, ac.CONSTRAINT_TYPE
00433                   FROM ALL_INDEXES i
00434                   JOIN ALL_IND_COLUMNS c ON c.INDEX_NAME=i.INDEX_NAME
00435              LEFT JOIN ALL_CONSTRAINTS ac ON (ac.TABLE_NAME=i.TABLE_NAME AND ac.CONSTRAINT_NAME=i.INDEX_NAME AND ac.CONSTRAINT_TYPE='P')
00436                  WHERE i.TABLE_NAME = '$tablename'
00437               ORDER BY i.INDEX_NAME, c.COLUMN_POSITION";
00438 
00439         $stmt = $this->parse_query($sql);
00440         $result = oci_execute($stmt, $this->commit_status);
00441         $this->query_end($result, $stmt);
00442         $records = null;
00443         oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
00444         oci_free_statement($stmt);
00445 
00446         foreach ($records as $record) {
00447             if ($record['CONSTRAINT_TYPE'] === 'P') {
00448                 //ignore for now;
00449                 continue;
00450             }
00451             $indexname = strtolower($record['INDEX_NAME']);
00452             if (!isset($indexes[$indexname])) {
00453                 $indexes[$indexname] = array('primary' => ($record['CONSTRAINT_TYPE'] === 'P'),
00454                                              'unique'  => ($record['UNIQUENESS'] === 'UNIQUE'),
00455                                              'columns' => array());
00456             }
00457             $indexes[$indexname]['columns'][] = strtolower($record['COLUMN_NAME']);
00458         }
00459 
00460         return $indexes;
00461     }
00462 
00469     public function get_columns($table, $usecache=true) {
00470         if ($usecache and isset($this->columns[$table])) {
00471             return $this->columns[$table];
00472         }
00473 
00474         if (!$table) { // table not specified, return empty array directly
00475             return array();
00476         }
00477 
00478         $this->columns[$table] = array();
00479 
00480         // We give precedence to CHAR_LENGTH for VARCHAR2 columns over WIDTH because the former is always
00481         // BYTE based and, for cross-db operations, we want CHAR based results. See MDL-29415
00482         $sql = "SELECT CNAME, COLTYPE, nvl(CHAR_LENGTH, WIDTH) AS WIDTH, SCALE, PRECISION, NULLS, DEFAULTVAL
00483                   FROM COL c
00484              LEFT JOIN USER_TAB_COLUMNS u ON (u.TABLE_NAME = c.TNAME AND u.COLUMN_NAME = c.CNAME AND u.DATA_TYPE = 'VARCHAR2')
00485                  WHERE TNAME = UPPER('{" . $table . "}')
00486               ORDER BY COLNO";
00487 
00488         list($sql, $params, $type) = $this->fix_sql_params($sql, null);
00489 
00490         $this->query_start($sql, null, SQL_QUERY_AUX);
00491         $stmt = $this->parse_query($sql);
00492         $result = oci_execute($stmt, $this->commit_status);
00493         $this->query_end($result, $stmt);
00494         $records = null;
00495         oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
00496         oci_free_statement($stmt);
00497 
00498         if (!$records) {
00499             return array();
00500         }
00501         foreach ($records as $rawcolumn) {
00502             $rawcolumn = (object)$rawcolumn;
00503 
00504             $info = new stdClass();
00505             $info->name = strtolower($rawcolumn->CNAME);
00506             $matches = null;
00507 
00508             if ($rawcolumn->COLTYPE === 'VARCHAR2'
00509              or $rawcolumn->COLTYPE === 'VARCHAR'
00510              or $rawcolumn->COLTYPE === 'NVARCHAR2'
00511              or $rawcolumn->COLTYPE === 'NVARCHAR'
00512              or $rawcolumn->COLTYPE === 'CHAR'
00513              or $rawcolumn->COLTYPE === 'NCHAR') {
00514                 //TODO add some basic enum support here
00515                 $info->type          = $rawcolumn->COLTYPE;
00516                 $info->meta_type     = 'C';
00517                 $info->max_length    = $rawcolumn->WIDTH;
00518                 $info->scale         = null;
00519                 $info->not_null      = ($rawcolumn->NULLS === 'NOT NULL');
00520                 $info->has_default   = !is_null($rawcolumn->DEFAULTVAL);
00521                 if ($info->has_default) {
00522 
00523                     // this is hacky :-(
00524                     if ($rawcolumn->DEFAULTVAL === 'NULL') {
00525                         $info->default_value = null;
00526                     } else if ($rawcolumn->DEFAULTVAL === "' ' ") { // Sometimes it's stored with trailing space
00527                         $info->default_value = "";
00528                     } else if ($rawcolumn->DEFAULTVAL === "' '") { // Sometimes it's stored without trailing space
00529                         $info->default_value = "";
00530                     } else {
00531                         $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space
00532                         $info->default_value = substr($info->default_value, 1, strlen($info->default_value)-2); //trim ''
00533                     }
00534                 } else {
00535                     $info->default_value = null;
00536                 }
00537                 $info->primary_key   = false;
00538                 $info->binary        = false;
00539                 $info->unsigned      = null;
00540                 $info->auto_increment= false;
00541                 $info->unique        = null;
00542 
00543             } else if ($rawcolumn->COLTYPE === 'NUMBER') {
00544                 $info->type       = $rawcolumn->COLTYPE;
00545                 $info->max_length = $rawcolumn->PRECISION;
00546                 $info->binary     = false;
00547                 if (!is_null($rawcolumn->SCALE) && $rawcolumn->SCALE == 0) { // null in oracle scale allows decimals => not integer
00548                     // integer
00549                     if ($info->name === 'id') {
00550                         $info->primary_key   = true;
00551                         $info->meta_type     = 'R';
00552                         $info->unique        = true;
00553                         $info->auto_increment= true;
00554                         $info->has_default   = false;
00555                     } else {
00556                         $info->primary_key   = false;
00557                         $info->meta_type     = 'I';
00558                         $info->unique        = null;
00559                         $info->auto_increment= false;
00560                     }
00561                     $info->scale = null;
00562 
00563                 } else {
00564                     //float
00565                     $info->meta_type     = 'N';
00566                     $info->primary_key   = false;
00567                     $info->unsigned      = null;
00568                     $info->auto_increment= false;
00569                     $info->unique        = null;
00570                     $info->scale         = $rawcolumn->SCALE;
00571                 }
00572                 $info->not_null      = ($rawcolumn->NULLS === 'NOT NULL');
00573                 $info->has_default   = !is_null($rawcolumn->DEFAULTVAL);
00574                 if ($info->has_default) {
00575                     $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space
00576                 } else {
00577                     $info->default_value = null;
00578                 }
00579 
00580             } else if ($rawcolumn->COLTYPE === 'FLOAT') {
00581                 $info->type       = $rawcolumn->COLTYPE;
00582                 $info->max_length = (int)($rawcolumn->PRECISION * 3.32193);
00583                 $info->primary_key   = false;
00584                 $info->meta_type     = 'N';
00585                 $info->unique        = null;
00586                 $info->auto_increment= false;
00587                 $info->not_null      = ($rawcolumn->NULLS === 'NOT NULL');
00588                 $info->has_default   = !is_null($rawcolumn->DEFAULTVAL);
00589                 if ($info->has_default) {
00590                     $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space
00591                 } else {
00592                     $info->default_value = null;
00593                 }
00594 
00595             } else if ($rawcolumn->COLTYPE === 'CLOB'
00596                     or $rawcolumn->COLTYPE === 'NCLOB') {
00597                 $info->type          = $rawcolumn->COLTYPE;
00598                 $info->meta_type     = 'X';
00599                 $info->max_length    = -1;
00600                 $info->scale         = null;
00601                 $info->scale         = null;
00602                 $info->not_null      = ($rawcolumn->NULLS === 'NOT NULL');
00603                 $info->has_default   = !is_null($rawcolumn->DEFAULTVAL);
00604                 if ($info->has_default) {
00605                     // this is hacky :-(
00606                     if ($rawcolumn->DEFAULTVAL === 'NULL') {
00607                         $info->default_value = null;
00608                     } else if ($rawcolumn->DEFAULTVAL === "' ' ") { // Sometimes it's stored with trailing space
00609                         $info->default_value = "";
00610                     } else if ($rawcolumn->DEFAULTVAL === "' '") { // Other times it's stored without trailing space
00611                         $info->default_value = "";
00612                     } else {
00613                         $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space
00614                         $info->default_value = substr($info->default_value, 1, strlen($info->default_value)-2); //trim ''
00615                     }
00616                 } else {
00617                     $info->default_value = null;
00618                 }
00619                 $info->primary_key   = false;
00620                 $info->binary        = false;
00621                 $info->unsigned      = null;
00622                 $info->auto_increment= false;
00623                 $info->unique        = null;
00624 
00625             } else if ($rawcolumn->COLTYPE === 'BLOB') {
00626                 $info->type          = $rawcolumn->COLTYPE;
00627                 $info->meta_type     = 'B';
00628                 $info->max_length    = -1;
00629                 $info->scale         = null;
00630                 $info->scale         = null;
00631                 $info->not_null      = ($rawcolumn->NULLS === 'NOT NULL');
00632                 $info->has_default   = !is_null($rawcolumn->DEFAULTVAL);
00633                 if ($info->has_default) {
00634                     // this is hacky :-(
00635                     if ($rawcolumn->DEFAULTVAL === 'NULL') {
00636                         $info->default_value = null;
00637                     } else if ($rawcolumn->DEFAULTVAL === "' ' ") { // Sometimes it's stored with trailing space
00638                         $info->default_value = "";
00639                     } else if ($rawcolumn->DEFAULTVAL === "' '") { // Sometimes it's stored without trailing space
00640                         $info->default_value = "";
00641                     } else {
00642                         $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space
00643                         $info->default_value = substr($info->default_value, 1, strlen($info->default_value)-2); //trim ''
00644                     }
00645                 } else {
00646                     $info->default_value = null;
00647                 }
00648                 $info->primary_key   = false;
00649                 $info->binary        = true;
00650                 $info->unsigned      = null;
00651                 $info->auto_increment= false;
00652                 $info->unique        = null;
00653 
00654             } else {
00655                 // unknown type - sorry
00656                 $info->type          = $rawcolumn->COLTYPE;
00657                 $info->meta_type     = '?';
00658             }
00659 
00660             $this->columns[$table][$info->name] = new database_column_info($info);
00661         }
00662 
00663         return $this->columns[$table];
00664     }
00665 
00673     protected function normalise_value($column, $value) {
00674         if (is_bool($value)) { // Always, convert boolean to int
00675             $value = (int)$value;
00676 
00677         } else if ($column->meta_type == 'B') { // CLOB detected, we return 'blob' array instead of raw value to allow
00678             if (!is_null($value)) {             // binding/executing code later to know about its nature
00679                 $value = array('blob' => $value);
00680             }
00681 
00682         } else if ($column->meta_type == 'X' && strlen($value) > 4000) { // CLOB detected (>4000 optimisation), we return 'clob'
00683             if (!is_null($value)) {                                      // array instead of raw value to allow binding/
00684                 $value = array('clob' => (string)$value);                // executing code later to know about its nature
00685             }
00686 
00687         } else if ($value === '') {
00688             if ($column->meta_type == 'I' or $column->meta_type == 'F' or $column->meta_type == 'N') {
00689                 $value = 0; // prevent '' problems in numeric fields
00690             }
00691         }
00692         return $value;
00693     }
00694 
00704     private function get_limit_sql($sql, array $params = null, $limitfrom=0, $limitnum=0) {
00705 
00706         $limitfrom = (int)$limitfrom;
00707         $limitnum  = (int)$limitnum;
00708         $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom;
00709         $limitnum  = ($limitnum < 0)  ? 0 : $limitnum;
00710 
00711         // TODO: Add the /*+ FIRST_ROWS */ hint if there isn't another hint
00712 
00713         if ($limitfrom and $limitnum) {
00714             $sql = "SELECT oracle_o.*
00715                       FROM (SELECT oracle_i.*, rownum AS oracle_rownum
00716                               FROM ($sql) oracle_i
00717                              WHERE rownum <= :oracle_num_rows
00718                             ) oracle_o
00719                      WHERE oracle_rownum > :oracle_skip_rows";
00720             $params['oracle_num_rows'] = $limitfrom + $limitnum;
00721             $params['oracle_skip_rows'] = $limitfrom;
00722 
00723         } else if ($limitfrom and !$limitnum) {
00724             $sql = "SELECT oracle_o.*
00725                       FROM (SELECT oracle_i.*, rownum AS oracle_rownum
00726                               FROM ($sql) oracle_i
00727                             ) oracle_o
00728                      WHERE oracle_rownum > :oracle_skip_rows";
00729             $params['oracle_skip_rows'] = $limitfrom;
00730 
00731         } else if (!$limitfrom and $limitnum) {
00732             $sql = "SELECT *
00733                       FROM ($sql)
00734                      WHERE rownum <= :oracle_num_rows";
00735             $params['oracle_num_rows'] = $limitnum;
00736         }
00737 
00738         return array($sql, $params);
00739     }
00740 
00756     private function oracle_dirty_hack ($table, $field, $value) {
00757 
00758         // Get metadata
00759         $columns = $this->get_columns($table);
00760         if (!isset($columns[$field])) {
00761             return $value;
00762         }
00763         $column = $columns[$field];
00764 
00765         // !! This paragraph explains behaviour before Moodle 2.0:
00766         //
00767         // For Oracle DB, empty strings are converted to NULLs in DB
00768         // and this breaks a lot of NOT NULL columns currently Moodle. In the future it's
00769         // planned to move some of them to NULL, if they must accept empty values and this
00770         // piece of code will become less and less used. But, for now, we need it.
00771         // What we are going to do is to examine all the data being inserted and if it's
00772         // an empty string (NULL for Oracle) and the field is defined as NOT NULL, we'll modify
00773         // such data in the best form possible ("0" for booleans and numbers and " " for the
00774         // rest of strings. It isn't optimal, but the only way to do so.
00775         // In the opposite, when retrieving records from Oracle, we'll decode " " back to
00776         // empty strings to allow everything to work properly. DIRTY HACK.
00777 
00778         // !! These paragraphs explain the rationale about the change for Moodle 2.0:
00779         //
00780         // Before Moodle 2.0, we only used to apply this DIRTY HACK to NOT NULL columns, as
00781         // stated above, but it causes one problem in NULL columns where both empty strings
00782         // and real NULLs are stored as NULLs, being impossible to differentiate them when
00783         // being retrieved from DB.
00784         //
00785         // So, starting with Moodle 2.0, we are going to apply the DIRTY HACK to all the
00786         // CHAR/CLOB columns no matter of their nullability. That way, when retrieving
00787         // NULLABLE fields we'll get proper empties and NULLs differentiated, so we'll be able
00788         // to rely in NULL/empty/content contents without problems, until now that wasn't
00789         // possible at all.
00790         //
00791         // No breakage with old data is expected as long as at the time of writing this
00792         // (20090922) all the current uses of both sql_empty() and sql_isempty() has been
00793         // revised in 2.0 and all them were being performed against NOT NULL columns,
00794         // where nothing has changed (the DIRTY HACK was already being applied).
00795         //
00796         // !! Conclusions:
00797         //
00798         // From Moodle 2.0 onwards, ALL empty strings in Oracle DBs will be stored as
00799         // 1-whitespace char, ALL NULLs as NULLs and, obviously, content as content. And
00800         // those 1-whitespace chars will be converted back to empty strings by all the
00801         // get_field/record/set() functions transparently and any SQL needing direct handling
00802         // of empties will need to use the sql_empty() and sql_isempty() helper functions.
00803         // MDL-17491.
00804 
00805         // If the field ins't VARCHAR or CLOB, skip
00806         if ($column->meta_type != 'C' and $column->meta_type != 'X') {
00807             return $value;
00808         }
00809 
00810         // If the value isn't empty, skip
00811         if (!empty($value)) {
00812             return $value;
00813         }
00814 
00815         // Now, we have one empty value, going to be inserted to one VARCHAR2 or CLOB field
00816         // Try to get the best value to be inserted
00817 
00818         // The '0' string doesn't need any transformation, skip
00819         if ($value === '0') {
00820             return $value;
00821         }
00822 
00823         // Transformations start
00824         if (gettype($value) == 'boolean') {
00825             return '0'; // Transform false to '0' that evaluates the same for PHP
00826 
00827         } else if (gettype($value) == 'integer') {
00828             return '0'; // Transform 0 to '0' that evaluates the same for PHP
00829 
00830         } else if ($value === '') {
00831             return ' '; // Transform '' to ' ' that DONT'T EVALUATE THE SAME
00832                         // (we'll transform back again on get_records_XXX functions and others)!!
00833         }
00834 
00835         // Fail safe to original value
00836         return $value;
00837     }
00838 
00846     private function compare_by_length_desc($a, $b) {
00847         return strlen($b) - strlen($a);
00848     }
00849 
00854     public function setup_is_unicodedb() {
00855         $sql = "SELECT VALUE
00856                   FROM NLS_DATABASE_PARAMETERS
00857                  WHERE PARAMETER = 'NLS_CHARACTERSET'";
00858         $this->query_start($sql, null, SQL_QUERY_AUX);
00859         $stmt = $this->parse_query($sql);
00860         $result = oci_execute($stmt, $this->commit_status);
00861         $this->query_end($result, $stmt);
00862         $records = null;
00863         oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_COLUMN);
00864         oci_free_statement($stmt);
00865 
00866         return (isset($records['VALUE'][0]) and $records['VALUE'][0] === 'AL32UTF8');
00867     }
00868 
00875     public function change_database_structure($sql) {
00876         $this->reset_caches();
00877 
00878         $this->query_start($sql, null, SQL_QUERY_STRUCTURE);
00879         $stmt = $this->parse_query($sql);
00880         $result = oci_execute($stmt, $this->commit_status);
00881         $this->query_end($result, $stmt);
00882         oci_free_statement($stmt);
00883 
00884         return true;
00885     }
00886 
00887     protected function bind_params($stmt, array $params=null, $tablename=null) {
00888         $descriptors = array();
00889         if ($params) {
00890             $columns = array();
00891             if ($tablename) {
00892                 $columns = $this->get_columns($tablename);
00893             }
00894             foreach($params as $key => $value) {
00895                 // Decouple column name and param name as far as sometimes they aren't the same
00896                 if ($key == 'o_newfieldtoset') { // found case where column and key diverge, handle that
00897                     $columnname   = key($value);    // columnname is the key of the array
00898                     $params[$key] = $value[$columnname]; // set the proper value in the $params array and
00899                     $value        = $value[$columnname]; // set the proper value in the $value variable
00900                 } else {
00901                     $columnname = preg_replace('/^o_/', '', $key); // Default columnname (for DB introspecting is key), but...
00902                 }
00903                 // Continue processing
00904                 // Now, handle already detected LOBs
00905                 if (is_array($value)) { // Let's go to bind special cases (lob descriptors)
00906                     if (isset($value['clob'])) {
00907                         $lob = oci_new_descriptor($this->oci, OCI_DTYPE_LOB);
00908                         oci_bind_by_name($stmt, $key, $lob, -1, SQLT_CLOB);
00909                         $lob->writeTemporary($this->oracle_dirty_hack($tablename, $columnname, $params[$key]['clob']), OCI_TEMP_CLOB);
00910                         $descriptors[] = $lob;
00911                         continue; // Column binding finished, go to next one
00912                     } else if (isset($value['blob'])) {
00913                         $lob = oci_new_descriptor($this->oci, OCI_DTYPE_LOB);
00914                         oci_bind_by_name($stmt, $key, $lob, -1, SQLT_BLOB);
00915                         $lob->writeTemporary($params[$key]['blob'], OCI_TEMP_BLOB);
00916                         $descriptors[] = $lob;
00917                         continue; // Column binding finished, go to next one
00918                     }
00919                 }
00920                 // TODO: Put proper types and length is possible (enormous speedup)
00921                 // Arrived here, continue with standard processing, using metadata if possible
00922                 if (isset($columns[$columnname])) {
00923                     $type = $columns[$columnname]->meta_type;
00924                     $maxlength = $columns[$columnname]->max_length;
00925                 } else {
00926                     $type = '?';
00927                     $maxlength = -1;
00928                 }
00929                 switch ($type) {
00930                     case 'I':
00931                     case 'R':
00932                         // TODO: Optimise
00933                         oci_bind_by_name($stmt, $key, $params[$key]);
00934                         break;
00935 
00936                     case 'N':
00937                     case 'F':
00938                         // TODO: Optimise
00939                         oci_bind_by_name($stmt, $key, $params[$key]);
00940                         break;
00941 
00942                     case 'B':
00943                         // TODO: Only arrive here if BLOB is null: Bind if so, else exception!
00944                         // don't break here
00945 
00946                     case 'X':
00947                         // TODO: Only arrive here if CLOB is null or <= 4000 cc, else exception
00948                         // don't break here
00949 
00950                     default: // Bind as CHAR (applying dirty hack)
00951                         // TODO: Optimise
00952                         oci_bind_by_name($stmt, $key, $this->oracle_dirty_hack($tablename, $columnname, $params[$key]));
00953                 }
00954             }
00955         }
00956         return $descriptors;
00957     }
00958 
00959     protected function free_descriptors($descriptors) {
00960         foreach ($descriptors as $descriptor) {
00961             oci_free_descriptor($descriptor);
00962         }
00963     }
00964 
00974     public static function onespace2empty(&$item, $key=null) {
00975         $item = ($item === ' ') ? '' : $item;
00976         return true;
00977     }
00978 
00987     public function execute($sql, array $params=null) {
00988         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
00989 
00990         if (strpos($sql, ';') !== false) {
00991             throw new coding_exception('moodle_database::execute() Multiple sql statements found or bound parameters not used properly in query!');
00992         }
00993 
00994         list($sql, $params) = $this->tweak_param_names($sql, $params);
00995         $this->query_start($sql, $params, SQL_QUERY_UPDATE);
00996         $stmt = $this->parse_query($sql);
00997         $this->bind_params($stmt, $params);
00998         $result = oci_execute($stmt, $this->commit_status);
00999         $this->query_end($result, $stmt);
01000         oci_free_statement($stmt);
01001 
01002         return true;
01003     }
01004 
01019     public function get_record_sql($sql, array $params=null, $strictness=IGNORE_MISSING) {
01020         $strictness = (int)$strictness;
01021         if ($strictness == IGNORE_MULTIPLE) {
01022             // do not limit here - ORA does not like that
01023             $rs = $this->get_recordset_sql($sql, $params);
01024             $result = false;
01025             foreach ($rs as $rec) {
01026                 $result = $rec;
01027                 break;
01028             }
01029             $rs->close();
01030             return $result;
01031         }
01032         return parent::get_record_sql($sql, $params, $strictness);
01033     }
01034 
01051     public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
01052 
01053         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
01054 
01055         list($rawsql, $params) = $this->get_limit_sql($sql, $params, $limitfrom, $limitnum);
01056 
01057         list($rawsql, $params) = $this->tweak_param_names($rawsql, $params);
01058         $this->query_start($rawsql, $params, SQL_QUERY_SELECT);
01059         $stmt = $this->parse_query($rawsql);
01060         $this->bind_params($stmt, $params);
01061         $result = oci_execute($stmt, $this->commit_status);
01062         $this->query_end($result, $stmt);
01063 
01064         return $this->create_recordset($stmt);
01065     }
01066 
01067     protected function create_recordset($stmt) {
01068         return new oci_native_moodle_recordset($stmt);
01069     }
01070 
01085     public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
01086 
01087         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
01088 
01089         list($rawsql, $params) = $this->get_limit_sql($sql, $params, $limitfrom, $limitnum);
01090 
01091         list($rawsql, $params) = $this->tweak_param_names($rawsql, $params);
01092         $this->query_start($rawsql, $params, SQL_QUERY_SELECT);
01093         $stmt = $this->parse_query($rawsql);
01094         $this->bind_params($stmt, $params);
01095         $result = oci_execute($stmt, $this->commit_status);
01096         $this->query_end($result, $stmt);
01097 
01098         $records = null;
01099         oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
01100         oci_free_statement($stmt);
01101 
01102         $return = array();
01103 
01104         foreach ($records as $row) {
01105             $row = array_change_key_case($row, CASE_LOWER);
01106             unset($row['oracle_rownum']);
01107             array_walk($row, array('oci_native_moodle_database', 'onespace2empty'));
01108             $id = reset($row);
01109             if (isset($return[$id])) {
01110                 $colname = key($row);
01111                 debugging("Did you remember to make the first column something unique in your call to get_records? Duplicate value '$id' found in column '$colname'.", DEBUG_DEVELOPER);
01112             }
01113             $return[$id] = (object)$row;
01114         }
01115 
01116         return $return;
01117     }
01118 
01127     public function get_fieldset_sql($sql, array $params=null) {
01128         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
01129 
01130         list($sql, $params) = $this->tweak_param_names($sql, $params);
01131         $this->query_start($sql, $params, SQL_QUERY_SELECT);
01132         $stmt = $this->parse_query($sql);
01133         $this->bind_params($stmt, $params);
01134         $result = oci_execute($stmt, $this->commit_status);
01135         $this->query_end($result, $stmt);
01136 
01137         $records = null;
01138         oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_COLUMN);
01139         oci_free_statement($stmt);
01140 
01141         $return = reset($records);
01142         array_walk($return, array('oci_native_moodle_database', 'onespace2empty'));
01143 
01144         return $return;
01145     }
01146 
01157     public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) {
01158         if (!is_array($params)) {
01159             $params = (array)$params;
01160         }
01161 
01162         $returning = "";
01163 
01164         if ($customsequence) {
01165             if (!isset($params['id'])) {
01166                 throw new coding_exception('moodle_database::insert_record_raw() id field must be specified if custom sequences used.');
01167             }
01168             $returnid = false;
01169         } else {
01170             unset($params['id']);
01171             if ($returnid) {
01172                 $returning = " RETURNING id INTO :oracle_id"; // crazy name nobody is ever going to use or parameter ;-)
01173             }
01174         }
01175 
01176         if (empty($params)) {
01177             throw new coding_exception('moodle_database::insert_record_raw() no fields found.');
01178         }
01179 
01180         $fields = implode(',', array_keys($params));
01181         $values = array();
01182         foreach ($params as $pname => $value) {
01183             $values[] = ":$pname";
01184         }
01185         $values = implode(',', $values);
01186 
01187         $sql = "INSERT INTO {" . $table . "} ($fields) VALUES ($values)";
01188         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
01189         $sql .= $returning;
01190 
01191         $id = null;
01192 
01193         // note we don't need tweak_param_names() here. Placeholders are safe column names. MDL-28080
01194         // list($sql, $params) = $this->tweak_param_names($sql, $params);
01195         $this->query_start($sql, $params, SQL_QUERY_INSERT);
01196         $stmt = $this->parse_query($sql);
01197         $descriptors = $this->bind_params($stmt, $params, $table);
01198         if ($returning) {
01199             oci_bind_by_name($stmt, ":oracle_id", $id, 10, SQLT_INT);
01200         }
01201         $result = oci_execute($stmt, $this->commit_status);
01202         $this->free_descriptors($descriptors);
01203         $this->query_end($result, $stmt);
01204         oci_free_statement($stmt);
01205 
01206         if (!$returnid) {
01207             return true;
01208         }
01209 
01210         if (!$returning) {
01211             die('TODO - implement oracle 9.2 insert support'); //TODO
01212         }
01213 
01214         return (int)$id;
01215     }
01216 
01229     public function insert_record($table, $dataobject, $returnid=true, $bulk=false) {
01230         $dataobject = (array)$dataobject;
01231 
01232         $columns = $this->get_columns($table);
01233         $cleaned = array();
01234 
01235         foreach ($dataobject as $field=>$value) {
01236             if ($field === 'id') {
01237                 continue;
01238             }
01239             if (!isset($columns[$field])) { // Non-existing table field, skip it
01240                 continue;
01241             }
01242             $column = $columns[$field];
01243             $cleaned[$field] = $this->normalise_value($column, $value);
01244         }
01245 
01246         return $this->insert_record_raw($table, $cleaned, $returnid, $bulk);
01247     }
01248 
01258     public function import_record($table, $dataobject) {
01259         $dataobject = (array)$dataobject;
01260 
01261         $columns = $this->get_columns($table);
01262         $cleaned = array();
01263 
01264         foreach ($dataobject as $field=>$value) {
01265             if (!isset($columns[$field])) {
01266                 continue;
01267             }
01268             $column = $columns[$field];
01269             $cleaned[$field] = $this->normalise_value($column, $value);
01270         }
01271 
01272         return $this->insert_record_raw($table, $cleaned, false, true, true);
01273     }
01274 
01283     public function update_record_raw($table, $params, $bulk=false) {
01284         $params = (array)$params;
01285 
01286         if (!isset($params['id'])) {
01287             throw new coding_exception('moodle_database::update_record_raw() id field must be specified.');
01288         }
01289 
01290         if (empty($params)) {
01291             throw new coding_exception('moodle_database::update_record_raw() no fields found.');
01292         }
01293 
01294         $sets = array();
01295         foreach ($params as $field=>$value) {
01296             if ($field == 'id') {
01297                 continue;
01298             }
01299             $sets[] = "$field = :$field";
01300         }
01301 
01302         $sets = implode(',', $sets);
01303         $sql = "UPDATE {" . $table . "} SET $sets WHERE id=:id";
01304         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
01305 
01306         // note we don't need tweak_param_names() here. Placeholders are safe column names. MDL-28080
01307         // list($sql, $params) = $this->tweak_param_names($sql, $params);
01308         $this->query_start($sql, $params, SQL_QUERY_UPDATE);
01309         $stmt = $this->parse_query($sql);
01310         $descriptors = $this->bind_params($stmt, $params, $table);
01311         $result = oci_execute($stmt, $this->commit_status);
01312         $this->free_descriptors($descriptors);
01313         $this->query_end($result, $stmt);
01314         oci_free_statement($stmt);
01315 
01316         return true;
01317     }
01318 
01332     public function update_record($table, $dataobject, $bulk=false) {
01333         $dataobject = (array)$dataobject;
01334 
01335         $columns = $this->get_columns($table);
01336         $cleaned = array();
01337 
01338         foreach ($dataobject as $field=>$value) {
01339             if (!isset($columns[$field])) {
01340                 continue;
01341             }
01342             $column = $columns[$field];
01343             $cleaned[$field] = $this->normalise_value($column, $value);
01344         }
01345 
01346         $this->update_record_raw($table, $cleaned, $bulk);
01347 
01348         return true;
01349     }
01350 
01362     public function set_field_select($table, $newfield, $newvalue, $select, array $params=null) {
01363 
01364         if ($select) {
01365             $select = "WHERE $select";
01366         }
01367         if (is_null($params)) {
01368             $params = array();
01369         }
01370 
01371         // Get column metadata
01372         $columns = $this->get_columns($table);
01373         $column = $columns[$newfield];
01374 
01375         $newvalue = $this->normalise_value($column, $newvalue);
01376 
01377         list($select, $params, $type) = $this->fix_sql_params($select, $params);
01378 
01379         if (is_bool($newvalue)) {
01380             $newvalue = (int)$newvalue; // prevent "false" problems
01381         }
01382         if (is_null($newvalue)) {
01383             $newsql = "$newfield = NULL";
01384         } else {
01385             // Set the param to array ($newfield => $newvalue) and key to 'newfieldtoset'
01386             // name in the build sql. Later, bind_params() will detect the value array and
01387             // perform the needed modifications to allow the query to work. Note that
01388             // 'newfieldtoset' is one arbitrary name that hopefully won't be used ever
01389             // in order to avoid problems where the same field is used both in the set clause and in
01390             // the conditions. This was breaking badly in drivers using NAMED params like oci.
01391             $params['newfieldtoset'] = array($newfield => $newvalue);
01392             $newsql = "$newfield = :newfieldtoset";
01393         }
01394         $sql = "UPDATE {" . $table . "} SET $newsql $select";
01395         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
01396 
01397         list($sql, $params) = $this->tweak_param_names($sql, $params);
01398         $this->query_start($sql, $params, SQL_QUERY_UPDATE);
01399         $stmt = $this->parse_query($sql);
01400         $descriptors = $this->bind_params($stmt, $params, $table);
01401         $result = oci_execute($stmt, $this->commit_status);
01402         $this->free_descriptors($descriptors);
01403         $this->query_end($result, $stmt);
01404         oci_free_statement($stmt);
01405 
01406         return true;
01407     }
01408 
01418     public function delete_records_select($table, $select, array $params=null) {
01419 
01420         if ($select) {
01421             $select = "WHERE $select";
01422         }
01423 
01424         $sql = "DELETE FROM {" . $table . "} $select";
01425 
01426         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
01427 
01428         list($sql, $params) = $this->tweak_param_names($sql, $params);
01429         $this->query_start($sql, $params, SQL_QUERY_UPDATE);
01430         $stmt = $this->parse_query($sql);
01431         $this->bind_params($stmt, $params);
01432         $result = oci_execute($stmt, $this->commit_status);
01433         $this->query_end($result, $stmt);
01434         oci_free_statement($stmt);
01435 
01436         return true;
01437     }
01438 
01439     function sql_null_from_clause() {
01440         return ' FROM dual';
01441     }
01442 
01443 // Bitwise operations
01444    protected function bitwise_supported() {
01445         if (isset($this->bitwise_supported)) { // Use cached value if available
01446             return $this->bitwise_supported;
01447         }
01448         $sql = "SELECT 1
01449                 FROM user_objects
01450                 WHERE object_type = 'PACKAGE BODY'
01451                   AND object_name = 'MOODLE_BITS'
01452                   AND status = 'VALID'";
01453         $this->query_start($sql, null, SQL_QUERY_AUX);
01454         $stmt = $this->parse_query($sql);
01455         $result = oci_execute($stmt, $this->commit_status);
01456         $this->query_end($result, $stmt);
01457         $records = null;
01458         oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
01459         oci_free_statement($stmt);
01460         $this->bitwise_supported = isset($records[0]) && reset($records[0]) ? true : false;
01461         return $this->bitwise_supported;
01462     }
01463 
01464     public function sql_bitand($int1, $int2) {
01465         return 'bitand((' . $int1 . '), (' . $int2 . '))';
01466     }
01467 
01468     public function sql_bitnot($int1) {
01469         return '((0 - (' . $int1 . ')) - 1)';
01470     }
01471 
01472     public function sql_bitor($int1, $int2) {
01473         // Use the MOODLE_BITS package if available
01474         if ($this->bitwise_supported()) {
01475             return 'MOODLE_BITS.BITOR(' . $int1 . ', ' . $int2 . ')';
01476         }
01477         // fallback to PHP bool operations, can break if using placeholders
01478         return '((' . $int1 . ') + (' . $int2 . ') - ' . $this->sql_bitand($int1, $int2) . ')';
01479     }
01480 
01481     public function sql_bitxor($int1, $int2) {
01482         // Use the MOODLE_BITS package if available
01483         if ($this->bitwise_supported()) {
01484             return 'MOODLE_BITS.BITXOR(' . $int1 . ', ' . $int2 . ')';
01485         }
01486         // fallback to PHP bool operations, can break if using placeholders
01487         return '(' . $this->sql_bitor($int1, $int2) . ' - ' . $this->sql_bitand($int1, $int2) . ')';
01488     }
01489 
01498     public function sql_modulo($int1, $int2) {
01499         return 'MOD(' . $int1 . ', ' . $int2 . ')';
01500     }
01501 
01502     public function sql_cast_char2int($fieldname, $text=false) {
01503         if (!$text) {
01504             return ' CAST(' . $fieldname . ' AS INT) ';
01505         } else {
01506             return ' CAST(' . $this->sql_compare_text($fieldname) . ' AS INT) ';
01507         }
01508     }
01509 
01510     public function sql_cast_char2real($fieldname, $text=false) {
01511         if (!$text) {
01512             return ' CAST(' . $fieldname . ' AS FLOAT) ';
01513         } else {
01514             return ' CAST(' . $this->sql_compare_text($fieldname) . ' AS FLOAT) ';
01515         }
01516     }
01517 
01529     public function sql_like($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notlike = false, $escapechar = '\\') {
01530         if (strpos($param, '%') !== false) {
01531             debugging('Potential SQL injection detected, sql_ilike() expects bound parameters (? or :named)');
01532         }
01533 
01534         $LIKE = $notlike ? 'NOT LIKE' : 'LIKE';
01535 
01536         // no accent sensitiveness here for now, sorry
01537 
01538         if ($casesensitive) {
01539             return "$fieldname $LIKE $param ESCAPE '$escapechar'";
01540         } else {
01541             return "LOWER($fieldname) $LIKE LOWER($param) ESCAPE '$escapechar'";
01542         }
01543     }
01544 
01545     // NOTE: Oracle concat implementation isn't ANSI compliant when using NULLs (the result of
01546     // any concatenation with NULL must return NULL) because of his inability to differentiate
01547     // NULLs and empty strings. So this function will cause some tests to fail. Hopefully
01548     // it's only a side case and it won't affect normal concatenation operations in Moodle.
01549     public function sql_concat() {
01550         $arr = func_get_args();
01551         $s = implode(' || ', $arr);
01552         if ($s === '') {
01553             return " '' ";
01554         }
01555         return " $s ";
01556     }
01557 
01558     public function sql_concat_join($separator="' '", $elements=array()) {
01559         for ($n=count($elements)-1; $n > 0 ; $n--) {
01560             array_splice($elements, $n, 0, $separator);
01561         }
01562         $s = implode(' || ', $elements);
01563         if ($s === '') {
01564             return " '' ";
01565         }
01566         return " $s ";
01567     }
01568 
01572     public function sql_position($needle, $haystack) {
01573         return "INSTR(($haystack), ($needle))";
01574     }
01575 
01576     public function sql_isempty($tablename, $fieldname, $nullablefield, $textfield) {
01577         if ($textfield) {
01578             return " (".$this->sql_compare_text($fieldname)." = '".$this->sql_empty()."') ";
01579         } else {
01580             return " ($fieldname = '".$this->sql_empty()."') ";
01581         }
01582     }
01583 
01584     public function sql_empty() {
01585         return ' ';
01586     }
01587 
01588     public function sql_order_by_text($fieldname, $numchars=32) {
01589         return 'dbms_lob.substr(' . $fieldname . ', ' . $numchars . ',1)';
01590     }
01591 
01593     public function session_lock_supported() {
01594         if (isset($this->dblocks_supported)) { // Use cached value if available
01595             return $this->dblocks_supported;
01596         }
01597         $sql = "SELECT 1
01598                 FROM user_objects
01599                 WHERE object_type = 'PACKAGE BODY'
01600                   AND object_name = 'MOODLE_LOCKS'
01601                   AND status = 'VALID'";
01602         $this->query_start($sql, null, SQL_QUERY_AUX);
01603         $stmt = $this->parse_query($sql);
01604         $result = oci_execute($stmt, $this->commit_status);
01605         $this->query_end($result, $stmt);
01606         $records = null;
01607         oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
01608         oci_free_statement($stmt);
01609         $this->dblocks_supported = isset($records[0]) && reset($records[0]) ? true : false;
01610         return $this->dblocks_supported;
01611     }
01612 
01619     public function get_session_lock($rowid, $timeout) {
01620         if (!$this->session_lock_supported()) {
01621             return;
01622         }
01623         parent::get_session_lock($rowid, $timeout);
01624 
01625         $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
01626         $sql = 'SELECT MOODLE_LOCKS.GET_LOCK(:lockname, :locktimeout) FROM DUAL';
01627         $params = array('lockname' => $fullname , 'locktimeout' => $timeout);
01628         $this->query_start($sql, $params, SQL_QUERY_AUX);
01629         $stmt = $this->parse_query($sql);
01630         $this->bind_params($stmt, $params);
01631         $result = oci_execute($stmt, $this->commit_status);
01632         if ($result === false) { // Any failure in get_lock() raises error, causing return of bool false
01633             throw new dml_sessionwait_exception();
01634         }
01635         $this->query_end($result, $stmt);
01636         oci_free_statement($stmt);
01637     }
01638 
01639     public function release_session_lock($rowid) {
01640         if (!$this->session_lock_supported()) {
01641             return;
01642         }
01643         parent::release_session_lock($rowid);
01644 
01645         $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
01646         $params = array('lockname' => $fullname);
01647         $sql = 'SELECT MOODLE_LOCKS.RELEASE_LOCK(:lockname) FROM DUAL';
01648         $this->query_start($sql, $params, SQL_QUERY_AUX);
01649         $stmt = $this->parse_query($sql);
01650         $this->bind_params($stmt, $params);
01651         $result = oci_execute($stmt, $this->commit_status);
01652         $this->query_end($result, $stmt);
01653         oci_free_statement($stmt);
01654     }
01655 
01657 
01662     protected function begin_transaction() {
01663         $this->commit_status = OCI_DEFAULT; //Done! ;-)
01664     }
01665 
01671     protected function commit_transaction() {
01672         $this->query_start('--oracle_commit', NULL, SQL_QUERY_AUX);
01673         $result = oci_commit($this->oci);
01674         $this->commit_status = OCI_COMMIT_ON_SUCCESS;
01675         $this->query_end($result);
01676     }
01677 
01683     protected function rollback_transaction() {
01684         $this->query_start('--oracle_rollback', NULL, SQL_QUERY_AUX);
01685         $result = oci_rollback($this->oci);
01686         $this->commit_status = OCI_COMMIT_ON_SUCCESS;
01687         $this->query_end($result);
01688     }
01689 }
 All Data Structures Namespaces Files Functions Variables Enumerations