[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/dml/ -> oci_native_moodle_database.php (source)

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Native oci class representing moodle database interface.
  19   *
  20   * @package    core_dml
  21   * @copyright  2008 Petr Skoda (http://skodak.org)
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  require_once (__DIR__.'/moodle_database.php');
  28  require_once (__DIR__.'/oci_native_moodle_recordset.php');
  29  require_once (__DIR__.'/oci_native_moodle_temptables.php');
  30  
  31  /**
  32   * Native oci class representing moodle database interface.
  33   *
  34   * One complete reference for PHP + OCI:
  35   * http://www.oracle.com/technology/tech/php/underground-php-oracle-manual.html
  36   *
  37   * @package    core_dml
  38   * @copyright  2008 Petr Skoda (http://skodak.org)
  39   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  40   */
  41  class oci_native_moodle_database extends moodle_database {
  42  
  43      protected $oci     = null;
  44  
  45      /** @var To store stmt errors and enable get_last_error() to detect them.*/
  46      private $last_stmt_error = null;
  47      /** @var Default value initialised in connect method, we need the driver to be present.*/
  48      private $commit_status = null;
  49  
  50      /** @var To handle oci driver default verbosity.*/
  51      private $last_error_reporting;
  52      /** @var To store unique_session_id. Needed for temp tables unique naming.*/
  53      private $unique_session_id;
  54  
  55      /**
  56       * Detects if all needed PHP stuff installed.
  57       * Note: can be used before connect()
  58       * @return mixed true if ok, string if something
  59       */
  60      public function driver_installed() {
  61          if (!extension_loaded('oci8')) {
  62              return get_string('ociextensionisnotpresentinphp', 'install');
  63          }
  64          return true;
  65      }
  66  
  67      /**
  68       * Returns database family type - describes SQL dialect
  69       * Note: can be used before connect()
  70       * @return string db family name (mysql, postgres, mssql, oracle, etc.)
  71       */
  72      public function get_dbfamily() {
  73          return 'oracle';
  74      }
  75  
  76      /**
  77       * Returns more specific database driver type
  78       * Note: can be used before connect()
  79       * @return string db type mysqli, pgsql, oci, mssql, sqlsrv
  80       */
  81      protected function get_dbtype() {
  82          return 'oci';
  83      }
  84  
  85      /**
  86       * Returns general database library name
  87       * Note: can be used before connect()
  88       * @return string db type pdo, native
  89       */
  90      protected function get_dblibrary() {
  91          return 'native';
  92      }
  93  
  94      /**
  95       * Returns localised database type name
  96       * Note: can be used before connect()
  97       * @return string
  98       */
  99      public function get_name() {
 100          return get_string('nativeoci', 'install');
 101      }
 102  
 103      /**
 104       * Returns localised database configuration help.
 105       * Note: can be used before connect()
 106       * @return string
 107       */
 108      public function get_configuration_help() {
 109          return get_string('nativeocihelp', 'install');
 110      }
 111  
 112      /**
 113       * Diagnose database and tables, this function is used
 114       * to verify database and driver settings, db engine types, etc.
 115       *
 116       * @return string null means everything ok, string means problem found.
 117       */
 118      public function diagnose() {
 119          return null;
 120      }
 121  
 122      /**
 123       * Connect to db
 124       * Must be called before other methods.
 125       * @param string $dbhost The database host.
 126       * @param string $dbuser The database username.
 127       * @param string $dbpass The database username's password.
 128       * @param string $dbname The name of the database being connected to.
 129       * @param mixed $prefix string means moodle db prefix, false used for external databases where prefix not used
 130       * @param array $dboptions driver specific options
 131       * @return bool true
 132       * @throws dml_connection_exception if error
 133       */
 134      public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) {
 135          if ($prefix == '' and !$this->external) {
 136              //Enforce prefixes for everybody but mysql
 137              throw new dml_exception('prefixcannotbeempty', $this->get_dbfamily());
 138          }
 139          if (!$this->external and strlen($prefix) > 2) {
 140              //Max prefix length for Oracle is 2cc
 141              $a = (object)array('dbfamily'=>'oracle', 'maxlength'=>2);
 142              throw new dml_exception('prefixtoolong', $a);
 143          }
 144  
 145          $driverstatus = $this->driver_installed();
 146  
 147          if ($driverstatus !== true) {
 148              throw new dml_exception('dbdriverproblem', $driverstatus);
 149          }
 150  
 151          // Autocommit ON by default.
 152          // Switching to OFF (OCI_DEFAULT), when playing with transactions
 153          // please note this thing is not defined if oracle driver not present in PHP
 154          // which means it can not be used as default value of object property!
 155          $this->commit_status = OCI_COMMIT_ON_SUCCESS;
 156  
 157          $this->store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions);
 158          unset($this->dboptions['dbsocket']);
 159  
 160          // NOTE: use of ', ", / and \ is very problematic, even native oracle tools seem to have
 161          //       problems with these, so just forget them and do not report problems into tracker...
 162  
 163          if (empty($this->dbhost)) {
 164              // old style full address (TNS)
 165              $dbstring = $this->dbname;
 166          } else {
 167              if (empty($this->dboptions['dbport'])) {
 168                  $this->dboptions['dbport'] = 1521;
 169              }
 170              $dbstring = '//'.$this->dbhost.':'.$this->dboptions['dbport'].'/'.$this->dbname;
 171          }
 172  
 173          ob_start();
 174          if (empty($this->dboptions['dbpersist'])) {
 175              $this->oci = oci_new_connect($this->dbuser, $this->dbpass, $dbstring, 'AL32UTF8');
 176          } else {
 177              $this->oci = oci_pconnect($this->dbuser, $this->dbpass, $dbstring, 'AL32UTF8');
 178          }
 179          $dberr = ob_get_contents();
 180          ob_end_clean();
 181  
 182  
 183          if ($this->oci === false) {
 184              $this->oci = null;
 185              $e = oci_error();
 186              if (isset($e['message'])) {
 187                  $dberr = $e['message'];
 188              }
 189              throw new dml_connection_exception($dberr);
 190          }
 191  
 192          // Make sure moodle package is installed - now required.
 193          if (!$this->oci_package_installed()) {
 194              try {
 195                  $this->attempt_oci_package_install();
 196              } catch (Exception $e) {
 197                  // Ignore problems, only the result counts,
 198                  // admins have to fix it manually if necessary.
 199              }
 200              if (!$this->oci_package_installed()) {
 201                  throw new dml_exception('dbdriverproblem', 'Oracle PL/SQL Moodle support package MOODLELIB is not installed! Database administrator has to execute /lib/dml/oci_native_moodle_package.sql script.');
 202              }
 203          }
 204  
 205          // get unique session id, to be used later for temp tables stuff
 206          $sql = 'SELECT DBMS_SESSION.UNIQUE_SESSION_ID() FROM DUAL';
 207          $this->query_start($sql, null, SQL_QUERY_AUX);
 208          $stmt = $this->parse_query($sql);
 209          $result = oci_execute($stmt, $this->commit_status);
 210          $this->query_end($result, $stmt);
 211          $records = null;
 212          oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
 213          oci_free_statement($stmt);
 214          $this->unique_session_id = reset($records[0]);
 215  
 216          //note: do not send "ALTER SESSION SET NLS_NUMERIC_CHARACTERS='.,'" !
 217          //      instead fix our PHP code to convert "," to "." properly!
 218  
 219          // Connection stabilised and configured, going to instantiate the temptables controller
 220          $this->temptables = new oci_native_moodle_temptables($this, $this->unique_session_id);
 221  
 222          return true;
 223      }
 224  
 225      /**
 226       * Close database connection and release all resources
 227       * and memory (especially circular memory references).
 228       * Do NOT use connect() again, create a new instance if needed.
 229       */
 230      public function dispose() {
 231          parent::dispose(); // Call parent dispose to write/close session and other common stuff before closing connection
 232          if ($this->oci) {
 233              oci_close($this->oci);
 234              $this->oci = null;
 235          }
 236      }
 237  
 238  
 239      /**
 240       * Called before each db query.
 241       * @param string $sql
 242       * @param array array of parameters
 243       * @param int $type type of query
 244       * @param mixed $extrainfo driver specific extra information
 245       * @return void
 246       */
 247      protected function query_start($sql, array $params=null, $type, $extrainfo=null) {
 248          parent::query_start($sql, $params, $type, $extrainfo);
 249          // oci driver tents to send debug to output, we do not need that ;-)
 250          $this->last_error_reporting = error_reporting(0);
 251      }
 252  
 253      /**
 254       * Called immediately after each db query.
 255       * @param mixed db specific result
 256       * @return void
 257       */
 258      protected function query_end($result, $stmt=null) {
 259          // reset original debug level
 260          error_reporting($this->last_error_reporting);
 261          if ($stmt and $result === false) {
 262              // Look for stmt error and store it
 263              if (is_resource($stmt)) {
 264                  $e = oci_error($stmt);
 265                  if ($e !== false) {
 266                      $this->last_stmt_error = $e['message'];
 267                  }
 268              }
 269              oci_free_statement($stmt);
 270          }
 271          parent::query_end($result);
 272      }
 273  
 274      /**
 275       * Returns database server info array
 276       * @return array Array containing 'description' and 'version' info
 277       */
 278      public function get_server_info() {
 279          static $info = null; // TODO: move to real object property
 280  
 281          if (is_null($info)) {
 282              $this->query_start("--oci_server_version()", null, SQL_QUERY_AUX);
 283              $description = oci_server_version($this->oci);
 284              $this->query_end(true);
 285              preg_match('/(\d+\.)+\d+/', $description, $matches);
 286              $info = array('description'=>$description, 'version'=>$matches[0]);
 287          }
 288  
 289          return $info;
 290      }
 291  
 292      /**
 293       * Converts short table name {tablename} to real table name
 294       * supporting temp tables ($this->unique_session_id based) if detected
 295       *
 296       * @param string sql
 297       * @return string sql
 298       */
 299      protected function fix_table_names($sql) {
 300          if (preg_match_all('/\{([a-z][a-z0-9_]*)\}/', $sql, $matches)) {
 301              foreach($matches[0] as $key=>$match) {
 302                  $name = $matches[1][$key];
 303                  if ($this->temptables && $this->temptables->is_temptable($name)) {
 304                      $sql = str_replace($match, $this->temptables->get_correct_name($name), $sql);
 305                  } else {
 306                      $sql = str_replace($match, $this->prefix.$name, $sql);
 307                  }
 308              }
 309          }
 310          return $sql;
 311      }
 312  
 313      /**
 314       * Returns supported query parameter types
 315       * @return int bitmask of accepted SQL_PARAMS_*
 316       */
 317      protected function allowed_param_types() {
 318          return SQL_PARAMS_NAMED;
 319      }
 320  
 321      /**
 322       * Returns last error reported by database engine.
 323       * @return string error message
 324       */
 325      public function get_last_error() {
 326          $error = false;
 327          // First look for any previously saved stmt error
 328          if (!empty($this->last_stmt_error)) {
 329              $error = $this->last_stmt_error;
 330              $this->last_stmt_error = null;
 331          } else { // Now try connection error
 332              $e = oci_error($this->oci);
 333              if ($e !== false) {
 334                  $error = $e['message'];
 335              }
 336          }
 337          return $error;
 338      }
 339  
 340      /**
 341       * Prepare the statement for execution
 342       * @throws dml_connection_exception
 343       * @param string $sql
 344       * @return resource
 345       */
 346      protected function parse_query($sql) {
 347          $stmt = oci_parse($this->oci, $sql);
 348          if ($stmt == false) {
 349              throw new dml_connection_exception('Can not parse sql query'); //TODO: maybe add better info
 350          }
 351          return $stmt;
 352      }
 353  
 354      /**
 355       * Make sure there are no reserved words in param names...
 356       * @param string $sql
 357       * @param array $params
 358       * @return array ($sql, $params) updated query and parameters
 359       */
 360      protected function tweak_param_names($sql, array $params) {
 361          if (empty($params)) {
 362              return array($sql, $params);
 363          }
 364  
 365          $newparams = array();
 366          $searcharr = array(); // search => replace pairs
 367          foreach ($params as $name => $value) {
 368              // Keep the name within the 30 chars limit always (prefixing/replacing)
 369              if (strlen($name) <= 28) {
 370                  $newname = 'o_' . $name;
 371              } else {
 372                  $newname = 'o_' . substr($name, 2);
 373              }
 374              $newparams[$newname] = $value;
 375              $searcharr[':' . $name] = ':' . $newname;
 376          }
 377          // sort by length desc to avoid potential str_replace() overlap
 378          uksort($searcharr, array('oci_native_moodle_database', 'compare_by_length_desc'));
 379  
 380          $sql = str_replace(array_keys($searcharr), $searcharr, $sql);
 381          return array($sql, $newparams);
 382      }
 383  
 384      /**
 385       * Return tables in database WITHOUT current prefix
 386       * @param bool $usecache if true, returns list of cached tables.
 387       * @return array of table names in lowercase and without prefix
 388       */
 389      public function get_tables($usecache=true) {
 390          if ($usecache and $this->tables !== null) {
 391              return $this->tables;
 392          }
 393          $this->tables = array();
 394          $prefix = str_replace('_', "\\_", strtoupper($this->prefix));
 395          $sql = "SELECT TABLE_NAME
 396                    FROM CAT
 397                   WHERE TABLE_TYPE='TABLE'
 398                         AND TABLE_NAME NOT LIKE 'BIN\$%'
 399                         AND TABLE_NAME LIKE '$prefix%' ESCAPE '\\'";
 400          $this->query_start($sql, null, SQL_QUERY_AUX);
 401          $stmt = $this->parse_query($sql);
 402          $result = oci_execute($stmt, $this->commit_status);
 403          $this->query_end($result, $stmt);
 404          $records = null;
 405          oci_fetch_all($stmt, $records, 0, -1, OCI_ASSOC);
 406          oci_free_statement($stmt);
 407          $records = array_map('strtolower', $records['TABLE_NAME']);
 408          foreach ($records as $tablename) {
 409              if ($this->prefix !== false && $this->prefix !== '') {
 410                  if (strpos($tablename, $this->prefix) !== 0) {
 411                      continue;
 412                  }
 413                  $tablename = substr($tablename, strlen($this->prefix));
 414              }
 415              $this->tables[$tablename] = $tablename;
 416          }
 417  
 418          // Add the currently available temptables
 419          $this->tables = array_merge($this->tables, $this->temptables->get_temptables());
 420  
 421          return $this->tables;
 422      }
 423  
 424      /**
 425       * Return table indexes - everything lowercased.
 426       * @param string $table The table we want to get indexes from.
 427       * @return array An associative array of indexes containing 'unique' flag and 'columns' being indexed
 428       */
 429      public function get_indexes($table) {
 430          $indexes = array();
 431          $tablename = strtoupper($this->prefix.$table);
 432  
 433          $sql = "SELECT i.INDEX_NAME, i.UNIQUENESS, c.COLUMN_POSITION, c.COLUMN_NAME, ac.CONSTRAINT_TYPE
 434                    FROM ALL_INDEXES i
 435                    JOIN ALL_IND_COLUMNS c ON c.INDEX_NAME=i.INDEX_NAME
 436               LEFT JOIN ALL_CONSTRAINTS ac ON (ac.TABLE_NAME=i.TABLE_NAME AND ac.CONSTRAINT_NAME=i.INDEX_NAME AND ac.CONSTRAINT_TYPE='P')
 437                   WHERE i.TABLE_NAME = '$tablename'
 438                ORDER BY i.INDEX_NAME, c.COLUMN_POSITION";
 439  
 440          $stmt = $this->parse_query($sql);
 441          $result = oci_execute($stmt, $this->commit_status);
 442          $this->query_end($result, $stmt);
 443          $records = null;
 444          oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
 445          oci_free_statement($stmt);
 446  
 447          foreach ($records as $record) {
 448              if ($record['CONSTRAINT_TYPE'] === 'P') {
 449                  //ignore for now;
 450                  continue;
 451              }
 452              $indexname = strtolower($record['INDEX_NAME']);
 453              if (!isset($indexes[$indexname])) {
 454                  $indexes[$indexname] = array('primary' => ($record['CONSTRAINT_TYPE'] === 'P'),
 455                                               'unique'  => ($record['UNIQUENESS'] === 'UNIQUE'),
 456                                               'columns' => array());
 457              }
 458              $indexes[$indexname]['columns'][] = strtolower($record['COLUMN_NAME']);
 459          }
 460  
 461          return $indexes;
 462      }
 463  
 464      /**
 465       * Returns detailed information about columns in table. This information is cached internally.
 466       * @param string $table name
 467       * @param bool $usecache
 468       * @return array array of database_column_info objects indexed with column names
 469       */
 470      public function get_columns($table, $usecache=true) {
 471  
 472          if ($usecache) {
 473              if ($this->temptables->is_temptable($table)) {
 474                  if ($data = $this->get_temp_tables_cache()->get($table)) {
 475                      return $data;
 476                  }
 477              } else {
 478                  if ($data = $this->get_metacache()->get($table)) {
 479                      return $data;
 480                  }
 481              }
 482          }
 483  
 484          if (!$table) { // table not specified, return empty array directly
 485              return array();
 486          }
 487  
 488          $structure = array();
 489  
 490          // We give precedence to CHAR_LENGTH for VARCHAR2 columns over WIDTH because the former is always
 491          // BYTE based and, for cross-db operations, we want CHAR based results. See MDL-29415
 492          // Instead of guessing sequence based exclusively on name, check tables against user_triggers to
 493          // ensure the table has a 'before each row' trigger to assume 'id' is auto_increment. MDL-32365
 494          $sql = "SELECT CNAME, COLTYPE, nvl(CHAR_LENGTH, WIDTH) AS WIDTH, SCALE, PRECISION, NULLS, DEFAULTVAL,
 495                    DECODE(NVL(TRIGGER_NAME, '0'), '0', '0', '1') HASTRIGGER
 496                    FROM COL c
 497               LEFT JOIN USER_TAB_COLUMNS u ON (u.TABLE_NAME = c.TNAME AND u.COLUMN_NAME = c.CNAME AND u.DATA_TYPE = 'VARCHAR2')
 498               LEFT JOIN USER_TRIGGERS t ON (t.TABLE_NAME = c.TNAME AND TRIGGER_TYPE = 'BEFORE EACH ROW' AND c.CNAME = 'ID')
 499                   WHERE TNAME = UPPER('{" . $table . "}')
 500                ORDER BY COLNO";
 501  
 502          list($sql, $params, $type) = $this->fix_sql_params($sql, null);
 503  
 504          $this->query_start($sql, null, SQL_QUERY_AUX);
 505          $stmt = $this->parse_query($sql);
 506          $result = oci_execute($stmt, $this->commit_status);
 507          $this->query_end($result, $stmt);
 508          $records = null;
 509          oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
 510          oci_free_statement($stmt);
 511  
 512          if (!$records) {
 513              return array();
 514          }
 515          foreach ($records as $rawcolumn) {
 516              $rawcolumn = (object)$rawcolumn;
 517  
 518              $info = new stdClass();
 519              $info->name = strtolower($rawcolumn->CNAME);
 520              $info->auto_increment = ((int)$rawcolumn->HASTRIGGER) ? true : false;
 521              $matches = null;
 522  
 523              if ($rawcolumn->COLTYPE === 'VARCHAR2'
 524               or $rawcolumn->COLTYPE === 'VARCHAR'
 525               or $rawcolumn->COLTYPE === 'NVARCHAR2'
 526               or $rawcolumn->COLTYPE === 'NVARCHAR'
 527               or $rawcolumn->COLTYPE === 'CHAR'
 528               or $rawcolumn->COLTYPE === 'NCHAR') {
 529                  $info->type          = $rawcolumn->COLTYPE;
 530                  $info->meta_type     = 'C';
 531                  $info->max_length    = $rawcolumn->WIDTH;
 532                  $info->scale         = null;
 533                  $info->not_null      = ($rawcolumn->NULLS === 'NOT NULL');
 534                  $info->has_default   = !is_null($rawcolumn->DEFAULTVAL);
 535                  if ($info->has_default) {
 536  
 537                      // this is hacky :-(
 538                      if ($rawcolumn->DEFAULTVAL === 'NULL') {
 539                          $info->default_value = null;
 540                      } else if ($rawcolumn->DEFAULTVAL === "' ' ") { // Sometimes it's stored with trailing space
 541                          $info->default_value = "";
 542                      } else if ($rawcolumn->DEFAULTVAL === "' '") { // Sometimes it's stored without trailing space
 543                          $info->default_value = "";
 544                      } else {
 545                          $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space
 546                          $info->default_value = substr($info->default_value, 1, strlen($info->default_value)-2); //trim ''
 547                      }
 548                  } else {
 549                      $info->default_value = null;
 550                  }
 551                  $info->primary_key   = false;
 552                  $info->binary        = false;
 553                  $info->unsigned      = null;
 554                  $info->unique        = null;
 555  
 556              } else if ($rawcolumn->COLTYPE === 'NUMBER') {
 557                  $info->type       = $rawcolumn->COLTYPE;
 558                  $info->max_length = $rawcolumn->PRECISION;
 559                  $info->binary     = false;
 560                  if (!is_null($rawcolumn->SCALE) && $rawcolumn->SCALE == 0) { // null in oracle scale allows decimals => not integer
 561                      // integer
 562                      if ($info->name === 'id') {
 563                          $info->primary_key   = true;
 564                          $info->meta_type     = 'R';
 565                          $info->unique        = true;
 566                          $info->has_default   = false;
 567                      } else {
 568                          $info->primary_key   = false;
 569                          $info->meta_type     = 'I';
 570                          $info->unique        = null;
 571                      }
 572                      $info->scale = 0;
 573  
 574                  } else {
 575                      //float
 576                      $info->meta_type     = 'N';
 577                      $info->primary_key   = false;
 578                      $info->unsigned      = null;
 579                      $info->unique        = null;
 580                      $info->scale         = $rawcolumn->SCALE;
 581                  }
 582                  $info->not_null      = ($rawcolumn->NULLS === 'NOT NULL');
 583                  $info->has_default   = !is_null($rawcolumn->DEFAULTVAL);
 584                  if ($info->has_default) {
 585                      $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space
 586                  } else {
 587                      $info->default_value = null;
 588                  }
 589  
 590              } else if ($rawcolumn->COLTYPE === 'FLOAT') {
 591                  $info->type       = $rawcolumn->COLTYPE;
 592                  $info->max_length = (int)($rawcolumn->PRECISION * 3.32193);
 593                  $info->primary_key   = false;
 594                  $info->meta_type     = 'N';
 595                  $info->unique        = null;
 596                  $info->not_null      = ($rawcolumn->NULLS === 'NOT NULL');
 597                  $info->has_default   = !is_null($rawcolumn->DEFAULTVAL);
 598                  if ($info->has_default) {
 599                      $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space
 600                  } else {
 601                      $info->default_value = null;
 602                  }
 603  
 604              } else if ($rawcolumn->COLTYPE === 'CLOB'
 605                      or $rawcolumn->COLTYPE === 'NCLOB') {
 606                  $info->type          = $rawcolumn->COLTYPE;
 607                  $info->meta_type     = 'X';
 608                  $info->max_length    = -1;
 609                  $info->scale         = null;
 610                  $info->scale         = null;
 611                  $info->not_null      = ($rawcolumn->NULLS === 'NOT NULL');
 612                  $info->has_default   = !is_null($rawcolumn->DEFAULTVAL);
 613                  if ($info->has_default) {
 614                      // this is hacky :-(
 615                      if ($rawcolumn->DEFAULTVAL === 'NULL') {
 616                          $info->default_value = null;
 617                      } else if ($rawcolumn->DEFAULTVAL === "' ' ") { // Sometimes it's stored with trailing space
 618                          $info->default_value = "";
 619                      } else if ($rawcolumn->DEFAULTVAL === "' '") { // Other times it's stored without trailing space
 620                          $info->default_value = "";
 621                      } else {
 622                          $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space
 623                          $info->default_value = substr($info->default_value, 1, strlen($info->default_value)-2); //trim ''
 624                      }
 625                  } else {
 626                      $info->default_value = null;
 627                  }
 628                  $info->primary_key   = false;
 629                  $info->binary        = false;
 630                  $info->unsigned      = null;
 631                  $info->unique        = null;
 632  
 633              } else if ($rawcolumn->COLTYPE === 'BLOB') {
 634                  $info->type          = $rawcolumn->COLTYPE;
 635                  $info->meta_type     = 'B';
 636                  $info->max_length    = -1;
 637                  $info->scale         = null;
 638                  $info->scale         = null;
 639                  $info->not_null      = ($rawcolumn->NULLS === 'NOT NULL');
 640                  $info->has_default   = !is_null($rawcolumn->DEFAULTVAL);
 641                  if ($info->has_default) {
 642                      // this is hacky :-(
 643                      if ($rawcolumn->DEFAULTVAL === 'NULL') {
 644                          $info->default_value = null;
 645                      } else if ($rawcolumn->DEFAULTVAL === "' ' ") { // Sometimes it's stored with trailing space
 646                          $info->default_value = "";
 647                      } else if ($rawcolumn->DEFAULTVAL === "' '") { // Sometimes it's stored without trailing space
 648                          $info->default_value = "";
 649                      } else {
 650                          $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space
 651                          $info->default_value = substr($info->default_value, 1, strlen($info->default_value)-2); //trim ''
 652                      }
 653                  } else {
 654                      $info->default_value = null;
 655                  }
 656                  $info->primary_key   = false;
 657                  $info->binary        = true;
 658                  $info->unsigned      = null;
 659                  $info->unique        = null;
 660  
 661              } else {
 662                  // unknown type - sorry
 663                  $info->type          = $rawcolumn->COLTYPE;
 664                  $info->meta_type     = '?';
 665              }
 666  
 667              $structure[$info->name] = new database_column_info($info);
 668          }
 669  
 670          if ($usecache) {
 671              if ($this->temptables->is_temptable($table)) {
 672                  $this->get_temp_tables_cache()->set($table, $structure);
 673              } else {
 674                  $this->get_metacache()->set($table, $structure);
 675              }
 676          }
 677  
 678          return $structure;
 679      }
 680  
 681      /**
 682       * Normalise values based in RDBMS dependencies (booleans, LOBs...)
 683       *
 684       * @param database_column_info $column column metadata corresponding with the value we are going to normalise
 685       * @param mixed $value value we are going to normalise
 686       * @return mixed the normalised value
 687       */
 688      protected function normalise_value($column, $value) {
 689          $this->detect_objects($value);
 690  
 691          if (is_bool($value)) { // Always, convert boolean to int
 692              $value = (int)$value;
 693  
 694          } else if ($column->meta_type == 'B') { // BLOB detected, we return 'blob' array instead of raw value to allow
 695              if (!is_null($value)) {             // binding/executing code later to know about its nature
 696                  $value = array('blob' => $value);
 697              }
 698  
 699          } else if ($column->meta_type == 'X' && strlen($value) > 4000) { // CLOB detected (>4000 optimisation), we return 'clob'
 700              if (!is_null($value)) {                                      // array instead of raw value to allow binding/
 701                  $value = array('clob' => (string)$value);                // executing code later to know about its nature
 702              }
 703  
 704          } else if ($value === '') {
 705              if ($column->meta_type == 'I' or $column->meta_type == 'F' or $column->meta_type == 'N') {
 706                  $value = 0; // prevent '' problems in numeric fields
 707              }
 708          }
 709          return $value;
 710      }
 711  
 712      /**
 713       * Transforms the sql and params in order to emulate the LIMIT clause available in other DBs
 714       *
 715       * @param string $sql the SQL select query to execute.
 716       * @param array $params array of sql parameters
 717       * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
 718       * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
 719       * @return array with the transformed sql and params updated
 720       */
 721      private function get_limit_sql($sql, array $params = null, $limitfrom=0, $limitnum=0) {
 722  
 723          list($limitfrom, $limitnum) = $this->normalise_limit_from_num($limitfrom, $limitnum);
 724          // TODO: Add the /*+ FIRST_ROWS */ hint if there isn't another hint
 725  
 726          if ($limitfrom and $limitnum) {
 727              $sql = "SELECT oracle_o.*
 728                        FROM (SELECT oracle_i.*, rownum AS oracle_rownum
 729                                FROM ($sql) oracle_i
 730                               WHERE rownum <= :oracle_num_rows
 731                              ) oracle_o
 732                       WHERE oracle_rownum > :oracle_skip_rows";
 733              $params['oracle_num_rows'] = $limitfrom + $limitnum;
 734              $params['oracle_skip_rows'] = $limitfrom;
 735  
 736          } else if ($limitfrom and !$limitnum) {
 737              $sql = "SELECT oracle_o.*
 738                        FROM (SELECT oracle_i.*, rownum AS oracle_rownum
 739                                FROM ($sql) oracle_i
 740                              ) oracle_o
 741                       WHERE oracle_rownum > :oracle_skip_rows";
 742              $params['oracle_skip_rows'] = $limitfrom;
 743  
 744          } else if (!$limitfrom and $limitnum) {
 745              $sql = "SELECT *
 746                        FROM ($sql)
 747                       WHERE rownum <= :oracle_num_rows";
 748              $params['oracle_num_rows'] = $limitnum;
 749          }
 750  
 751          return array($sql, $params);
 752      }
 753  
 754      /**
 755       * This function will handle all the column values before being inserted/updated to DB for Oracle
 756       * installations. This is because the "special feature" of Oracle where the empty string is
 757       * equal to NULL and this presents a problem with all our currently NOT NULL default '' fields.
 758       * (and with empties handling in general)
 759       *
 760       * Note that this function is 100% private and should be used, exclusively by DML functions
 761       * in this file. Also, this is considered a DIRTY HACK to be removed when possible.
 762       *
 763       * This function is private and must not be used outside this driver at all
 764       *
 765       * @param $table string the table where the record is going to be inserted/updated (without prefix)
 766       * @param $field string the field where the record is going to be inserted/updated
 767       * @param $value mixed the value to be inserted/updated
 768       */
 769      private function oracle_dirty_hack ($table, $field, $value) {
 770  
 771          // General bound parameter, just hack the spaces and pray it will work.
 772          if (!$table) {
 773              if ($value === '') {
 774                  return ' ';
 775              } else if (is_bool($value)) {
 776                  return (int)$value;
 777              } else {
 778                  return $value;
 779              }
 780          }
 781  
 782          // Get metadata
 783          $columns = $this->get_columns($table);
 784          if (!isset($columns[$field])) {
 785              if ($value === '') {
 786                  return ' ';
 787              } else if (is_bool($value)) {
 788                  return (int)$value;
 789              } else {
 790                  return $value;
 791              }
 792          }
 793          $column = $columns[$field];
 794  
 795          // !! This paragraph explains behaviour before Moodle 2.0:
 796          //
 797          // For Oracle DB, empty strings are converted to NULLs in DB
 798          // and this breaks a lot of NOT NULL columns currently Moodle. In the future it's
 799          // planned to move some of them to NULL, if they must accept empty values and this
 800          // piece of code will become less and less used. But, for now, we need it.
 801          // What we are going to do is to examine all the data being inserted and if it's
 802          // an empty string (NULL for Oracle) and the field is defined as NOT NULL, we'll modify
 803          // such data in the best form possible ("0" for booleans and numbers and " " for the
 804          // rest of strings. It isn't optimal, but the only way to do so.
 805          // In the opposite, when retrieving records from Oracle, we'll decode " " back to
 806          // empty strings to allow everything to work properly. DIRTY HACK.
 807  
 808          // !! These paragraphs explain the rationale about the change for Moodle 2.5:
 809          //
 810          // Before Moodle 2.0, we only used to apply this DIRTY HACK to NOT NULL columns, as
 811          // stated above, but it causes one problem in NULL columns where both empty strings
 812          // and real NULLs are stored as NULLs, being impossible to differentiate them when
 813          // being retrieved from DB.
 814          //
 815          // So, starting with Moodle 2.0, we are going to apply the DIRTY HACK to all the
 816          // CHAR/CLOB columns no matter of their nullability. That way, when retrieving
 817          // NULLABLE fields we'll get proper empties and NULLs differentiated, so we'll be able
 818          // to rely in NULL/empty/content contents without problems, until now that wasn't
 819          // possible at all.
 820          //
 821          // One space DIRTY HACK is now applied automatically for all query parameters
 822          // and results. The only problem is string concatenation where the glue must
 823          // be specified as "' '" sql fragment.
 824          //
 825          // !! Conclusions:
 826          //
 827          // From Moodle 2.5 onwards, ALL empty strings in Oracle DBs will be stored as
 828          // 1-whitespace char, ALL NULLs as NULLs and, obviously, content as content. And
 829          // those 1-whitespace chars will be converted back to empty strings by all the
 830          // get_field/record/set() functions transparently and any SQL needing direct handling
 831          // of empties will have to use placeholders or sql_isempty() helper function.
 832  
 833          // If the field isn't VARCHAR or CLOB, skip
 834          if ($column->meta_type != 'C' and $column->meta_type != 'X') {
 835              return $value;
 836          }
 837  
 838          // If the value isn't empty, skip
 839          if (!empty($value)) {
 840              return $value;
 841          }
 842  
 843          // Now, we have one empty value, going to be inserted to one VARCHAR2 or CLOB field
 844          // Try to get the best value to be inserted
 845  
 846          // The '0' string doesn't need any transformation, skip
 847          if ($value === '0') {
 848              return $value;
 849          }
 850  
 851          // Transformations start
 852          if (gettype($value) == 'boolean') {
 853              return '0'; // Transform false to '0' that evaluates the same for PHP
 854  
 855          } else if (gettype($value) == 'integer') {
 856              return '0'; // Transform 0 to '0' that evaluates the same for PHP
 857  
 858          } else if ($value === '') {
 859              return ' '; // Transform '' to ' ' that DON'T EVALUATE THE SAME
 860                          // (we'll transform back again on get_records_XXX functions and others)!!
 861          }
 862  
 863          // Fail safe to original value
 864          return $value;
 865      }
 866  
 867      /**
 868       * Helper function to order by string length desc
 869       *
 870       * @param $a string first element to compare
 871       * @param $b string second element to compare
 872       * @return int < 0 $a goes first (is less), 0 $b goes first, 0 doesn't matter
 873       */
 874      private function compare_by_length_desc($a, $b) {
 875          return strlen($b) - strlen($a);
 876      }
 877  
 878      /**
 879       * Is db in unicode mode?
 880       * @return bool
 881       */
 882      public function setup_is_unicodedb() {
 883          $sql = "SELECT VALUE
 884                    FROM NLS_DATABASE_PARAMETERS
 885                   WHERE PARAMETER = 'NLS_CHARACTERSET'";
 886          $this->query_start($sql, null, SQL_QUERY_AUX);
 887          $stmt = $this->parse_query($sql);
 888          $result = oci_execute($stmt, $this->commit_status);
 889          $this->query_end($result, $stmt);
 890          $records = null;
 891          oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_COLUMN);
 892          oci_free_statement($stmt);
 893  
 894          return (isset($records['VALUE'][0]) and $records['VALUE'][0] === 'AL32UTF8');
 895      }
 896  
 897      /**
 898       * Do NOT use in code, to be used by database_manager only!
 899       * @param string|array $sql query
 900       * @param array|null $tablenames an array of xmldb table names affected by this request.
 901       * @return bool true
 902       * @throws ddl_change_structure_exception A DDL specific exception is thrown for any errors.
 903       */
 904      public function change_database_structure($sql, $tablenames = null) {
 905          $this->get_manager(); // Includes DDL exceptions classes ;-)
 906          $sqls = (array)$sql;
 907  
 908          try {
 909              foreach ($sqls as $sql) {
 910                  $this->query_start($sql, null, SQL_QUERY_STRUCTURE);
 911                  $stmt = $this->parse_query($sql);
 912                  $result = oci_execute($stmt, $this->commit_status);
 913                  $this->query_end($result, $stmt);
 914                  oci_free_statement($stmt);
 915              }
 916          } catch (ddl_change_structure_exception $e) {
 917              $this->reset_caches($tablenames);
 918              throw $e;
 919          }
 920  
 921          $this->reset_caches($tablenames);
 922          return true;
 923      }
 924  
 925      protected function bind_params($stmt, array &$params=null, $tablename=null, array &$descriptors = null) {
 926          if ($params) {
 927              $columns = array();
 928              if ($tablename) {
 929                  $columns = $this->get_columns($tablename);
 930              }
 931              foreach($params as $key => $value) {
 932                  // Decouple column name and param name as far as sometimes they aren't the same
 933                  if ($key == 'o_newfieldtoset') { // found case where column and key diverge, handle that
 934                      $columnname   = key($value);    // columnname is the key of the array
 935                      $params[$key] = $value[$columnname]; // set the proper value in the $params array and
 936                      $value        = $value[$columnname]; // set the proper value in the $value variable
 937                  } else {
 938                      $columnname = preg_replace('/^o_/', '', $key); // Default columnname (for DB introspecting is key), but...
 939                  }
 940                  // Continue processing
 941                  // Now, handle already detected LOBs
 942                  if (is_array($value)) { // Let's go to bind special cases (lob descriptors)
 943                      if (isset($value['clob'])) {
 944                          $lob = oci_new_descriptor($this->oci, OCI_DTYPE_LOB);
 945                          if ($descriptors === null) {
 946                              throw new coding_exception('moodle_database::bind_params() $descriptors not specified for clob');
 947                          }
 948                          $descriptors[] = $lob;
 949                          oci_bind_by_name($stmt, $key, $lob, -1, SQLT_CLOB);
 950                          $lob->writeTemporary($this->oracle_dirty_hack($tablename, $columnname, $params[$key]['clob']), OCI_TEMP_CLOB);
 951                          continue; // Column binding finished, go to next one
 952                      } else if (isset($value['blob'])) {
 953                          $lob = oci_new_descriptor($this->oci, OCI_DTYPE_LOB);
 954                          if ($descriptors === null) {
 955                              throw new coding_exception('moodle_database::bind_params() $descriptors not specified for clob');
 956                          }
 957                          $descriptors[] = $lob;
 958                          oci_bind_by_name($stmt, $key, $lob, -1, SQLT_BLOB);
 959                          $lob->writeTemporary($params[$key]['blob'], OCI_TEMP_BLOB);
 960                          continue; // Column binding finished, go to next one
 961                      }
 962                  } else {
 963                      // If, at this point, the param value > 4000 (bytes), let's assume it's a clob
 964                      // passed in an arbitrary sql (not processed by normalise_value() ever,
 965                      // and let's handle it as such. This will provide proper binding of CLOBs in
 966                      // conditions and other raw SQLs not covered by the above function.
 967                      if (strlen($value) > 4000) {
 968                          $lob = oci_new_descriptor($this->oci, OCI_DTYPE_LOB);
 969                          if ($descriptors === null) {
 970                              throw new coding_exception('moodle_database::bind_params() $descriptors not specified for clob');
 971                          }
 972                          $descriptors[] = $lob;
 973                          oci_bind_by_name($stmt, $key, $lob, -1, SQLT_CLOB);
 974                          $lob->writeTemporary($this->oracle_dirty_hack($tablename, $columnname, $params[$key]), OCI_TEMP_CLOB);
 975                          continue; // Param binding finished, go to next one.
 976                      }
 977                  }
 978                  // TODO: Put proper types and length is possible (enormous speedup)
 979                  // Arrived here, continue with standard processing, using metadata if possible
 980                  if (isset($columns[$columnname])) {
 981                      $type = $columns[$columnname]->meta_type;
 982                      $maxlength = $columns[$columnname]->max_length;
 983                  } else {
 984                      $type = '?';
 985                      $maxlength = -1;
 986                  }
 987                  switch ($type) {
 988                      case 'I':
 989                      case 'R':
 990                          // TODO: Optimise
 991                          oci_bind_by_name($stmt, $key, $params[$key]);
 992                          break;
 993  
 994                      case 'N':
 995                      case 'F':
 996                          // TODO: Optimise
 997                          oci_bind_by_name($stmt, $key, $params[$key]);
 998                          break;
 999  
1000                      case 'B':
1001                          // TODO: Only arrive here if BLOB is null: Bind if so, else exception!
1002                          // don't break here
1003  
1004                      case 'X':
1005                          // TODO: Only arrive here if CLOB is null or <= 4000 cc, else exception
1006                          // don't break here
1007  
1008                      default: // Bind as CHAR (applying dirty hack)
1009                          // TODO: Optimise
1010                          $params[$key] = $this->oracle_dirty_hack($tablename, $columnname, $params[$key]);
1011                          // Because of PHP7 bug (https://bugs.php.net/bug.php?id=72524) it seems that it's
1012                          // impossible to bind NULL values in a reliable way, let's use empty string
1013                          // instead in the mean time.
1014                          if ($params[$key] === null && version_compare(PHP_VERSION, '7.0.0', '>=')) {
1015                              $params[$key] = '';
1016                          }
1017                          oci_bind_by_name($stmt, $key, $params[$key]);
1018                  }
1019              }
1020          }
1021          return $descriptors;
1022      }
1023  
1024      protected function free_descriptors($descriptors) {
1025          foreach ($descriptors as $descriptor) {
1026              // Because all descriptors used in the driver come from LOB::writeTemporary() calls
1027              // we can safely close them here unconditionally.
1028              $descriptor->close();
1029              // Free resources.
1030              oci_free_descriptor($descriptor);
1031          }
1032      }
1033  
1034      /**
1035       * This function is used to convert all the Oracle 1-space defaults to the empty string
1036       * like a really DIRTY HACK to allow it to work better until all those NOT NULL DEFAULT ''
1037       * fields will be out from Moodle.
1038       * @param string the string to be converted to '' (empty string) if it's ' ' (one space)
1039       * @param mixed the key of the array in case we are using this function from array_walk,
1040       *              defaults to null for other (direct) uses
1041       * @return boolean always true (the converted variable is returned by reference)
1042       */
1043      public static function onespace2empty(&$item, $key=null) {
1044          $item = ($item === ' ') ? '' : $item;
1045          return true;
1046      }
1047  
1048      /**
1049       * Execute general sql query. Should be used only when no other method suitable.
1050       * Do NOT use this to make changes in db structure, use database_manager methods instead!
1051       * @param string $sql query
1052       * @param array $params query parameters
1053       * @return bool true
1054       * @throws dml_exception A DML specific exception is thrown for any errors.
1055       */
1056      public function execute($sql, array $params=null) {
1057          list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
1058  
1059          if (strpos($sql, ';') !== false) {
1060              throw new coding_exception('moodle_database::execute() Multiple sql statements found or bound parameters not used properly in query!');
1061          }
1062  
1063          list($sql, $params) = $this->tweak_param_names($sql, $params);
1064          $this->query_start($sql, $params, SQL_QUERY_UPDATE);
1065          $stmt = $this->parse_query($sql);
1066          $descriptors = array();
1067          $this->bind_params($stmt, $params, null, $descriptors);
1068          $result = oci_execute($stmt, $this->commit_status);
1069          $this->free_descriptors($descriptors);
1070          $this->query_end($result, $stmt);
1071          oci_free_statement($stmt);
1072  
1073          return true;
1074      }
1075  
1076      /**
1077       * Get a single database record as an object using a SQL statement.
1078       *
1079       * The SQL statement should normally only return one record.
1080       * It is recommended to use get_records_sql() if more matches possible!
1081       *
1082       * @param string $sql The SQL string you wish to be executed, should normally only return one record.
1083       * @param array $params array of sql parameters
1084       * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
1085       *                        IGNORE_MULTIPLE means return first, ignore multiple records found(not recommended);
1086       *                        MUST_EXIST means throw exception if no record or multiple records found
1087       * @return mixed a fieldset object containing the first matching record, false or exception if error not found depending on mode
1088       * @throws dml_exception A DML specific exception is thrown for any errors.
1089       */
1090      public function get_record_sql($sql, array $params=null, $strictness=IGNORE_MISSING) {
1091          $strictness = (int)$strictness;
1092          if ($strictness == IGNORE_MULTIPLE) {
1093              // do not limit here - ORA does not like that
1094              $rs = $this->get_recordset_sql($sql, $params);
1095              $result = false;
1096              foreach ($rs as $rec) {
1097                  $result = $rec;
1098                  break;
1099              }
1100              $rs->close();
1101              return $result;
1102          }
1103          return parent::get_record_sql($sql, $params, $strictness);
1104      }
1105  
1106      /**
1107       * Get a number of records as a moodle_recordset using a SQL statement.
1108       *
1109       * Since this method is a little less readable, use of it should be restricted to
1110       * code where it's possible there might be large datasets being returned.  For known
1111       * small datasets use get_records_sql - it leads to simpler code.
1112       *
1113       * The return type is like:
1114       * @see function get_recordset.
1115       *
1116       * @param string $sql the SQL select query to execute.
1117       * @param array $params array of sql parameters
1118       * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
1119       * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
1120       * @return moodle_recordset instance
1121       * @throws dml_exception A DML specific exception is thrown for any errors.
1122       */
1123      public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
1124  
1125          list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
1126  
1127          list($rawsql, $params) = $this->get_limit_sql($sql, $params, $limitfrom, $limitnum);
1128  
1129          list($rawsql, $params) = $this->tweak_param_names($rawsql, $params);
1130          $this->query_start($rawsql, $params, SQL_QUERY_SELECT);
1131          $stmt = $this->parse_query($rawsql);
1132          $descriptors = array();
1133          $this->bind_params($stmt, $params, null, $descriptors);
1134          $result = oci_execute($stmt, $this->commit_status);
1135          $this->free_descriptors($descriptors);
1136          $this->query_end($result, $stmt);
1137  
1138          return $this->create_recordset($stmt);
1139      }
1140  
1141      protected function create_recordset($stmt) {
1142          return new oci_native_moodle_recordset($stmt);
1143      }
1144  
1145      /**
1146       * Get a number of records as an array of objects using a SQL statement.
1147       *
1148       * Return value is like:
1149       * @see function get_records.
1150       *
1151       * @param string $sql the SQL select query to execute. The first column of this SELECT statement
1152       *   must be a unique value (usually the 'id' field), as it will be used as the key of the
1153       *   returned array.
1154       * @param array $params array of sql parameters
1155       * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
1156       * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
1157       * @return array of objects, or empty array if no records were found
1158       * @throws dml_exception A DML specific exception is thrown for any errors.
1159       */
1160      public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
1161  
1162          list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
1163  
1164          list($rawsql, $params) = $this->get_limit_sql($sql, $params, $limitfrom, $limitnum);
1165  
1166          list($rawsql, $params) = $this->tweak_param_names($rawsql, $params);
1167          $this->query_start($rawsql, $params, SQL_QUERY_SELECT);
1168          $stmt = $this->parse_query($rawsql);
1169          $descriptors = array();
1170          $this->bind_params($stmt, $params, null, $descriptors);
1171          $result = oci_execute($stmt, $this->commit_status);
1172          $this->free_descriptors($descriptors);
1173          $this->query_end($result, $stmt);
1174  
1175          $records = null;
1176          oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
1177          oci_free_statement($stmt);
1178  
1179          $return = array();
1180  
1181          foreach ($records as $row) {
1182              $row = array_change_key_case($row, CASE_LOWER);
1183              unset($row['oracle_rownum']);
1184              array_walk($row, array('oci_native_moodle_database', 'onespace2empty'));
1185              $id = reset($row);
1186              if (isset($return[$id])) {
1187                  $colname = key($row);
1188                  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);
1189              }
1190              $return[$id] = (object)$row;
1191          }
1192  
1193          return $return;
1194      }
1195  
1196      /**
1197       * Selects records and return values (first field) as an array using a SQL statement.
1198       *
1199       * @param string $sql The SQL query
1200       * @param array $params array of sql parameters
1201       * @return array of values
1202       * @throws dml_exception A DML specific exception is thrown for any errors.
1203       */
1204      public function get_fieldset_sql($sql, array $params=null) {
1205          list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
1206  
1207          list($sql, $params) = $this->tweak_param_names($sql, $params);
1208          $this->query_start($sql, $params, SQL_QUERY_SELECT);
1209          $stmt = $this->parse_query($sql);
1210          $descriptors = array();
1211          $this->bind_params($stmt, $params, null, $descriptors);
1212          $result = oci_execute($stmt, $this->commit_status);
1213          $this->free_descriptors($descriptors);
1214          $this->query_end($result, $stmt);
1215  
1216          $records = null;
1217          oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_COLUMN);
1218          oci_free_statement($stmt);
1219  
1220          $return = reset($records);
1221          array_walk($return, array('oci_native_moodle_database', 'onespace2empty'));
1222  
1223          return $return;
1224      }
1225  
1226      /**
1227       * Insert new record into database, as fast as possible, no safety checks, lobs not supported.
1228       * @param string $table name
1229       * @param mixed $params data record as object or array
1230       * @param bool $returnit return it of inserted record
1231       * @param bool $bulk true means repeated inserts expected
1232       * @param bool $customsequence true if 'id' included in $params, disables $returnid
1233       * @return bool|int true or new id
1234       * @throws dml_exception A DML specific exception is thrown for any errors.
1235       */
1236      public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) {
1237          if (!is_array($params)) {
1238              $params = (array)$params;
1239          }
1240  
1241          $returning = "";
1242  
1243          if ($customsequence) {
1244              if (!isset($params['id'])) {
1245                  throw new coding_exception('moodle_database::insert_record_raw() id field must be specified if custom sequences used.');
1246              }
1247              $returnid = false;
1248          } else {
1249              unset($params['id']);
1250              if ($returnid) {
1251                  $returning = " RETURNING id INTO :oracle_id"; // crazy name nobody is ever going to use or parameter ;-)
1252              }
1253          }
1254  
1255          if (empty($params)) {
1256              throw new coding_exception('moodle_database::insert_record_raw() no fields found.');
1257          }
1258  
1259          $fields = implode(',', array_keys($params));
1260          $values = array();
1261          foreach ($params as $pname => $value) {
1262              $values[] = ":$pname";
1263          }
1264          $values = implode(',', $values);
1265  
1266          $sql = "INSERT INTO {" . $table . "} ($fields) VALUES ($values)";
1267          list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
1268          $sql .= $returning;
1269  
1270          $id = 0;
1271  
1272          // note we don't need tweak_param_names() here. Placeholders are safe column names. MDL-28080
1273          // list($sql, $params) = $this->tweak_param_names($sql, $params);
1274          $this->query_start($sql, $params, SQL_QUERY_INSERT);
1275          $stmt = $this->parse_query($sql);
1276          if ($returning) {
1277              oci_bind_by_name($stmt, ":oracle_id", $id, 10, SQLT_INT);
1278          }
1279          $descriptors = array();
1280          $this->bind_params($stmt, $params, $table, $descriptors);
1281          $result = oci_execute($stmt, $this->commit_status);
1282          $this->free_descriptors($descriptors);
1283          $this->query_end($result, $stmt);
1284          oci_free_statement($stmt);
1285  
1286          if (!$returnid) {
1287              return true;
1288          }
1289  
1290          if (!$returning) {
1291              die('TODO - implement oracle 9.2 insert support'); //TODO
1292          }
1293  
1294          return (int)$id;
1295      }
1296  
1297      /**
1298       * Insert a record into a table and return the "id" field if required.
1299       *
1300       * Some conversions and safety checks are carried out. Lobs are supported.
1301       * If the return ID isn't required, then this just reports success as true/false.
1302       * $data is an object containing needed data
1303       * @param string $table The database table to be inserted into
1304       * @param object $data A data object with values for one or more fields in the record
1305       * @param bool $returnid Should the id of the newly created record entry be returned? If this option is not requested then true/false is returned.
1306       * @return bool|int true or new id
1307       * @throws dml_exception A DML specific exception is thrown for any errors.
1308       */
1309      public function insert_record($table, $dataobject, $returnid=true, $bulk=false) {
1310          $dataobject = (array)$dataobject;
1311  
1312          $columns = $this->get_columns($table);
1313          if (empty($columns)) {
1314              throw new dml_exception('ddltablenotexist', $table);
1315          }
1316  
1317          $cleaned = array();
1318  
1319          foreach ($dataobject as $field=>$value) {
1320              if ($field === 'id') {
1321                  continue;
1322              }
1323              if (!isset($columns[$field])) { // Non-existing table field, skip it
1324                  continue;
1325              }
1326              $column = $columns[$field];
1327              $cleaned[$field] = $this->normalise_value($column, $value);
1328          }
1329  
1330          return $this->insert_record_raw($table, $cleaned, $returnid, $bulk);
1331      }
1332  
1333      /**
1334       * Import a record into a table, id field is required.
1335       * Safety checks are NOT carried out. Lobs are supported.
1336       *
1337       * @param string $table name of database table to be inserted into
1338       * @param object $dataobject A data object with values for one or more fields in the record
1339       * @return bool true
1340       * @throws dml_exception A DML specific exception is thrown for any errors.
1341       */
1342      public function import_record($table, $dataobject) {
1343          $dataobject = (array)$dataobject;
1344  
1345          $columns = $this->get_columns($table);
1346          $cleaned = array();
1347  
1348          foreach ($dataobject as $field=>$value) {
1349              if (!isset($columns[$field])) {
1350                  continue;
1351              }
1352              $column = $columns[$field];
1353              $cleaned[$field] = $this->normalise_value($column, $value);
1354          }
1355  
1356          return $this->insert_record_raw($table, $cleaned, false, true, true);
1357      }
1358  
1359      /**
1360       * Update record in database, as fast as possible, no safety checks, lobs not supported.
1361       * @param string $table name
1362       * @param mixed $params data record as object or array
1363       * @param bool true means repeated updates expected
1364       * @return bool true
1365       * @throws dml_exception A DML specific exception is thrown for any errors.
1366       */
1367      public function update_record_raw($table, $params, $bulk=false) {
1368          $params = (array)$params;
1369  
1370          if (!isset($params['id'])) {
1371              throw new coding_exception('moodle_database::update_record_raw() id field must be specified.');
1372          }
1373  
1374          if (empty($params)) {
1375              throw new coding_exception('moodle_database::update_record_raw() no fields found.');
1376          }
1377  
1378          $sets = array();
1379          foreach ($params as $field=>$value) {
1380              if ($field == 'id') {
1381                  continue;
1382              }
1383              $sets[] = "$field = :$field";
1384          }
1385  
1386          $sets = implode(',', $sets);
1387          $sql = "UPDATE {" . $table . "} SET $sets WHERE id=:id";
1388          list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
1389  
1390          // note we don't need tweak_param_names() here. Placeholders are safe column names. MDL-28080
1391          // list($sql, $params) = $this->tweak_param_names($sql, $params);
1392          $this->query_start($sql, $params, SQL_QUERY_UPDATE);
1393          $stmt = $this->parse_query($sql);
1394          $descriptors = array();
1395          $this->bind_params($stmt, $params, $table, $descriptors);
1396          $result = oci_execute($stmt, $this->commit_status);
1397          $this->free_descriptors($descriptors);
1398          $this->query_end($result, $stmt);
1399          oci_free_statement($stmt);
1400  
1401          return true;
1402      }
1403  
1404      /**
1405       * Update a record in a table
1406       *
1407       * $dataobject is an object containing needed data
1408       * Relies on $dataobject having a variable "id" to
1409       * specify the record to update
1410       *
1411       * @param string $table The database table to be checked against.
1412       * @param object $dataobject An object with contents equal to fieldname=>fieldvalue. Must have an entry for 'id' to map to the table specified.
1413       * @param bool true means repeated updates expected
1414       * @return bool true
1415       * @throws dml_exception A DML specific exception is thrown for any errors.
1416       */
1417      public function update_record($table, $dataobject, $bulk=false) {
1418          $dataobject = (array)$dataobject;
1419  
1420          $columns = $this->get_columns($table);
1421          $cleaned = array();
1422  
1423          foreach ($dataobject as $field=>$value) {
1424              if (!isset($columns[$field])) {
1425                  continue;
1426              }
1427              $column = $columns[$field];
1428              $cleaned[$field] = $this->normalise_value($column, $value);
1429          }
1430  
1431          $this->update_record_raw($table, $cleaned, $bulk);
1432  
1433          return true;
1434      }
1435  
1436      /**
1437       * Set a single field in every table record which match a particular WHERE clause.
1438       *
1439       * @param string $table The database table to be checked against.
1440       * @param string $newfield the field to set.
1441       * @param string $newvalue the value to set the field to.
1442       * @param string $select A fragment of SQL to be used in a where clause in the SQL call.
1443       * @param array $params array of sql parameters
1444       * @return bool true
1445       * @throws dml_exception A DML specific exception is thrown for any errors.
1446       */
1447      public function set_field_select($table, $newfield, $newvalue, $select, array $params=null) {
1448  
1449          if ($select) {
1450              $select = "WHERE $select";
1451          }
1452          if (is_null($params)) {
1453              $params = array();
1454          }
1455  
1456          // Get column metadata
1457          $columns = $this->get_columns($table);
1458          $column = $columns[$newfield];
1459  
1460          $newvalue = $this->normalise_value($column, $newvalue);
1461  
1462          list($select, $params, $type) = $this->fix_sql_params($select, $params);
1463  
1464          if (is_bool($newvalue)) {
1465              $newvalue = (int)$newvalue; // prevent "false" problems
1466          }
1467          if (is_null($newvalue)) {
1468              $newsql = "$newfield = NULL";
1469          } else {
1470              // Set the param to array ($newfield => $newvalue) and key to 'newfieldtoset'
1471              // name in the build sql. Later, bind_params() will detect the value array and
1472              // perform the needed modifications to allow the query to work. Note that
1473              // 'newfieldtoset' is one arbitrary name that hopefully won't be used ever
1474              // in order to avoid problems where the same field is used both in the set clause and in
1475              // the conditions. This was breaking badly in drivers using NAMED params like oci.
1476              $params['newfieldtoset'] = array($newfield => $newvalue);
1477              $newsql = "$newfield = :newfieldtoset";
1478          }
1479          $sql = "UPDATE {" . $table . "} SET $newsql $select";
1480          list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
1481  
1482          list($sql, $params) = $this->tweak_param_names($sql, $params);
1483          $this->query_start($sql, $params, SQL_QUERY_UPDATE);
1484          $stmt = $this->parse_query($sql);
1485          $descriptors = array();
1486          $this->bind_params($stmt, $params, $table, $descriptors);
1487          $result = oci_execute($stmt, $this->commit_status);
1488          $this->free_descriptors($descriptors);
1489          $this->query_end($result, $stmt);
1490          oci_free_statement($stmt);
1491  
1492          return true;
1493      }
1494  
1495      /**
1496       * Delete one or more records from a table which match a particular WHERE clause.
1497       *
1498       * @param string $table The database table to be checked against.
1499       * @param string $select A fragment of SQL to be used in a where clause in the SQL call (used to define the selection criteria).
1500       * @param array $params array of sql parameters
1501       * @return bool true
1502       * @throws dml_exception A DML specific exception is thrown for any errors.
1503       */
1504      public function delete_records_select($table, $select, array $params=null) {
1505  
1506          if ($select) {
1507              $select = "WHERE $select";
1508          }
1509  
1510          $sql = "DELETE FROM {" . $table . "} $select";
1511  
1512          list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
1513  
1514          list($sql, $params) = $this->tweak_param_names($sql, $params);
1515          $this->query_start($sql, $params, SQL_QUERY_UPDATE);
1516          $stmt = $this->parse_query($sql);
1517          $descriptors = array();
1518          $this->bind_params($stmt, $params, null, $descriptors);
1519          $result = oci_execute($stmt, $this->commit_status);
1520          $this->free_descriptors($descriptors);
1521          $this->query_end($result, $stmt);
1522          oci_free_statement($stmt);
1523  
1524          return true;
1525      }
1526  
1527      function sql_null_from_clause() {
1528          return ' FROM dual';
1529      }
1530  
1531      public function sql_bitand($int1, $int2) {
1532          return 'bitand((' . $int1 . '), (' . $int2 . '))';
1533      }
1534  
1535      public function sql_bitnot($int1) {
1536          return '((0 - (' . $int1 . ')) - 1)';
1537      }
1538  
1539      public function sql_bitor($int1, $int2) {
1540          return 'MOODLELIB.BITOR(' . $int1 . ', ' . $int2 . ')';
1541      }
1542  
1543      public function sql_bitxor($int1, $int2) {
1544          return 'MOODLELIB.BITXOR(' . $int1 . ', ' . $int2 . ')';
1545      }
1546  
1547      /**
1548       * Returns the SQL text to be used in order to perform module '%'
1549       * operation - remainder after division
1550       *
1551       * @param integer int1 first integer in the operation
1552       * @param integer int2 second integer in the operation
1553       * @return string the piece of SQL code to be used in your statement.
1554       */
1555      public function sql_modulo($int1, $int2) {
1556          return 'MOD(' . $int1 . ', ' . $int2 . ')';
1557      }
1558  
1559      public function sql_cast_char2int($fieldname, $text=false) {
1560          if (!$text) {
1561              return ' CAST(' . $fieldname . ' AS INT) ';
1562          } else {
1563              return ' CAST(' . $this->sql_compare_text($fieldname) . ' AS INT) ';
1564          }
1565      }
1566  
1567      public function sql_cast_char2real($fieldname, $text=false) {
1568          if (!$text) {
1569              return ' CAST(' . $fieldname . ' AS FLOAT) ';
1570          } else {
1571              return ' CAST(' . $this->sql_compare_text($fieldname) . ' AS FLOAT) ';
1572          }
1573      }
1574  
1575      /**
1576       * Returns 'LIKE' part of a query.
1577       *
1578       * @param string $fieldname usually name of the table column
1579       * @param string $param usually bound query parameter (?, :named)
1580       * @param bool $casesensitive use case sensitive search
1581       * @param bool $accensensitive use accent sensitive search (not all databases support accent insensitive)
1582       * @param bool $notlike true means "NOT LIKE"
1583       * @param string $escapechar escape char for '%' and '_'
1584       * @return string SQL code fragment
1585       */
1586      public function sql_like($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notlike = false, $escapechar = '\\') {
1587          if (strpos($param, '%') !== false) {
1588              debugging('Potential SQL injection detected, sql_like() expects bound parameters (? or :named)');
1589          }
1590  
1591          $LIKE = $notlike ? 'NOT LIKE' : 'LIKE';
1592  
1593          // no accent sensitiveness here for now, sorry
1594  
1595          if ($casesensitive) {
1596              return "$fieldname $LIKE $param ESCAPE '$escapechar'";
1597          } else {
1598              return "LOWER($fieldname) $LIKE LOWER($param) ESCAPE '$escapechar'";
1599          }
1600      }
1601  
1602      public function sql_concat() {
1603          $arr = func_get_args();
1604          if (empty($arr)) {
1605              return " ' ' ";
1606          }
1607          foreach ($arr as $k => $v) {
1608              if ($v === "' '") {
1609                  $arr[$k] = "'*OCISP*'"; // New mega hack.
1610              }
1611          }
1612          $s = $this->recursive_concat($arr);
1613          return " MOODLELIB.UNDO_MEGA_HACK($s) ";
1614      }
1615  
1616      public function sql_concat_join($separator="' '", $elements = array()) {
1617          if ($separator === "' '") {
1618              $separator = "'*OCISP*'"; // New mega hack.
1619          }
1620          foreach ($elements as $k => $v) {
1621              if ($v === "' '") {
1622                  $elements[$k] = "'*OCISP*'"; // New mega hack.
1623              }
1624          }
1625          for ($n = count($elements)-1; $n > 0 ; $n--) {
1626              array_splice($elements, $n, 0, $separator);
1627          }
1628          if (empty($elements)) {
1629              return " ' ' ";
1630          }
1631          $s = $this->recursive_concat($elements);
1632          return " MOODLELIB.UNDO_MEGA_HACK($s) ";
1633      }
1634  
1635      /**
1636       * Constructs 'IN()' or '=' sql fragment
1637       *
1638       * Method overriding {@link moodle_database::get_in_or_equal} to be able to get
1639       * more than 1000 elements working, to avoid ORA-01795. We use a pivoting technique
1640       * to be able to transform the params into virtual rows, so the original IN()
1641       * expression gets transformed into a subquery. Once more, be noted that we shouldn't
1642       * be using ever get_in_or_equal() with such number of parameters (proper subquery and/or
1643       * chunking should be used instead).
1644       *
1645       * @param mixed $items A single value or array of values for the expression.
1646       * @param int $type Parameter bounding type : SQL_PARAMS_QM or SQL_PARAMS_NAMED.
1647       * @param string $prefix Named parameter placeholder prefix (a unique counter value is appended to each parameter name).
1648       * @param bool $equal True means we want to equate to the constructed expression, false means we don't want to equate to it.
1649       * @param mixed $onemptyitems This defines the behavior when the array of items provided is empty. Defaults to false,
1650       *              meaning throw exceptions. Other values will become part of the returned SQL fragment.
1651       * @throws coding_exception | dml_exception
1652       * @return array A list containing the constructed sql fragment and an array of parameters.
1653       */
1654      public function get_in_or_equal($items, $type=SQL_PARAMS_QM, $prefix='param', $equal=true, $onemptyitems=false) {
1655          list($sql, $params) = parent::get_in_or_equal($items, $type, $prefix,  $equal, $onemptyitems);
1656  
1657          // Less than 1000 elements, nothing to do.
1658          if (count($params) < 1000) {
1659              return array($sql, $params); // Return unmodified.
1660          }
1661  
1662          // Extract the interesting parts of the sql to rewrite.
1663          if (preg_match('!(^.*IN \()([^\)]*)(.*)$!', $sql, $matches) === false) {
1664              return array($sql, $params); // Return unmodified.
1665          }
1666  
1667          $instart = $matches[1];
1668          $insql = $matches[2];
1669          $inend = $matches[3];
1670          $newsql = '';
1671  
1672          // Some basic verification about the matching going ok.
1673          $insqlarr = explode(',', $insql);
1674          if (count($insqlarr) !== count($params)) {
1675              return array($sql, $params); // Return unmodified.
1676          }
1677  
1678          // Arrived here, we need to chunk and pivot the params, building a new sql (params remain the same).
1679          $addunionclause = false;
1680          while ($chunk = array_splice($insqlarr, 0, 125)) { // Each chunk will handle up to 125 (+125 +1) elements (DECODE max is 255).
1681              $chunksize = count($chunk);
1682              if ($addunionclause) {
1683                  $newsql .= "\n    UNION ALL";
1684              }
1685              $newsql .= "\n        SELECT DECODE(pivot";
1686              $counter = 1;
1687              foreach ($chunk as $element) {
1688                  $newsql .= ",\n            {$counter}, " . trim($element);
1689                  $counter++;
1690              }
1691              $newsql .= ")";
1692              $newsql .= "\n        FROM dual";
1693              $newsql .= "\n        CROSS JOIN (SELECT LEVEL AS pivot FROM dual CONNECT BY LEVEL <= {$chunksize})";
1694              $addunionclause = true;
1695          }
1696  
1697          // Rebuild the complete IN() clause and return it.
1698          return array($instart . $newsql . $inend, $params);
1699      }
1700  
1701      /**
1702       * Mega hacky magic to work around crazy Oracle NULL concats.
1703       * @param array $args
1704       * @return string
1705       */
1706      protected function recursive_concat(array $args) {
1707          $count = count($args);
1708          if ($count == 1) {
1709              $arg = reset($args);
1710              return $arg;
1711          }
1712          if ($count == 2) {
1713              $args[] = "' '";
1714              // No return here intentionally.
1715          }
1716          $first = array_shift($args);
1717          $second = array_shift($args);
1718          $third = $this->recursive_concat($args);
1719          return "MOODLELIB.TRICONCAT($first, $second, $third)";
1720      }
1721  
1722      /**
1723       * Returns the SQL for returning searching one string for the location of another.
1724       */
1725      public function sql_position($needle, $haystack) {
1726          return "INSTR(($haystack), ($needle))";
1727      }
1728  
1729      /**
1730       * Returns the SQL to know if one field is empty.
1731       *
1732       * @param string $tablename Name of the table (without prefix). Not used for now but can be
1733       *                          necessary in the future if we want to use some introspection using
1734       *                          meta information against the DB.
1735       * @param string $fieldname Name of the field we are going to check
1736       * @param bool $nullablefield For specifying if the field is nullable (true) or no (false) in the DB.
1737       * @param bool $textfield For specifying if it is a text (also called clob) field (true) or a varchar one (false)
1738       * @return string the sql code to be added to check for empty values
1739       */
1740      public function sql_isempty($tablename, $fieldname, $nullablefield, $textfield) {
1741          if ($textfield) {
1742              return " (".$this->sql_compare_text($fieldname)." = ' ') ";
1743          } else {
1744              return " ($fieldname = ' ') ";
1745          }
1746      }
1747  
1748      public function sql_order_by_text($fieldname, $numchars=32) {
1749          return 'dbms_lob.substr(' . $fieldname . ', ' . $numchars . ',1)';
1750      }
1751  
1752      /**
1753       * Is the required OCI server package installed?
1754       * @return bool
1755       */
1756      protected function oci_package_installed() {
1757          $sql = "SELECT 1
1758                  FROM user_objects
1759                  WHERE object_type = 'PACKAGE BODY'
1760                    AND object_name = 'MOODLELIB'
1761                    AND status = 'VALID'";
1762          $this->query_start($sql, null, SQL_QUERY_AUX);
1763          $stmt = $this->parse_query($sql);
1764          $result = oci_execute($stmt, $this->commit_status);
1765          $this->query_end($result, $stmt);
1766          $records = null;
1767          oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
1768          oci_free_statement($stmt);
1769          return isset($records[0]) && reset($records[0]) ? true : false;
1770      }
1771  
1772      /**
1773       * Try to add required moodle package into oracle server.
1774       */
1775      protected function attempt_oci_package_install() {
1776          $sqls = file_get_contents(__DIR__.'/oci_native_moodle_package.sql');
1777          $sqls = preg_split('/^\/$/sm', $sqls);
1778          foreach ($sqls as $sql) {
1779              $sql = trim($sql);
1780              if ($sql === '' or $sql === 'SHOW ERRORS') {
1781                  continue;
1782              }
1783              $this->change_database_structure($sql);
1784          }
1785      }
1786  
1787      /**
1788       * Does this driver support tool_replace?
1789       *
1790       * @since Moodle 2.8
1791       * @return bool
1792       */
1793      public function replace_all_text_supported() {
1794          return true;
1795      }
1796  
1797      public function session_lock_supported() {
1798          return true;
1799      }
1800  
1801      /**
1802       * Obtain session lock
1803       * @param int $rowid id of the row with session record
1804       * @param int $timeout max allowed time to wait for the lock in seconds
1805       * @return void
1806       */
1807      public function get_session_lock($rowid, $timeout) {
1808          parent::get_session_lock($rowid, $timeout);
1809  
1810          $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
1811          $sql = 'SELECT MOODLELIB.GET_LOCK(:lockname, :locktimeout) FROM DUAL';
1812          $params = array('lockname' => $fullname , 'locktimeout' => $timeout);
1813          $this->query_start($sql, $params, SQL_QUERY_AUX);
1814          $stmt = $this->parse_query($sql);
1815          $this->bind_params($stmt, $params);
1816          $result = oci_execute($stmt, $this->commit_status);
1817          if ($result === false) { // Any failure in get_lock() raises error, causing return of bool false
1818              throw new dml_sessionwait_exception();
1819          }
1820          $this->query_end($result, $stmt);
1821          oci_free_statement($stmt);
1822      }
1823  
1824      public function release_session_lock($rowid) {
1825          if (!$this->used_for_db_sessions) {
1826              return;
1827          }
1828  
1829          parent::release_session_lock($rowid);
1830  
1831          $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
1832          $params = array('lockname' => $fullname);
1833          $sql = 'SELECT MOODLELIB.RELEASE_LOCK(:lockname) FROM DUAL';
1834          $this->query_start($sql, $params, SQL_QUERY_AUX);
1835          $stmt = $this->parse_query($sql);
1836          $this->bind_params($stmt, $params);
1837          $result = oci_execute($stmt, $this->commit_status);
1838          $this->query_end($result, $stmt);
1839          oci_free_statement($stmt);
1840      }
1841  
1842      /**
1843       * Driver specific start of real database transaction,
1844       * this can not be used directly in code.
1845       * @return void
1846       */
1847      protected function begin_transaction() {
1848          $this->commit_status = OCI_DEFAULT; //Done! ;-)
1849      }
1850  
1851      /**
1852       * Driver specific commit of real database transaction,
1853       * this can not be used directly in code.
1854       * @return void
1855       */
1856      protected function commit_transaction() {
1857          $this->query_start('--oracle_commit', NULL, SQL_QUERY_AUX);
1858          $result = oci_commit($this->oci);
1859          $this->commit_status = OCI_COMMIT_ON_SUCCESS;
1860          $this->query_end($result);
1861      }
1862  
1863      /**
1864       * Driver specific abort of real database transaction,
1865       * this can not be used directly in code.
1866       * @return void
1867       */
1868      protected function rollback_transaction() {
1869          $this->query_start('--oracle_rollback', NULL, SQL_QUERY_AUX);
1870          $result = oci_rollback($this->oci);
1871          $this->commit_status = OCI_COMMIT_ON_SUCCESS;
1872          $this->query_end($result);
1873      }
1874  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1