repository/local MDL-16597 Some wording changes local -> server (more user-centric)
[moodle.git] / lib / dml / oci_native_moodle_database.php
CommitLineData
49926145 1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18
19/**
20 * Native oci class representing moodle database interface.
21 *
22 * @package moodlecore
23 * @subpackage DML
24 * @copyright 2008 Petr Skoda (http://skodak.org)
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 */
66e75f8d 27
28require_once($CFG->libdir.'/dml/moodle_database.php');
29require_once($CFG->libdir.'/dml/oci_native_moodle_recordset.php');
30
31/**
32 * Native oci class representing moodle database interface.
8089f8f6 33 *
34 * One complete reference for PHP + OCI:
35 * http://www.oracle.com/technology/tech/php/underground-php-oracle-manual.html
66e75f8d 36 */
37class oci_native_moodle_database extends moodle_database {
38
39 protected $oci = null;
66e75f8d 40
7cfaef13 41 private $last_stmt_error = null; // To store stmt errors and enable get_last_error() to detect them
8089f8f6 42 private $commit_status = OCI_COMMIT_ON_SUCCESS; // Autocommit ON by default. Switching to OFF (OCI_DEFAULT)
43 // when playing with transactions
66e75f8d 44
45 /**
46 * Detects if all needed PHP stuff installed.
47 * Note: can be used before connect()
48 * @return mixed true if ok, string if something
49 */
50 public function driver_installed() {
51 if (!extension_loaded('oci8')) {
52 return get_string('ociextensionisnotpresentinphp', 'install');
53 }
54 return true;
55 }
56
57 /**
58 * Returns database family type - describes SQL dialect
59 * Note: can be used before connect()
60 * @return string db family name (mysql, postgres, mssql, oracle, etc.)
61 */
62 public function get_dbfamily() {
63 return 'oracle';
64 }
65
66 /**
67 * Returns more specific database driver type
68 * Note: can be used before connect()
69 * @return string db type mysql, oci, postgres7
70 */
71 protected function get_dbtype() {
72 return 'oci';
73 }
74
75 /**
76 * Returns general database library name
77 * Note: can be used before connect()
78 * @return string db type adodb, pdo, native
79 */
80 protected function get_dblibrary() {
81 return 'native';
82 }
83
84 /**
85 * Returns localised database type name
86 * Note: can be used before connect()
87 * @return string
88 */
89 public function get_name() {
90 return get_string('nativeoci', 'install'); // TODO: localise
91 }
92
3b093310 93 /**
94 * Returns localised database configuration help.
95 * Note: can be used before connect()
96 * @return string
97 */
98 public function get_configuration_help() {
99 return get_string('nativeocihelp', 'install');
100 }
101
66e75f8d 102 /**
103 * Returns localised database description
104 * Note: can be used before connect()
105 * @return string
106 */
107 public function get_configuration_hints() {
108 return get_string('databasesettingssub_oci', 'install'); // TODO: l
109 }
110
111 /**
112 * Connect to db
113 * Must be called before other methods.
114 * @param string $dbhost
115 * @param string $dbuser
116 * @param string $dbpass
117 * @param string $dbname
118 * @param mixed $prefix string means moodle db prefix, false used for external databases where prefix not used
119 * @param array $dboptions driver specific options
120 * @return bool true
121 * @throws dml_connection_exception if error
122 */
123 public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) {
124 if ($prefix == '' and !$this->external) {
125 //Enforce prefixes for everybody but mysql
126 throw new dml_exception('prefixcannotbeempty', $this->get_dbfamily());
127 }
128 if (!$this->external and strlen($prefix) > 2) {
129 //Max prefix length for Oracle is 2cc
130 $a = (object)array('dbfamily'=>'oracle', 'maxlength'=>2);
131 throw new dml_exception('prefixtoolong', $a);
132 }
133
134 $driverstatus = $this->driver_installed();
135
136 if ($driverstatus !== true) {
137 throw new dml_exception('dbdriverproblem', $driverstatus);
138 }
139
140 $this->store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions);
3b093310 141 unset($this->dboptions['dbsocket']);
66e75f8d 142
143 $pass = addcslashes($this->dbpass, "'\\");
144
145 if (empty($this->dbhost)) {
146 // old style full address
147 } else {
148 if (empty($this->dboptions['dbport'])) {
149 $this->dboptions['dbport'] = 1521;
150 }
151 $this->dbname = '//'.$this->dbhost.':'.$this->dboptions['dbport'].'/'.$this->dbname;
152 }
153
154 ob_start();
155 if (empty($this->dboptions['dbpersit'])) {
8089f8f6 156 $this->oci = oci_connect($this->dbuser, $this->dbpass, $this->dbname, 'AL32UTF8');
66e75f8d 157 } else {
8089f8f6 158 $this->oci = oci_pconnect($this->dbuser, $this->dbpass, $this->dbname, 'AL32UTF8');
66e75f8d 159 }
160 $dberr = ob_get_contents();
161 ob_end_clean();
162
163
164 if ($this->oci === false) {
165 $this->oci = null;
166 $e = oci_error();
167 if (isset($e['message'])) {
168 $dberr = $e['message'];
169 }
170 throw new dml_connection_exception($dberr);
171 }
172
173 //note: do not send "ALTER SESSION SET NLS_NUMERIC_CHARACTERS='.,'" !
174 // instead fix our PHP code to convert "," to "." properly!
175
176 return true;
177 }
178
179 /**
180 * Close database connection and release all resources
181 * and memory (especially circular memory references).
182 * Do NOT use connect() again, create a new instance if needed.
183 */
184 public function dispose() {
8fdb24f0 185 parent::dispose(); // Call parent dispose to write/close session and other common stuff before clossing conn
66e75f8d 186 if ($this->oci) {
187 oci_close($this->oci);
188 $this->oci = null;
189 }
66e75f8d 190 }
191
192
193 /**
194 * Called before each db query.
195 * @param string $sql
196 * @param array array of parameters
197 * @param int $type type of query
198 * @param mixed $extrainfo driver specific extra information
199 * @return void
200 */
201 protected function query_start($sql, array $params=null, $type, $extrainfo=null) {
202 parent::query_start($sql, $params, $type, $extrainfo);
66e75f8d 203 }
204
205 /**
206 * Called immediately after each db query.
207 * @param mixed db specific result
208 * @return void
209 */
210 protected function query_end($result, $stmt=null) {
66e75f8d 211 if ($stmt and $result === false) {
7cfaef13 212 // Look for stmt error and store it
213 if (is_resource($stmt)) {
214 $e = oci_error($stmt);
215 if ($e !== false) {
216 $this->last_stmt_error = $e['message'];
217 }
218 }
66e75f8d 219 oci_free_statement($stmt);
220 }
221 parent::query_end($result);
222 }
223
224 /**
225 * Returns database server info array
226 * @return array
227 */
228 public function get_server_info() {
229 static $info = null; // TODO: move to real object property
230
231 if (is_null($info)) {
232 $this->query_start("--oci_server_version()", null, SQL_QUERY_AUX);
233 $description = oci_server_version($this->oci);
234 $this->query_end(true);
235 preg_match('/(\d+\.)+\d+/', $description, $matches);
236 $info = array('description'=>$description, 'version'=>$matches[0]);
237 }
238
239 return $info;
240 }
241
242 protected function is_min_version($version) {
243 $server = $this->get_server_info();
244 $server = $server['version'];
245 return version_compare($server, $version, '>=');
246 }
247
248 /**
249 * Returns supported query parameter types
250 * @return bitmask
251 */
252 protected function allowed_param_types() {
253 return SQL_PARAMS_NAMED;
254 }
255
256 /**
257 * Returns last error reported by database engine.
258 */
259 public function get_last_error() {
7cfaef13 260 $error = false;
261 // First look for any previously saved stmt error
262 if (!empty($this->last_stmt_error)) {
263 $error = $this->last_stmt_error;
264 $this->last_stmt_error = null;
265 } else { // Now try connection error
266 $e = oci_error($this->oci);
267 if ($e !== false) {
268 $error = $e['message'];
269 }
66e75f8d 270 }
7cfaef13 271 return $error;
66e75f8d 272 }
273
274 protected function parse_query($sql) {
275 $stmt = oci_parse($this->oci, $sql);
8089f8f6 276 if ($stmt == false) {
66e75f8d 277 throw new dml_connection_exception('Can not parse sql query'); //TODO: maybe add better info
278 }
279 return $stmt;
280 }
281
282 /**
283 * Return tables in database WITHOUT current prefix
284 * @return array of table names in lowercase and without prefix
285 */
117679db 286 public function get_tables($usecache=true) {
66e75f8d 287 $tables = array();
288 $prefix = str_replace('_', "\\_", strtoupper($this->prefix));
289 $sql = "SELECT TABLE_NAME
290 FROM CAT
291 WHERE TABLE_TYPE='TABLE'
292 AND TABLE_NAME NOT LIKE 'BIN\$%'
293 AND TABLE_NAME LIKE '$prefix%' ESCAPE '\\'";
294 $this->query_start($sql, null, SQL_QUERY_AUX);
295 $stmt = $this->parse_query($sql);
8089f8f6 296 $result = oci_execute($stmt, $this->commit_status);
66e75f8d 297 $this->query_end($result, $stmt);
298 $records = null;
299 oci_fetch_all($stmt, $records, 0, -1, OCI_ASSOC);
300 oci_free_statement($stmt);
301 $records = array_map('strtolower', $records['TABLE_NAME']);
302 foreach ($records as $tablename) {
303 if (strpos($tablename, $this->prefix) !== 0) {
304 continue;
305 }
306 $tablename = substr($tablename, strlen($this->prefix));
307 $tables[$tablename] = $tablename;
308 }
309
310 return $tables;
311 }
312
313 /**
314 * Return table indexes - everything lowercased
315 * @return array of arrays
316 */
317 public function get_indexes($table) {
318 $indexes = array();
319 $tablename = strtoupper($this->prefix.$table);
320
321 $sql = "SELECT i.INDEX_NAME, i.UNIQUENESS, c.COLUMN_POSITION, c.COLUMN_NAME, ac.CONSTRAINT_TYPE
322 FROM ALL_INDEXES i
323 JOIN ALL_IND_COLUMNS c ON c.INDEX_NAME=i.INDEX_NAME
324 LEFT JOIN ALL_CONSTRAINTS ac ON (ac.TABLE_NAME=i.TABLE_NAME AND ac.CONSTRAINT_NAME=i.INDEX_NAME AND ac.CONSTRAINT_TYPE='P')
325 WHERE i.TABLE_NAME = '$tablename'
326 ORDER BY i.INDEX_NAME, c.COLUMN_POSITION";
327
328 $stmt = $this->parse_query($sql);
8089f8f6 329 $result = oci_execute($stmt, $this->commit_status);
66e75f8d 330 $this->query_end($result, $stmt);
331 $records = null;
332 oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
333 oci_free_statement($stmt);
334
335 foreach ($records as $record) {
336 if ($record['CONSTRAINT_TYPE'] === 'P') {
337 //ignore for now;
338 continue;
339 }
340 $indexname = strtolower($record['INDEX_NAME']);
341 if (!isset($indexes[$indexname])) {
342 $indexes[$indexname] = array('primary' => ($record['CONSTRAINT_TYPE'] === 'P'),
343 'unique' => ($record['UNIQUENESS'] === 'UNIQUE'),
344 'columns' => array());
345 }
346 $indexes[$indexname]['columns'][] = strtolower($record['COLUMN_NAME']);
347 }
348
349 return $indexes;
350 }
351
352 /**
353 * Returns datailed information about columns in table. This information is cached internally.
354 * @param string $table name
355 * @param bool $usecache
356 * @return array array of database_column_info objects indexed with column names
357 */
358 public function get_columns($table, $usecache=true) {
359 if ($usecache and isset($this->columns[$table])) {
360 return $this->columns[$table];
361 }
362
363 $this->columns[$table] = array();
364
365 $tablename = strtoupper($this->prefix.$table);
366
367 $sql = "SELECT CNAME, COLTYPE, WIDTH, SCALE, PRECISION, NULLS, DEFAULTVAL
368 FROM COL
369 WHERE TNAME='$tablename'
370 ORDER BY COLNO";
371
372 $this->query_start($sql, null, SQL_QUERY_AUX);
373 $stmt = $this->parse_query($sql);
8089f8f6 374 $result = oci_execute($stmt, $this->commit_status);
66e75f8d 375 $this->query_end($result, $stmt);
376 $records = null;
377 oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
378 oci_free_statement($stmt);
379
380 if (!$records) {
381 return array();
382 }
383 foreach ($records as $rawcolumn) {
384 $rawcolumn = (object)$rawcolumn;
385
386 $info = new object();
387 $info->name = strtolower($rawcolumn->CNAME);
388 $matches = null;
389
390 if ($rawcolumn->COLTYPE === 'VARCHAR2'
391 or $rawcolumn->COLTYPE === 'VARCHAR'
392 or $rawcolumn->COLTYPE === 'NVARCHAR2'
393 or $rawcolumn->COLTYPE === 'NVARCHAR'
394 or $rawcolumn->COLTYPE === 'CHAR'
395 or $rawcolumn->COLTYPE === 'NCHAR') {
396 //TODO add some basic enum support here
397 $info->type = $rawcolumn->COLTYPE;
398 $info->meta_type = 'C';
399 $info->max_length = $rawcolumn->WIDTH;
400 $info->scale = null;
401 $info->not_null = ($rawcolumn->NULLS === 'NOT NULL');
402 $info->has_default = !is_null($rawcolumn->DEFAULTVAL);
403 if ($info->has_default) {
404 // this is hacky :-(
405 if ($rawcolumn->DEFAULTVAL === 'NULL') {
406 $info->default_value = null;
407 } else if ($rawcolumn->DEFAULTVAL === "' ' ") {
408 $info->default_value = "";
409 } else {
410 $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space
411 $info->default_value = substr($info->default_value, 1, strlen($info->default_value)-2); //trim ''
412 }
413 } else {
414 $info->default_value = null;
415 }
416 $info->primary_key = false;
417 $info->binary = false;
418 $info->unsigned = null;
419 $info->auto_increment= false;
420 $info->unique = null;
421
422 } else if ($rawcolumn->COLTYPE === 'NUMBER') {
423 $info->type = $rawcolumn->COLTYPE;
424 $info->max_length = $rawcolumn->PRECISION;
425 $info->binary = false;
426 if ($rawcolumn->SCALE == 0) {
427 // integer
428 if ($info->name === 'id') {
429 $info->primary_key = true;
430 $info->meta_type = 'R';
431 $info->unique = true;
432 $info->auto_increment= true;
433 $info->has_default = false;
434 } else {
435 $info->primary_key = false;
436 $info->meta_type = 'I';
437 $info->unique = null;
438 $info->auto_increment= false;
439 }
440 $info->scale = null;
441
442 } else {
443 //float
444 $info->meta_type = 'N';
445 $info->primary_key = false;
446 $info->unsigned = null;
447 $info->auto_increment= false;
448 $info->unique = null;
449 $info->scale = $rawcolumn->SCALE;
450 }
451 $info->not_null = ($rawcolumn->NULLS === 'NOT NULL');
452 $info->has_default = !is_null($rawcolumn->DEFAULTVAL);
453 if ($info->has_default) {
454 $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space
455 } else {
456 $info->default_value = null;
457 }
458
459 } else if ($rawcolumn->COLTYPE === 'FLOAT') {
460 $info->type = $rawcolumn->COLTYPE;
461 $info->max_length = (int)($rawcolumn->PRECISION * 3.32193);
462 $info->primary_key = false;
463 $info->meta_type = 'N';
464 $info->unique = null;
465 $info->auto_increment= false;
466 $info->not_null = ($rawcolumn->NULLS === 'NOT NULL');
467 $info->has_default = !is_null($rawcolumn->DEFAULTVAL);
468 if ($info->has_default) {
469 $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space
470 } else {
471 $info->default_value = null;
472 }
473
474 } else if ($rawcolumn->COLTYPE === 'CLOB'
475 or $rawcolumn->COLTYPE === 'NCLOB') {
476 $info->type = $rawcolumn->COLTYPE;
477 $info->meta_type = 'X';
478 $info->max_length = $rawcolumn->WIDTH;
479 $info->scale = null;
480 $info->scale = null;
481 $info->not_null = ($rawcolumn->NULLS === 'NOT NULL');
482 $info->has_default = !is_null($rawcolumn->DEFAULTVAL);
483 if ($info->has_default) {
484 // this is hacky :-(
485 if ($rawcolumn->DEFAULTVAL === 'NULL') {
486 $info->default_value = null;
487 } else if ($rawcolumn->DEFAULTVAL === "' ' ") {
488 $info->default_value = "";
489 } else {
490 $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space
491 $info->default_value = substr($info->default_value, 1, strlen($info->default_value)-2); //trim ''
492 }
493 } else {
494 $info->default_value = null;
495 }
496 $info->primary_key = false;
497 $info->binary = false;
498 $info->unsigned = null;
499 $info->auto_increment= false;
500 $info->unique = null;
501
502 } else if ($rawcolumn->COLTYPE === 'BLOB') {
503 $info->type = $rawcolumn->COLTYPE;
504 $info->meta_type = 'B';
505 $info->max_length = $rawcolumn->WIDTH;
506 $info->scale = null;
507 $info->scale = null;
508 $info->not_null = ($rawcolumn->NULLS === 'NOT NULL');
509 $info->has_default = !is_null($rawcolumn->DEFAULTVAL);
510 if ($info->has_default) {
511 // this is hacky :-(
512 if ($rawcolumn->DEFAULTVAL === 'NULL') {
513 $info->default_value = null;
514 } else if ($rawcolumn->DEFAULTVAL === "' ' ") {
515 $info->default_value = "";
516 } else {
517 $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space
518 $info->default_value = substr($info->default_value, 1, strlen($info->default_value)-2); //trim ''
519 }
520 } else {
521 $info->default_value = null;
522 }
523 $info->primary_key = false;
524 $info->binary = true;
525 $info->unsigned = null;
526 $info->auto_increment= false;
527 $info->unique = null;
528
529 } else {
530 // unknown type - sorry
531 $info->type = $rawcolumn->COLTYPE;
532 $info->meta_type = '?';
533 }
534
535 $this->columns[$table][$info->name] = new database_column_info($info);
536 }
537
538 return $this->columns[$table];
539 }
540
8089f8f6 541 /**
542 * This function will handle all the records before being inserted/updated to DB for Oracle
543 * installations. This is because the "special feature" of Oracle where the empty string is
544 * equal to NULL and this presents a problem with all our currently NOT NULL default '' fields.
545 *
546 * Once Moodle DB will be free of this sort of false NOT NULLS, this hack could be removed safely
547 *
548 * Note that this function is 100% private and should be used, exclusively by DML functions
549 * in this file. Also, this is considered a DIRTY HACK to be removed when possible. (stronk7)
550 *
551 * This function is private and must not be used outside this driver at all
552 *
553 * @param $table string the table where the record is going to be inserted/updated (without prefix)
554 * @param $field string the field where the record is going to be inserted/updated
555 * @param $value mixed the value to be inserted/updated
556 */
557 private function oracle_dirty_hack ($table, $field, $value) {
558
559 /// Get metadata
560 $columns = $this->get_columns($table);
561 if (!isset($columns[$field])) {
562 return $value;
563 }
564 $column = $columns[$field];
565
566 /// For Oracle DB, empty strings are converted to NULLs in DB
567 /// and this breaks a lot of NOT NULL columns currenty Moodle. In the future it's
568 /// planned to move some of them to NULL, if they must accept empty values and this
569 /// piece of code will become less and less used. But, for now, we need it.
570 /// What we are going to do is to examine all the data being inserted and if it's
571 /// an empty string (NULL for Oracle) and the field is defined as NOT NULL, we'll modify
572 /// such data in the best form possible ("0" for booleans and numbers and " " for the
573 /// rest of strings. It isn't optimal, but the only way to do so.
574 /// In the oppsite, when retrieving records from Oracle, we'll decode " " back to
575 /// empty strings to allow everything to work properly. DIRTY HACK.
576
577 /// If the field ins't VARCHAR or CLOB, skip
578 if ($column->meta_type != 'C' and $column->meta_type != 'X') {
579 return $value;
580 }
581
582 /// If the field isn't NOT NULL, skip (it's nullable, so accept empty-null values)
583 if (!$column->not_null) {
584 return $value;
585 }
586
587 /// If the value isn't empty, skip
588 if (!empty($value)) {
589 return $value;
590 }
591
592 /// Now, we have one empty value, going to be inserted to one NOT NULL, VARCHAR2 or CLOB field
593 /// Try to get the best value to be inserted
594
595 /// The '0' string doesn't need any transformation, skip
596 if ($value === '0') {
597 return $value;
598 }
599
600 /// Transformations start
601 if (gettype($value) == 'boolean') {
602 return '0'; /// Transform false to '0' that evaluates the same for PHP
603
604 } else if (gettype($value) == 'integer') {
605 return '0'; /// Transform 0 to '0' that evaluates the same for PHP
606
607 } else if (gettype($value) == 'NULL') {
608 return '0'; /// Transform NULL to '0' that evaluates the same for PHP
609
610 } else if ($value === '') {
611 return ' '; /// Transform '' to ' ' that DONT'T EVALUATE THE SAME
612 /// (we'll transform back again on get_records_XXX functions and others)!!
613 }
614
615 /// Fail safe to original value
616 return $value;
617 }
618
66e75f8d 619 /**
620 * Is db in unicode mode?
621 * @return bool
622 */
623 public function setup_is_unicodedb() {
624 $sql = "SELECT VALUE
625 FROM NLS_DATABASE_PARAMETERS
626 WHERE PARAMETER = 'NLS_CHARACTERSET'";
627 $this->query_start($sql, null, SQL_QUERY_AUX);
628 $stmt = $this->parse_query($sql);
8089f8f6 629 $result = oci_execute($stmt, $this->commit_status);
66e75f8d 630 $this->query_end($result, $stmt);
631 $records = null;
632 oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_COLUMN);
633 oci_free_statement($stmt);
634
635 return (isset($records['VALUE'][0]) and $records['VALUE'][0] === 'AL32UTF8');
636 }
637
638 /**
639 * Do NOT use in code, to be used by database_manager only!
640 * @param string $sql query
641 * @return bool true
642 * @throws dml_exception if error
643 */
644 public function change_database_structure($sql) {
117679db 645 $this->reset_caches();
66e75f8d 646
647 $this->query_start($sql, null, SQL_QUERY_STRUCTURE);
648 $stmt = $this->parse_query($sql);
8089f8f6 649 $result = oci_execute($stmt, $this->commit_status);
66e75f8d 650 $this->query_end($result, $stmt);
651 oci_free_statement($stmt);
652
653 return true;
654 }
655
d3f9913a 656 protected function bind_params($stmt, array $params=null, $tablename=null) {
66e75f8d 657 $descriptors = array();
658 if ($params) {
659 $columns = array();
660 if ($tablename) {
661 $columns = $this->get_columns($tablename);
662 }
663 foreach($params as $key=>$value) {
664 if (isset($columns[$key])) {
665 $type = $columns[$key]->meta_type;
666 $maxlength = $columns[$key]->max_length;
667 } else {
668 $type = '?';
669 $maxlength = -1;
670 }
671 switch ($type) {
672 case 'I':
673 case 'R':
674 case 'N':
675 $params[$key] = (int)$value;
676 oci_bind_by_name($stmt, ":$key", $params[$key]);
677 break;
678 case 'F':
679 $params[$key] = (float)$value;
680 oci_bind_by_name($stmt, ":$key", $params[$key]);
681 break;
682
683 case 'B':
684 //TODO
685/* $lob = oci_new_descriptor($this->oci, OCI_D_LOB);
686 $lob->write($params[$key]);
687 oci_bind_by_name($stmt, ":$key", $lob, -1, SQLT_BLOB);
688 $descriptors[] = $lob;
689 break;*/
690
691 case 'X':
692 default:
693 if ($params[$key] === '') {
694 $params[$key] = ' ';
695 }
696 oci_bind_by_name($stmt, ":$key", $params[$key]);
697 }
698 }
699 }
700 return $descriptors;
701 }
702
703 protected function free_descriptors($descriptors) {
704 foreach ($descriptors as $descriptor) {
705 oci_free_descriptor($descriptor);
706 }
707 }
708
709 /**
710 * This function is used to convert all the Oracle 1-space defaults to the empty string
711 * like a really DIRTY HACK to allow it to work better until all those NOT NULL DEFAULT ''
712 * fields will be out from Moodle.
713 * @param string the string to be converted to '' (empty string) if it's ' ' (one space)
714 * @param mixed the key of the array in case we are using this function from array_walk,
715 * defaults to null for other (direct) uses
716 * @return boolean always true (the converted variable is returned by reference)
717 */
718 public static function onespace2empty(&$item, $key=null) {
719 $item = ($item === ' ') ? '' : $item;
720 return true;
721 }
722
723 /**
724 * Execute general sql query. Should be used only when no other method suitable.
725 * Do NOT use this to make changes in db structure, use database_manager::execute_sql() instead!
726 * @param string $sql query
727 * @param array $params query parameters
728 * @return bool true
729 * @throws dml_exception if error
730 */
731 public function execute($sql, array $params=null) {
732 list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
733
734 if (strpos($sql, ';') !== false) {
735 throw new coding_exception('moodle_database::execute() Multiple sql statements found or bound parameters not used properly in query!');
736 }
737
738 $this->query_start($sql, $params, SQL_QUERY_UPDATE);
739 $stmt = $this->parse_query($sql);
740 $this->bind_params($stmt, $params);
8089f8f6 741 $result = oci_execute($stmt, $this->commit_status);
66e75f8d 742 $this->query_end($result, $stmt);
743 oci_free_statement($stmt);
744
745 return true;
746 }
747
748 /**
749 * Get a single database record as an object using a SQL statement.
750 *
af12ea93 751 * The SQL statement should normally only return one record.
752 * It is recommended to use get_records_sql() if more matches possible!
66e75f8d 753 *
754 * @param string $sql The SQL string you wish to be executed, should normally only return one record.
755 * @param array $params array of sql parameters
e6c6531c 756 * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
757 * IGNORE_MULTIPLE means return first, ignore multiple records found(not recommended);
758 * MUST_EXIST means throw exception if no record or multiple records found
af12ea93 759 * @return mixed a fieldset object containing the first matching record, false or exception if error not found depending on mode
66e75f8d 760 * @throws dml_exception if error
761 */
2bad34a3 762 public function get_record_sql($sql, array $params=null, $strictness=IGNORE_MISSING) {
763 $strictness = (int)$strictness;
764 if ($strictness == IGNORE_MULTIPLE) {
af12ea93 765 // do not limit here - ORA does not like that
766 if (!$rs = $this->get_recordset_sql($sql, $params)) {
767 return false;
768 }
769 foreach ($rs as $result) {
770 $rs->close();
771 return $result;
772 }
773 $rs->close();
66e75f8d 774 return false;
775 }
2bad34a3 776 return parent::get_record_sql($sql, $params, $strictness);
66e75f8d 777 }
778
779 /**
780 * Get a number of records as a moodle_recordset using a SQL statement.
781 *
782 * Since this method is a little less readable, use of it should be restricted to
783 * code where it's possible there might be large datasets being returned. For known
784 * small datasets use get_records_sql - it leads to simpler code.
785 *
786 * The return type is as for @see function get_recordset.
787 *
788 * @param string $sql the SQL select query to execute.
789 * @param array $params array of sql parameters
790 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
791 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
792 * @return mixed an moodle_recordset object
793 * @throws dml_exception if error
794 */
795 public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
796 $limitfrom = (int)$limitfrom;
797 $limitnum = (int)$limitnum;
798 $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom;
799 $limitnum = ($limitnum < 0) ? 0 : $limitnum;
800
801 list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
802
803 if ($limitfrom and $limitnum) {
8089f8f6 804 $rawsql = "SELECT oracle_o.*
805 FROM (SELECT oracle_i.*, rownum AS oracle_rownum
806 FROM ($sql) oracle_i
807 ) oracle_o
808 WHERE rownum <= :oracle_max AND oracle_rownum > :oracle_min";
66e75f8d 809 $params['oracle_max'] = $limitfrom + $limitnum;
810 $params['oracle_min'] = $limitfrom;
811
812 } else if ($limitfrom and !$limitnum) {
8089f8f6 813 $rawsql = "SELECT oracle_o.*
814 FROM (SELECT oracle_i.*, rownum AS oracle_rownum
815 FROM ($sql) oracle_i
816 ) oracle_o
817 WHERE oracle_rownum > :oracle_min";
66e75f8d 818 $params['oracle_min'] = $limitfrom;
819
820 } else if (!$limitfrom and $limitnum) {
8089f8f6 821 $rawsql = "SELECT *
822 FROM ($sql)
823 WHERE rownum <= :oracle_max";
66e75f8d 824 $params['oracle_max'] = $limitnum;
8089f8f6 825
826 } else { // No limitfrom nor limitnum
827 $rawsql = $sql;
66e75f8d 828 }
829
830 $this->query_start($sql, $params, SQL_QUERY_SELECT);
8089f8f6 831 $stmt = $this->parse_query($rawsql);
66e75f8d 832 $this->bind_params($stmt, $params);
8089f8f6 833 $result = oci_execute($stmt, $this->commit_status);
66e75f8d 834 $this->query_end($result, $stmt);
835
836 return $this->create_recordset($stmt);
837 }
838
839 protected function create_recordset($stmt) {
840 return new oci_native_moodle_recordset($stmt);
841 }
842
843 /**
844 * Get a number of records as an array of objects using a SQL statement.
845 *
846 * Return value as for @see function get_records.
847 *
848 * @param string $sql the SQL select query to execute. The first column of this SELECT statement
849 * must be a unique value (usually the 'id' field), as it will be used as the key of the
850 * returned array.
851 * @param array $params array of sql parameters
852 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
853 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
854 * @return mixed an array of objects, or empty array if no records were found
855 * @throws dml_exception if error
856 */
857 public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
858 $limitfrom = (int)$limitfrom;
859 $limitnum = (int)$limitnum;
860 $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom;
861 $limitnum = ($limitnum < 0) ? 0 : $limitnum;
862
863 list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
864
865 if ($limitfrom and $limitnum) {
866 $sql = "SELECT oracle_o.*
867 FROM (SELECT oracle_i.*, rownum AS oracle_rownum
868 FROM ($sql) oracle_i
869 ) oracle_o
870 WHERE rownum <= :oracle_max AND oracle_rownum > :oracle_min";
871 $params['oracle_max'] = $limitfrom + $limitnum;
872 $params['oracle_min'] = $limitfrom;
873
874 } else if ($limitfrom and !$limitnum) {
875 $sql = "SELECT oracle_o.*
876 FROM (SELECT oracle_i.*, rownum AS oracle_rownum
877 FROM ($sql) oracle_i
878 ) oracle_o
879 WHERE oracle_rownum > :oracle_min";
880 $params['oracle_min'] = $limitfrom;
881
882 } else if (!$limitfrom and $limitnum) {
883 $sql = "SELECT *
884 FROM ($sql)
885 WHERE rownum <= :oracle_max";
886 $params['oracle_max'] = $limitnum;
887 }
888
889 $this->query_start($sql, $params, SQL_QUERY_SELECT);
890 $stmt = $this->parse_query($sql);
891 $this->bind_params($stmt, $params);
8089f8f6 892 $result = oci_execute($stmt, $this->commit_status);
66e75f8d 893 $this->query_end($result, $stmt);
894
895 $records = null;
896 oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW);
897 oci_free_statement($stmt);
898
899 $return = array();
900
901 foreach ($records as $row) {
902 $row = array_change_key_case($row, CASE_LOWER);
903 unset($row['oracle_rownum']);
904 array_walk($row, array('oci_native_moodle_database', 'onespace2empty'));
905 $id = reset($row);
906 if (isset($return[$id])) {
907 $colname = key($row);
908 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);
909 }
910 $return[$id] = (object)$row;
911 }
912
913 return $return;
914 }
915
916 /**
917 * Selects records and return values (first field) as an array using a SQL statement.
918 *
919 * @param string $sql The SQL query
920 * @param array $params array of sql parameters
921 * @return array of values
922 * @throws dml_exception if error
923 */
924 public function get_fieldset_sql($sql, array $params=null) {
925 list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
926
927 $this->query_start($sql, $params, SQL_QUERY_SELECT);
928 $stmt = $this->parse_query($sql);
929 $this->bind_params($stmt, $params);
8089f8f6 930 $result = oci_execute($stmt, $this->commit_status);
66e75f8d 931 $this->query_end($result, $stmt);
932
933 $records = null;
934 oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_COLUMN);
935 oci_free_statement($stmt);
936
937 $return = reset($records);
938 array_walk($return, array('oci_native_moodle_database', 'onespace2empty'));
939
940 return $return;
941 }
942
943 /**
944 * Insert new record into database, as fast as possible, no safety checks, lobs not supported.
945 * @param string $table name
946 * @param mixed $params data record as object or array
947 * @param bool $returnit return it of inserted record
948 * @param bool $bulk true means repeated inserts expected
949 * @param bool $customsequence true if 'id' included in $params, disables $returnid
950 * @return true or new id
951 * @throws dml_exception if error
952 */
953 public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) {
954 if (!is_array($params)) {
955 $params = (array)$params;
956 }
957
958 $returning = "";
959
960 if ($customsequence) {
961 if (!isset($params['id'])) {
962 throw new coding_exception('moodle_database::insert_record_raw() id field must be specified if custom sequences used.');
963 }
964 $returnid = false;
965 } else {
966 if ($returnid) {
f9f8baa8 967 $returning = "RETURNING id INTO :oracle_id";// crazy name nobody is ever going to use or parameter ;-)
66e75f8d 968 }
f9f8baa8 969 unset($params['id']);
66e75f8d 970 }
971
972 if (empty($params)) {
973 throw new coding_exception('moodle_database::insert_record_raw() no fields found.');
974 }
975
976 $fields = implode(',', array_keys($params));
977 $values = array();
978 foreach ($params as $pname=>$value) {
979 $values[] = ":$pname";
980 }
981 $values = implode(',', $values);
982
983 $sql = "INSERT INTO {$this->prefix}$table ($fields) VALUES($values) $returning";
984 $id = null;
985
986 $this->query_start($sql, $params, SQL_QUERY_INSERT);
987 $stmt = $this->parse_query($sql);
988 $descriptors = $this->bind_params($stmt, $params, $table);
989 if ($returning) {
990 oci_bind_by_name($stmt, ":oracle_id", $id, -1, SQLT_LNG);
991 }
8089f8f6 992 $result = oci_execute($stmt, $this->commit_status);
66e75f8d 993 $this->free_descriptors($descriptors);
994 $this->query_end($result, $stmt);
995 oci_free_statement($stmt);
996
997 if (!$returnid) {
998 return true;
999 }
1000
1001 if (!$returning) {
1002 die('TODO - implement oracle 9.2 insert support'); //TODO
1003 }
1004
1005 return (int)$id;
1006 }
1007
1008 /**
1009 * Insert a record into a table and return the "id" field if required.
1010 *
1011 * Some conversions and safety checks are carried out. Lobs are supported.
1012 * If the return ID isn't required, then this just reports success as true/false.
1013 * $data is an object containing needed data
1014 * @param string $table The database table to be inserted into
1015 * @param object $data A data object with values for one or more fields in the record
1016 * @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.
1017 * @return true or new id
1018 * @throws dml_exception if error
1019 */
1020 public function insert_record($table, $dataobject, $returnid=true, $bulk=false) {
1021 if (!is_object($dataobject)) {
1022 $dataobject = (object)$dataobject;
1023 }
1024
66e75f8d 1025 unset($dataobject->id);
8089f8f6 1026
1027 $columns = $this->get_columns($table);
66e75f8d 1028 $cleaned = array();
8089f8f6 1029 $blobs = array();
1030 $clobs = array();
66e75f8d 1031
1032 foreach ($dataobject as $field=>$value) {
1033 if (!isset($columns[$field])) {
1034 continue;
1035 }
1036 $cleaned[$field] = $value;
1037 }
1038
1039 $id = $this->insert_record_raw($table, $cleaned, true, $bulk);
1040
1041 return ($returnid ? $id : true);
1042
1043 }
1044
1045 /**
1046 * Import a record into a table, id field is required.
1047 * Safety checks are NOT carried out. Lobs are supported.
1048 *
1049 * @param string $table name of database table to be inserted into
1050 * @param object $dataobject A data object with values for one or more fields in the record
1051 * @return bool true
1052 * @throws dml_exception if error
1053 */
1054 public function import_record($table, $dataobject) {
1055 $dataobject = (object)$dataobject;
1056
1057 $columns = $this->get_columns($table);
1058 $cleaned = array();
1059
1060 foreach ($dataobject as $field=>$value) {
1061 if (!isset($columns[$field])) {
1062 continue;
1063 }
1064 $cleaned[$field] = $value;
1065 }
1066
1067 return $this->insert_record_raw($table, $cleaned, false, true, true);
1068 }
1069
1070 /**
1071 * Update record in database, as fast as possible, no safety checks, lobs not supported.
1072 * @param string $table name
1073 * @param mixed $params data record as object or array
1074 * @param bool true means repeated updates expected
1075 * @return bool true
1076 * @throws dml_exception if error
1077 */
1078 public function update_record_raw($table, $params, $bulk=false) {
1079 if (!is_array($params)) {
1080 $params = (array)$params;
1081 }
1082 if (!isset($params['id'])) {
1083 throw new coding_exception('moodle_database::update_record_raw() id field must be specified.');
1084 }
1085
1086 if (empty($params)) {
1087 throw new coding_exception('moodle_database::update_record_raw() no fields found.');
1088 }
1089
1090 $sets = array();
1091 foreach ($params as $field=>$value) {
1092 if ($field == 'id') {
1093 continue;
1094 }
1095 $sets[] = "$field = :$field";
1096 }
1097
1098 $sets = implode(',', $sets);
1099 $sql = "UPDATE {$this->prefix}$table SET $sets WHERE id=:id";
1100
1101 $this->query_start($sql, $params, SQL_QUERY_UPDATE);
1102 $stmt = $this->parse_query($sql);
1103 $descriptors = $this->bind_params($stmt, $params, $table);
8089f8f6 1104 $result = oci_execute($stmt, $this->commit_status);
66e75f8d 1105 $this->query_end($result, $stmt);
1106 $this->free_descriptors($descriptors);
1107 oci_free_statement($stmt);
1108
1109 return true;
1110 }
1111
1112 /**
1113 * Update a record in a table
1114 *
1115 * $dataobject is an object containing needed data
1116 * Relies on $dataobject having a variable "id" to
1117 * specify the record to update
1118 *
1119 * @param string $table The database table to be checked against.
1120 * @param object $dataobject An object with contents equal to fieldname=>fieldvalue. Must have an entry for 'id' to map to the table specified.
1121 * @param bool true means repeated updates expected
1122 * @return bool true
1123 * @throws dml_exception if error
1124 */
1125 public function update_record($table, $dataobject, $bulk=false) {
1126 if (!is_object($dataobject)) {
1127 $dataobject = (object)$dataobject;
1128 }
1129
1130 $columns = $this->get_columns($table);
1131 $cleaned = array();
1132
1133 foreach ($dataobject as $field=>$value) {
1134 if (!isset($columns[$field])) {
1135 continue;
1136 }
1137 $cleaned[$field] = $value;
1138 }
1139
1140 $this->update_record_raw($table, $cleaned, $bulk);
1141
1142 return true;
1143 }
1144
1145 /**
1146 * Set a single field in every table record which match a particular WHERE clause.
1147 *
1148 * @param string $table The database table to be checked against.
1149 * @param string $newfield the field to set.
1150 * @param string $newvalue the value to set the field to.
1151 * @param string $select A fragment of SQL to be used in a where clause in the SQL call.
1152 * @param array $params array of sql parameters
1153 * @return bool true
1154 * @throws dml_exception if error
1155 */
1156 public function set_field_select($table, $newfield, $newvalue, $select, array $params=null) {
1157 if ($select) {
1158 $select = "WHERE $select";
1159 }
1160 $params = (array)$params;
1161
1162 list($select, $params, $type) = $this->fix_sql_params($select, $params);
1163
1164 if (is_bool($newvalue)) {
1165 $newvalue = (int)$newvalue; // prevent "false" problems
1166 }
1167 if (is_null($newvalue)) {
1168 $newsql = "$newfield = NULL";
1169 } else {
1170 $params[$newfield] = $newvalue;
1171 $newsql = "$newfield = :$newfield";
1172 }
1173 $sql = "UPDATE {$this->prefix}$table SET $newsql $select";
1174
1175 $this->query_start($sql, $params, SQL_QUERY_UPDATE);
1176 $stmt = $this->parse_query($sql);
1177 $descriptors = $this->bind_params($stmt, $params, $table);
8089f8f6 1178 $result = oci_execute($stmt, $this->commit_status);
66e75f8d 1179 $this->query_end($result, $stmt);
1180 $this->free_descriptors($descriptors);
1181 oci_free_statement($stmt);
1182
1183 return true;
1184 }
1185
1186 /**
1187 * Delete one or more records from a table which match a particular WHERE clause.
1188 *
1189 * @param string $table The database table to be checked against.
1190 * @param string $select A fragment of SQL to be used in a where clause in the SQL call (used to define the selection criteria).
1191 * @param array $params array of sql parameters
1192 * @return bool true
1193 * @throws dml_exception if error
1194 */
1195 public function delete_records_select($table, $select, array $params=null) {
1196 if ($select) {
1197 $select = "WHERE $select";
1198 }
1199 $sql = "DELETE FROM {$this->prefix}$table $select";
1200
1201 list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
1202
1203 $this->query_start($sql, $params, SQL_QUERY_UPDATE);
1204 $stmt = $this->parse_query($sql);
1205 $this->bind_params($stmt, $params);
8089f8f6 1206 $result = oci_execute($stmt, $this->commit_status);
66e75f8d 1207 $this->query_end($result, $stmt);
1208 oci_free_statement($stmt);
1209
1210 return true;
1211 }
1212
1213 function sql_null_from_clause() {
1214 return ' FROM dual';
1215 }
1216
1217 public function sql_bitand($int1, $int2) {
1218 return 'bitand((' . $int1 . '), (' . $int2 . '))';
1219 }
1220
1221 public function sql_bitnot($int1) {
1222 return '((0 - (' . $int1 . ')) - 1)';
1223 }
1224
1225 public function sql_bitor($int1, $int2) {
1226 return '((' . $int1 . ') + (' . $int2 . ') - ' . $this->sql_bitand($int1, $int2) . ')';
1227 }
1228
1229 public function sql_bitxor($int1, $int2) {
2c4eb006 1230 return '(' . $this->sql_bitor($int1, $int2) . ' - ' . $this->sql_bitand($int1, $int2) . ')';
66e75f8d 1231 }
1232
1233 /**
1234 * Returns the SQL text to be used in order to perform module '%'
1235 * opration - remainder after division
1236 *
1237 * @param integer int1 first integer in the operation
1238 * @param integer int2 second integer in the operation
1239 * @return string the piece of SQL code to be used in your statement.
1240 */
1241 public function sql_modulo($int1, $int2) {
1242 return 'MOD(' . $int1 . ', ' . $int2 . ')';
1243 }
1244
1245 public function sql_cast_char2int($fieldname, $text=false) {
1246 if (!$text) {
1247 return ' CAST(' . $fieldname . ' AS INT) ';
1248 } else {
1249 return ' CAST(' . $this->sql_compare_text($fieldname) . ' AS INT) ';
1250 }
1251 }
1252
1253 public function sql_ilike() {
1254 // TODO: add some ilike workaround
1255 return 'LIKE';
1256 }
1257
1258 public function sql_concat() {
1259 $arr = func_get_args();
1260 $s = implode(' || ', $arr);
1261 if ($s === '') {
1262 return " '' ";
1263 }
1264 return " $s ";
1265 }
1266
1267 public function sql_concat_join($separator="' '", $elements=array()) {
1268 for ($n=count($elements)-1; $n > 0 ; $n--) {
1269 array_splice($elements, $n, 0, $separator);
1270 }
1271 $s = implode(' || ', $elements);
1272 if ($s === '') {
1273 return " '' ";
1274 }
1275 return " $s ";
1276 }
1277
1278 /**
1279 * Returns the SQL for returning searching one string for the location of another.
1280 */
1281 public function sql_position($needle, $haystack) {
1282 return "INSTR(($haystack), ($needle))";
1283 }
1284
1285 public function sql_isempty($tablename, $fieldname, $nullablefield, $textfield) {
1286 if ($nullablefield) {
1287 return " $fieldname IS NULL "; /// empties in nullable fields are stored as
1288 } else { /// NULLs
1289 if ($textfield) {
1290 return " ".$this->sql_compare_text($fieldname)." = ' ' "; /// oracle_dirty_hack inserts 1-whitespace
1291 } else { /// in NOT NULL varchar and text columns so
1292 return " $fieldname = ' ' "; /// we need to look for that in any situation
1293 }
1294 }
1295 }
1296
8089f8f6 1297 public function sql_empty() {
66e75f8d 1298 return ' ';
1299 }
1300
d7d86462 1301/// session locking
fc12e1f7 1302 // http://download.oracle.com/docs/cd/B10501_01/appdev.920/a96612/d_lock2.htm#999576
1303
66e75f8d 1304/// transactions
1305 /**
1306 * on DBs that support it, switch to transaction mode and begin a transaction
1307 * you'll need to ensure you call commit_sql() or your changes *will* be lost.
1308 *
1309 * this is _very_ useful for massive updates
1310 */
1311 public function begin_sql() {
a1dda107 1312 if (!parent::begin_sql()) {
1313 return false;
1314 }
1315 return true;
66e75f8d 1316
1317 $sql = "BEGIN";
1318 $this->query_start($sql, NULL, SQL_QUERY_AUX);
1319 $stmt = $this->parse_query($sql);
8089f8f6 1320 $result = oci_execute($stmt, $this->commit_status);
66e75f8d 1321 $this->query_end($result, $stmt);
1322 oci_free_statement($stmt);
1323 return true;
1324 }
1325
1326 /**
1327 * on DBs that support it, commit the transaction
1328 */
1329 public function commit_sql() {
a1dda107 1330 if (!parent::commit_sql()) {
1331 return false;
1332 }
1333 return true;
66e75f8d 1334
1335 $sql = "COMMIT";
1336 $this->query_start($sql, NULL, SQL_QUERY_AUX);
1337 $stmt = $this->parse_query($sql);
8089f8f6 1338 $result = oci_execute($stmt, $this->commit_status);
66e75f8d 1339 $this->query_end($result, $stmt);
1340 oci_free_statement($stmt);
1341 return true;
1342 }
1343
1344 /**
1345 * on DBs that support it, rollback the transaction
1346 */
1347 public function rollback_sql() {
a1dda107 1348 if (!parent::rollback_sql()) {
1349 return false;
1350 }
1351 return true;
66e75f8d 1352
1353 $sql = "ROLLBACK";
1354 $this->query_start($sql, NULL, SQL_QUERY_AUX);
1355 $stmt = $this->parse_query($sql);
8089f8f6 1356 $result = oci_execute($stmt, $this->commit_status);
66e75f8d 1357 $this->query_end($result, $stmt);
1358 oci_free_statement($stmt);
1359 return true;
1360 }
1361}