d525a4b37a2aa35581dfbc0c7265dee5827f1b82
[moodle.git] / lib / dml / moodle_database.php
1 <?php
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/>.
19 /**
20  * Abstract database driver class.
21  *
22  * @package    core
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  */
28 defined('MOODLE_INTERNAL') || die();
30 require_once($CFG->libdir.'/dml/database_column_info.php');
31 require_once($CFG->libdir.'/dml/moodle_recordset.php');
32 require_once($CFG->libdir.'/dml/moodle_transaction.php');
34 /// GLOBAL CONSTANTS /////////////////////////////////////////////////////////
36 /** Bitmask, indicates :name type parameters are supported by db backend. */
37 define('SQL_PARAMS_NAMED', 1);
39 /** Bitmask, indicates ? type parameters are supported by db backend. */
40 define('SQL_PARAMS_QM', 2);
42 /** Bitmask, indicates $1, $2, ... type parameters are supported by db backend. */
43 define('SQL_PARAMS_DOLLAR', 4);
46 /** Normal select query, reading only */
47 define('SQL_QUERY_SELECT', 1);
49 /** Insert select query, writing */
50 define('SQL_QUERY_INSERT', 2);
52 /** Update select query, writing */
53 define('SQL_QUERY_UPDATE', 3);
55 /** Query changing db structure, writing */
56 define('SQL_QUERY_STRUCTURE', 4);
58 /** Auxiliary query done by driver, setting connection config, getting table info, etc. */
59 define('SQL_QUERY_AUX', 5);
61 /**
62  * Abstract class representing moodle database interface.
63  */
64 abstract class moodle_database {
66     /** @var database_manager db manager which allows db structure modifications */
67     protected $database_manager;
68     /** @var moodle_temptables temptables manager to provide cross-db support for temp tables */
69     protected $temptables;
70     /** @var array cache of column info */
71     protected $columns = array(); // I wish we had a shared memory cache for this :-(
72     /** @var array cache of table info */
73     protected $tables  = null;
75     // db connection options
76     /** @var string db host name */
77     protected $dbhost;
78     /** @var string db host user */
79     protected $dbuser;
80     /** @var string db host password */
81     protected $dbpass;
82     /** @var string db name */
83     protected $dbname;
84     /** @var string table prefix */
85     protected $prefix;
87     /** @var array Database or driver specific options, such as sockets or TCPIP db connections */
88     protected $dboptions;
90     /** @var bool true means non-moodle external database used.*/
91     protected $external;
93     /** @var int The database reads (performance counter).*/
94     protected $reads = 0;
95     /** @var int The database writes (performance counter).*/
96     protected $writes = 0;
98     /** @var int Debug level */
99     protected $debug  = 0;
101     /** @var string last query sql */
102     protected $last_sql;
103     /** @var array last query parameters */
104     protected $last_params;
105     /** @var int last query type */
106     protected $last_type;
107     /** @var string last extra info */
108     protected $last_extrainfo;
109     /** @var float last time in seconds with millisecond precision */
110     protected $last_time;
111     /** @var bool flag indicating logging of query in progress, prevents infinite loops */
112     private $loggingquery = false;
114     /** @var bool true if db used for db sessions */
115     protected $used_for_db_sessions = false;
117     /** @var array open transactions */
118     private $transactions = array();
119     /** @var bool force rollback of all current transactions */
120     private $force_rollback = false;
122     /** @var int internal temporary variable */
123     private $fix_sql_params_i;
124     /** @var int internal temporary variable used by {@link get_in_or_equal()}. */
125     private $inorequaluniqueindex = 1; // guarantees unique parameters in each request
127     /**
128      * Constructor - instantiates the database, specifying if it's external (connect to other systems) or no (Moodle DB)
129      *              note this has effect to decide if prefix checks must be performed or no
130      * @param bool true means external database used
131      */
132     public function __construct($external=false) {
133         $this->external  = $external;
134     }
136     /**
137      * Destructor - cleans up and flushes everything needed.
138      */
139     public function __destruct() {
140         $this->dispose();
141     }
143     /**
144      * Detects if all needed PHP stuff installed.
145      * Note: can be used before connect()
146      * @return mixed true if ok, string if something
147      */
148     public abstract function driver_installed();
150     /**
151      * Returns database table prefix
152      * Note: can be used before connect()
153      * @return string database table prefix
154      */
155     public function get_prefix() {
156         return $this->prefix;
157     }
159     /**
160      * Loads and returns a database instance with the specified type and library.
161      * @param string $type database type of the driver (mysqli, pgsql, mssql, sqldrv, oci, etc.)
162      * @param string $library database library of the driver (native, pdo, etc.)
163      * @return moodle_database driver object or null if error
164      */
165     public static function get_driver_instance($type, $library) {
166         global $CFG;
168         $classname = $type.'_'.$library.'_moodle_database';
169         $libfile   = "$CFG->libdir/dml/$classname.php";
171         if (!file_exists($libfile)) {
172             return null;
173         }
175         require_once($libfile);
176         return new $classname();
177     }
179     /**
180      * Returns database family type - describes SQL dialect
181      * Note: can be used before connect()
182      * @return string db family name (mysql, postgres, mssql, oracle, etc.)
183      */
184     public abstract function get_dbfamily();
186     /**
187      * Returns more specific database driver type
188      * Note: can be used before connect()
189      * @return string db type mysqli, pgsql, oci, mssql, sqlsrv
190      */
191     protected abstract function get_dbtype();
193     /**
194      * Returns general database library name
195      * Note: can be used before connect()
196      * @return string db type pdo, native
197      */
198     protected abstract function get_dblibrary();
200     /**
201      * Returns localised database type name
202      * Note: can be used before connect()
203      * @return string
204      */
205     public abstract function get_name();
207     /**
208      * Returns localised database configuration help.
209      * Note: can be used before connect()
210      * @return string
211      */
212     public abstract function get_configuration_help();
214     /**
215      * Returns localised database description
216      * Note: can be used before connect()
217      * @return string
218      */
219     public abstract function get_configuration_hints();
221     /**
222      * Returns db related part of config.php
223      * @return object
224      */
225     public function export_dbconfig() {
226         $cfg = new stdClass();
227         $cfg->dbtype    = $this->get_dbtype();
228         $cfg->dblibrary = $this->get_dblibrary();
229         $cfg->dbhost    = $this->dbhost;
230         $cfg->dbname    = $this->dbname;
231         $cfg->dbuser    = $this->dbuser;
232         $cfg->dbpass    = $this->dbpass;
233         $cfg->prefix    = $this->prefix;
234         if ($this->dboptions) {
235             $cfg->dboptions = $this->dboptions;
236         }
238         return $cfg;
239     }
241     /**
242      * Diagnose database and tables, this function is used
243      * to verify database and driver settings, db engine types, etc.
244      *
245      * @return string null means everything ok, string means problem found.
246      */
247     public function diagnose() {
248         return null;
249     }
251     /**
252      * Connect to db
253      * Must be called before other methods.
254      * @param string $dbhost
255      * @param string $dbuser
256      * @param string $dbpass
257      * @param string $dbname
258      * @param mixed $prefix string means moodle db prefix, false used for external databases where prefix not used
259      * @param array $dboptions driver specific options
260      * @return bool true
261      * @throws dml_connection_exception if error
262      */
263     public abstract function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null);
265     /**
266      * Store various database settings
267      * @param string $dbhost
268      * @param string $dbuser
269      * @param string $dbpass
270      * @param string $dbname
271      * @param mixed $prefix string means moodle db prefix, false used for external databases where prefix not used
272      * @param array $dboptions driver specific options
273      * @return void
274      */
275     protected function store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) {
276         $this->dbhost    = $dbhost;
277         $this->dbuser    = $dbuser;
278         $this->dbpass    = $dbpass;
279         $this->dbname    = $dbname;
280         $this->prefix    = $prefix;
281         $this->dboptions = (array)$dboptions;
282     }
284     /**
285      * Attempt to create the database
286      * @param string $dbhost
287      * @param string $dbuser
288      * @param string $dbpass
289      * @param string $dbname
290      *
291      * @return bool success
292      */
293     public function create_database($dbhost, $dbuser, $dbpass, $dbname, array $dboptions=null) {
294         return false;
295     }
297     /**
298      * Close database connection and release all resources
299      * and memory (especially circular memory references).
300      * Do NOT use connect() again, create a new instance if needed.
301      * @return void
302      */
303     public function dispose() {
304         if ($this->transactions) {
305             // this should not happen, it usually indicates wrong catching of exceptions,
306             // because all transactions should be finished manually or in default exception handler.
307             // unfortunately we can not access global $CFG any more and can not print debug,
308             // the diagnostic info should be printed in footer instead
309             $lowesttransaction = end($this->transactions);
310             $backtrace = $lowesttransaction->get_backtrace();
312             error_log('Potential coding error - active database transaction detected when disposing database:'."\n".format_backtrace($backtrace, true));
313             $this->force_transaction_rollback();
314         }
315         if ($this->used_for_db_sessions) {
316             // this is needed because we need to save session to db before closing it
317             session_get_instance()->write_close();
318             $this->used_for_db_sessions = false;
319         }
320         if ($this->temptables) {
321             $this->temptables->dispose();
322             $this->temptables = null;
323         }
324         if ($this->database_manager) {
325             $this->database_manager->dispose();
326             $this->database_manager = null;
327         }
328         $this->columns = array();
329         $this->tables  = null;
330     }
332     /**
333      * Called before each db query.
334      * @param string $sql
335      * @param array array of parameters
336      * @param int $type type of query
337      * @param mixed $extrainfo driver specific extra information
338      * @return void
339      */
340     protected function query_start($sql, array $params=null, $type, $extrainfo=null) {
341         if ($this->loggingquery) {
342             return;
343         }
344         $this->last_sql       = $sql;
345         $this->last_params    = $params;
346         $this->last_type      = $type;
347         $this->last_extrainfo = $extrainfo;
348         $this->last_time      = microtime(true);
350         switch ($type) {
351             case SQL_QUERY_SELECT:
352             case SQL_QUERY_AUX:
353                 $this->reads++;
354                 break;
355             case SQL_QUERY_INSERT:
356             case SQL_QUERY_UPDATE:
357             case SQL_QUERY_STRUCTURE:
358                 $this->writes++;
359         }
361         $this->print_debug($sql, $params);
362     }
364     /**
365      * Called immediately after each db query.
366      * @param mixed db specific result
367      * @return void
368      */
369     protected function query_end($result) {
370         if ($this->loggingquery) {
371             return;
372         }
373         if ($result !== false) {
374             $this->query_log();
375             // free memory
376             $this->last_sql    = null;
377             $this->last_params = null;
378             return;
379         }
381         // remember current info, log queries may alter it
382         $type   = $this->last_type;
383         $sql    = $this->last_sql;
384         $params = $this->last_params;
385         $time   = microtime(true) - $this->last_time;
386         $error  = $this->get_last_error();
388         $this->query_log($error);
390         switch ($type) {
391             case SQL_QUERY_SELECT:
392             case SQL_QUERY_AUX:
393                 throw new dml_read_exception($error, $sql, $params);
394             case SQL_QUERY_INSERT:
395             case SQL_QUERY_UPDATE:
396                 throw new dml_write_exception($error, $sql, $params);
397             case SQL_QUERY_STRUCTURE:
398                 $this->get_manager(); // includes ddl exceptions classes ;-)
399                 throw new ddl_change_structure_exception($error, $sql);
400         }
401     }
403     /**
404      * Log last database query if requested
405      * @param mixed string error or false if not error
406      * @return void
407      */
408     public function query_log($error=false) {
409         $logall    = !empty($this->dboptions['logall']);
410         $logslow   = !empty($this->dboptions['logslow']) ? $this->dboptions['logslow'] : false;
411         $logerrors = !empty($this->dboptions['logerrors']);
412         $iserror   = ($error !== false);
414         $time = microtime(true) - $this->last_time;
416         if ($logall or ($logslow and ($logslow < ($time+0.00001))) or ($iserror and $logerrors)) {
417             $this->loggingquery = true;
418             try {
419                 $backtrace = debug_backtrace();
420                 if ($backtrace) {
421                     //remove query_log()
422                     array_shift($backtrace);
423                 }
424                 if ($backtrace) {
425                     //remove query_end()
426                     array_shift($backtrace);
427                 }
428                 $log = new stdClass();
429                 $log->qtype      = $this->last_type;
430                 $log->sqltext    = $this->last_sql;
431                 $log->sqlparams  = var_export((array)$this->last_params, true);
432                 $log->error      = (int)$iserror;
433                 $log->info       = $iserror ? $error : null;
434                 $log->backtrace  = format_backtrace($backtrace, true);
435                 $log->exectime   = $time;
436                 $log->timelogged = time();
437                 $this->insert_record('log_queries', $log);
438             } catch (Exception $ignored) {
439             }
440             $this->loggingquery = false;
441         }
442     }
444     /**
445      * Returns database server info array
446      * @return array
447      */
448     public abstract function get_server_info();
450     /**
451      * Returns supported query parameter types
452      * @return int bitmask
453      */
454     protected abstract function allowed_param_types();
456     /**
457      * Returns last error reported by database engine.
458      * @return string error message
459      */
460     public abstract function get_last_error();
462     /**
463      * Print sql debug info
464      * @param string $sql query which caused problems
465      * @param array $params optional query parameters
466      * @param mixed $obj optional library specific object
467      * @return void
468      */
469     protected function print_debug($sql, array $params=null, $obj=null) {
470         if (!$this->get_debug()) {
471             return;
472         }
473         if (CLI_SCRIPT) {
474             echo "--------------------------------\n";
475             echo $sql."\n";
476             if (!is_null($params)) {
477                 echo "[".var_export($params, true)."]\n";
478             }
479             echo "--------------------------------\n";
480         } else {
481             echo "<hr />\n";
482             echo s($sql)."\n";
483             if (!is_null($params)) {
484                 echo "[".s(var_export($params, true))."]\n";
485             }
486             echo "<hr />\n";
487         }
488     }
490     /**
491      * Returns SQL WHERE conditions.
492      * @param string $table - the table name that these conditions will be validated against.
493      * @param array conditions - must not contain numeric indexes
494      * @return array sql part and params
495      */
496     protected function where_clause($table, array $conditions=null) {
497         $allowed_types = $this->allowed_param_types();
498         if (empty($conditions)) {
499             return array('', array());
500         }
501         $where = array();
502         $params = array();
504         $columns = $this->get_columns($table);
505         foreach ($conditions as $key=>$value) {
506             if (!isset($columns[$key])) {
507                 $a = new stdClass();
508                 $a->fieldname = $key;
509                 $a->tablename = $table;
510                 throw new dml_exception('ddlfieldnotexist', $a);
511             }
512             $column = $columns[$key];
513             if ($column->meta_type == 'X') {
514                 //ok so the column is a text column. sorry no text columns in the where clause conditions
515                 throw new dml_exception('textconditionsnotallowed', $conditions);
516             }
517             if (is_int($key)) {
518                 throw new dml_exception('invalidnumkey');
519             }
520             if (is_null($value)) {
521                 $where[] = "$key IS NULL";
522             } else {
523                 if ($allowed_types & SQL_PARAMS_NAMED) {
524                     // Need to verify key names because they can contain, originally,
525                     // spaces and other forbidden chars when using sql_xxx() functions and friends.
526                     $normkey = trim(preg_replace('/[^a-zA-Z0-9_-]/', '_', $key), '-_');
527                     if ($normkey !== $key) {
528                         debugging('Invalid key found in the conditions array.');
529                     }
530                     $where[] = "$key = :$normkey";
531                     $params[$normkey] = $value;
532                 } else {
533                     $where[] = "$key = ?";
534                     $params[] = $value;
535                 }
536             }
537         }
538         $where = implode(" AND ", $where);
539         return array($where, $params);
540     }
542     /**
543      * Returns SQL WHERE conditions for the ..._list methods.
544      *
545      * @param string $field the name of a field.
546      * @param array $values the values field might take.
547      * @return array sql part and params
548      */
549     protected function where_clause_list($field, array $values) {
550         $params = array();
551         $select = array();
552         $values = (array)$values;
553         foreach ($values as $value) {
554             if (is_bool($value)) {
555                 $value = (int)$value;
556             }
557             if (is_null($value)) {
558                 $select[] = "$field IS NULL";
559             } else {
560                 $select[] = "$field = ?";
561                 $params[] = $value;
562             }
563         }
564         $select = implode(" OR ", $select);
565         return array($select, $params);
566     }
568     /**
569      * Constructs IN() or = sql fragment
570      * @param mixed $items single or array of values
571      * @param int $type bound param type SQL_PARAMS_QM or SQL_PARAMS_NAMED
572      * @param string $prefix named parameter placeholder prefix (unique counter value is appended to each parameter name)
573      * @param bool $equal true means equal, false not equal
574      * @param mixed $onemptyitems defines the behavior when the array of items is empty. Defaults to false,
575      *              meaning throw exceptions. Other values will become part of the returned SQL fragment.
576      * @return array - $sql and $params
577      */
578     public function get_in_or_equal($items, $type=SQL_PARAMS_QM, $prefix='param', $equal=true, $onemptyitems=false) {
580         // default behavior, throw exception on empty array
581         if (is_array($items) and empty($items) and $onemptyitems === false) {
582             throw new coding_exception('moodle_database::get_in_or_equal() does not accept empty arrays');
583         }
584         // handle $onemptyitems on empty array of items
585         if (is_array($items) and empty($items)) {
586             if (is_null($onemptyitems)) {             // Special case, NULL value
587                 $sql = $equal ? ' IS NULL' : ' IS NOT NULL';
588                 return (array($sql, array()));
589             } else {
590                 $items = array($onemptyitems);        // Rest of cases, prepare $items for std processing
591             }
592         }
594         if ($type == SQL_PARAMS_QM) {
595             if (!is_array($items) or count($items) == 1) {
596                 $sql = $equal ? '= ?' : '<> ?';
597                 $items = (array)$items;
598                 $params = array_values($items);
599             } else {
600                 if ($equal) {
601                     $sql = 'IN ('.implode(',', array_fill(0, count($items), '?')).')';
602                 } else {
603                     $sql = 'NOT IN ('.implode(',', array_fill(0, count($items), '?')).')';
604                 }
605                 $params = array_values($items);
606             }
608         } else if ($type == SQL_PARAMS_NAMED) {
609             if (empty($prefix)) {
610                 $prefix = 'param';
611             }
613             if (!is_array($items)){
614                 $param = $prefix.$this->inorequaluniqueindex++;
615                 $sql = $equal ? "= :$param" : "<> :$param";
616                 $params = array($param=>$items);
617             } else if (count($items) == 1) {
618                 $param = $prefix.$this->inorequaluniqueindex++;
619                 $sql = $equal ? "= :$param" : "<> :$param";
620                 $item = reset($items);
621                 $params = array($param=>$item);
622             } else {
623                 $params = array();
624                 $sql = array();
625                 foreach ($items as $item) {
626                     $param = $prefix.$this->inorequaluniqueindex++;
627                     $params[$param] = $item;
628                     $sql[] = ':'.$param;
629                 }
630                 if ($equal) {
631                     $sql = 'IN ('.implode(',', $sql).')';
632                 } else {
633                     $sql = 'NOT IN ('.implode(',', $sql).')';
634                 }
635             }
637         } else {
638             throw new dml_exception('typenotimplement');
639         }
640         return array($sql, $params);
641     }
643     /**
644      * Converts short table name {tablename} to real table name
645      * @param string sql
646      * @return string sql
647      */
648     protected function fix_table_names($sql) {
649         return preg_replace('/\{([a-z][a-z0-9_]*)\}/', $this->prefix.'$1', $sql);
650     }
652     /** Internal function */
653     private function _fix_sql_params_dollar_callback($match) {
654         $this->fix_sql_params_i++;
655         return "\$".$this->fix_sql_params_i;
656     }
658     /**
659      * Normalizes sql query parameters and verifies parameters.
660      * @param string $sql query or part of it
661      * @param array $params query parameters
662      * @return array (sql, params, type of params)
663      */
664     public function fix_sql_params($sql, array $params=null) {
665         $params = (array)$params; // mke null array if needed
666         $allowed_types = $this->allowed_param_types();
668         // convert table names
669         $sql = $this->fix_table_names($sql);
671         // cast booleans to 1/0 int
672         foreach ($params as $key => $value) {
673             $params[$key] = is_bool($value) ? (int)$value : $value;
674         }
676         // NICOLAS C: Fixed regexp for negative backwards lookahead of double colons. Thanks for Sam Marshall's help
677         $named_count = preg_match_all('/(?<!:):[a-z][a-z0-9_]*/', $sql, $named_matches); // :: used in pgsql casts
678         $dollar_count = preg_match_all('/\$[1-9][0-9]*/', $sql, $dollar_matches);
679         $q_count     = substr_count($sql, '?');
681         $count = 0;
683         if ($named_count) {
684             $type = SQL_PARAMS_NAMED;
685             $count = $named_count;
687         }
688         if ($dollar_count) {
689             if ($count) {
690                 throw new dml_exception('mixedtypesqlparam');
691             }
692             $type = SQL_PARAMS_DOLLAR;
693             $count = $dollar_count;
695         }
696         if ($q_count) {
697             if ($count) {
698                 throw new dml_exception('mixedtypesqlparam');
699             }
700             $type = SQL_PARAMS_QM;
701             $count = $q_count;
703         }
705         if (!$count) {
706              // ignore params
707             if ($allowed_types & SQL_PARAMS_NAMED) {
708                 return array($sql, array(), SQL_PARAMS_NAMED);
709             } else if ($allowed_types & SQL_PARAMS_QM) {
710                 return array($sql, array(), SQL_PARAMS_QM);
711             } else {
712                 return array($sql, array(), SQL_PARAMS_DOLLAR);
713             }
714         }
716         if ($count > count($params)) {
717             $a = new stdClass;
718             $a->expected = $count;
719             $a->actual = count($params);
720             throw new dml_exception('invalidqueryparam', $a);
721         }
723         $target_type = $allowed_types;
725         if ($type & $allowed_types) { // bitwise AND
726             if ($count == count($params)) {
727                 if ($type == SQL_PARAMS_QM) {
728                     return array($sql, array_values($params), SQL_PARAMS_QM); // 0-based array required
729                 } else {
730                     //better do the validation of names below
731                 }
732             }
733             // needs some fixing or validation - there might be more params than needed
734             $target_type = $type;
735         }
737         if ($type == SQL_PARAMS_NAMED) {
738             $finalparams = array();
739             foreach ($named_matches[0] as $key) {
740                 $key = trim($key, ':');
741                 if (!array_key_exists($key, $params)) {
742                     throw new dml_exception('missingkeyinsql', $key, '');
743                 }
744                 $finalparams[$key] = $params[$key];
745             }
746             if ($count != count($finalparams)) {
747                 throw new dml_exception('duplicateparaminsql');
748             }
750             if ($target_type & SQL_PARAMS_QM) {
751                 $sql = preg_replace('/(?<!:):[a-z][a-z0-9_]*/', '?', $sql);
752                 return array($sql, array_values($finalparams), SQL_PARAMS_QM); // 0-based required
753             } else if ($target_type & SQL_PARAMS_NAMED) {
754                 return array($sql, $finalparams, SQL_PARAMS_NAMED);
755             } else {  // $type & SQL_PARAMS_DOLLAR
756                 //lambda-style functions eat memory - we use globals instead :-(
757                 $this->fix_sql_params_i = 0;
758                 $sql = preg_replace_callback('/(?<!:):[a-z][a-z0-9_]*/', array($this, '_fix_sql_params_dollar_callback'), $sql);
759                 return array($sql, array_values($finalparams), SQL_PARAMS_DOLLAR); // 0-based required
760             }
762         } else if ($type == SQL_PARAMS_DOLLAR) {
763             if ($target_type & SQL_PARAMS_DOLLAR) {
764                 return array($sql, array_values($params), SQL_PARAMS_DOLLAR); // 0-based required
765             } else if ($target_type & SQL_PARAMS_QM) {
766                 $sql = preg_replace('/\$[0-9]+/', '?', $sql);
767                 return array($sql, array_values($params), SQL_PARAMS_QM); // 0-based required
768             } else { //$target_type & SQL_PARAMS_NAMED
769                 $sql = preg_replace('/\$([0-9]+)/', ':param\\1', $sql);
770                 $finalparams = array();
771                 foreach ($params as $key=>$param) {
772                     $key++;
773                     $finalparams['param'.$key] = $param;
774                 }
775                 return array($sql, $finalparams, SQL_PARAMS_NAMED);
776             }
778         } else { // $type == SQL_PARAMS_QM
779             if (count($params) != $count) {
780                 $params = array_slice($params, 0, $count);
781             }
783             if ($target_type & SQL_PARAMS_QM) {
784                 return array($sql, array_values($params), SQL_PARAMS_QM); // 0-based required
785             } else if ($target_type & SQL_PARAMS_NAMED) {
786                 $finalparams = array();
787                 $pname = 'param0';
788                 $parts = explode('?', $sql);
789                 $sql = array_shift($parts);
790                 foreach ($parts as $part) {
791                     $param = array_shift($params);
792                     $pname++;
793                     $sql .= ':'.$pname.$part;
794                     $finalparams[$pname] = $param;
795                 }
796                 return array($sql, $finalparams, SQL_PARAMS_NAMED);
797             } else {  // $type & SQL_PARAMS_DOLLAR
798                 //lambda-style functions eat memory - we use globals instead :-(
799                 $this->fix_sql_params_i = 0;
800                 $sql = preg_replace_callback('/\?/', array($this, '_fix_sql_params_dollar_callback'), $sql);
801                 return array($sql, array_values($params), SQL_PARAMS_DOLLAR); // 0-based required
802             }
803         }
804     }
806     /**
807      * Return tables in database WITHOUT current prefix
808      * @return array of table names in lowercase and without prefix
809      */
810     public abstract function get_tables($usecache=true);
812     /**
813      * Return table indexes - everything lowercased
814      * @return array of arrays
815      */
816     public abstract function get_indexes($table);
818     /**
819      * Returns detailed information about columns in table. This information is cached internally.
820      * @param string $table name
821      * @param bool $usecache
822      * @return array of database_column_info objects indexed with column names
823      */
824     public abstract function get_columns($table, $usecache=true);
826     /**
827      * Normalise values based in RDBMS dependencies (booleans, LOBs...)
828      *
829      * @param database_column_info $column column metadata corresponding with the value we are going to normalise
830      * @param mixed $value value we are going to normalise
831      * @return mixed the normalised value
832      */
833     protected abstract function normalise_value($column, $value);
835     /**
836      * Reset internal column details cache
837      * @param string $table - empty means all, or one if name of table given
838      * @return void
839      */
840     public function reset_caches() {
841         $this->columns = array();
842         $this->tables  = null;
843     }
845     /**
846      * Returns sql generator used for db manipulation.
847      * Used mostly in upgrade.php scripts.
848      * @return database_manager instance
849      */
850     public function get_manager() {
851         global $CFG;
853         if (!$this->database_manager) {
854             require_once($CFG->libdir.'/ddllib.php');
856             $classname = $this->get_dbfamily().'_sql_generator';
857             require_once("$CFG->libdir/ddl/$classname.php");
858             $generator = new $classname($this, $this->temptables);
860             $this->database_manager = new database_manager($this, $generator);
861         }
862         return $this->database_manager;
863     }
865     /**
866      * Attempt to change db encoding toUTF-8 if possible
867      * @return bool success
868      */
869     public function change_db_encoding() {
870         return false;
871     }
873     /**
874      * Is db in unicode mode?
875      * @return bool
876      */
877     public function setup_is_unicodedb() {
878         return true;
879     }
881     /**
882      * Enable/disable very detailed debugging
883      * @param bool $state
884      * @return void
885      */
886     public function set_debug($state) {
887         $this->debug = $state;
888     }
890     /**
891      * Returns debug status
892      * @return bool $state
893      */
894     public function get_debug() {
895         return $this->debug;
896     }
898     /**
899      * Enable/disable detailed sql logging
900      * @param bool $state
901      */
902     public function set_logging($state) {
903         // adodb sql logging shares one table without prefix per db - this is no longer acceptable :-(
904         // we must create one table shared by all drivers
905     }
907     /**
908      * Do NOT use in code, to be used by database_manager only!
909      * @param string $sql query
910      * @return bool true
911      * @throws dml_exception if error
912      */
913     public abstract function change_database_structure($sql);
915     /**
916      * Execute general sql query. Should be used only when no other method suitable.
917      * Do NOT use this to make changes in db structure, use database_manager::execute_sql() instead!
918      * @param string $sql query
919      * @param array $params query parameters
920      * @return bool true
921      * @throws dml_exception if error
922      */
923     public abstract function execute($sql, array $params=null);
925     /**
926      * Get a number of records as a moodle_recordset where all the given conditions met.
927      *
928      * Selects records from the table $table.
929      *
930      * If specified, only records meeting $conditions.
931      *
932      * If specified, the results will be sorted as specified by $sort. This
933      * is added to the SQL as "ORDER BY $sort". Example values of $sort
934      * might be "time ASC" or "time DESC".
935      *
936      * If $fields is specified, only those fields are returned.
937      *
938      * Since this method is a little less readable, use of it should be restricted to
939      * code where it's possible there might be large datasets being returned.  For known
940      * small datasets use get_records - it leads to simpler code.
941      *
942      * If you only want some of the records, specify $limitfrom and $limitnum.
943      * The query will skip the first $limitfrom records (according to the sort
944      * order) and then return the next $limitnum records. If either of $limitfrom
945      * or $limitnum is specified, both must be present.
946      *
947      * The return value is a moodle_recordset
948      * if the query succeeds. If an error occurs, false is returned.
949      *
950      * @param string $table the table to query.
951      * @param array $conditions optional array $fieldname=>requestedvalue with AND in between
952      * @param string $sort an order to sort the results in (optional, a valid SQL ORDER BY parameter).
953      * @param string $fields a comma separated list of fields to return (optional, by default all fields are returned).
954      * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
955      * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
956      * @return moodle_recordset instance
957      * @throws dml_exception if error
958      */
959     public function get_recordset($table, array $conditions=null, $sort='', $fields='*', $limitfrom=0, $limitnum=0) {
960         list($select, $params) = $this->where_clause($table, $conditions);
961         return $this->get_recordset_select($table, $select, $params, $sort, $fields, $limitfrom, $limitnum);
962     }
964     /**
965      * Get a number of records as a moodle_recordset where one field match one list of values.
966      *
967      * Only records where $field takes one of the values $values are returned.
968      * $values must be an array of values.
969      *
970      * Other arguments and the return type as for @see function get_recordset.
971      *
972      * @param string $table the table to query.
973      * @param string $field a field to check (optional).
974      * @param array $values array of values the field must have
975      * @param string $sort an order to sort the results in (optional, a valid SQL ORDER BY parameter).
976      * @param string $fields a comma separated list of fields to return (optional, by default all fields are returned).
977      * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
978      * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
979      * @return moodle_recordset instance
980      * @throws dml_exception if error
981      */
982     public function get_recordset_list($table, $field, array $values, $sort='', $fields='*', $limitfrom=0, $limitnum=0) {
983         list($select, $params) = $this->where_clause_list($field, $values);
984         if (empty($select)) {
985             $select = '1 = 2'; /// Fake condition, won't return rows ever. MDL-17645
986             $params = array();
987         }
988         return $this->get_recordset_select($table, $select, $params, $sort, $fields, $limitfrom, $limitnum);
989     }
991     /**
992      * Get a number of records as a moodle_recordset which match a particular WHERE clause.
993      *
994      * If given, $select is used as the SELECT parameter in the SQL query,
995      * otherwise all records from the table are returned.
996      *
997      * Other arguments and the return type as for @see function get_recordset.
998      *
999      * @param string $table the table to query.
1000      * @param string $select A fragment of SQL to be used in a where clause in the SQL call.
1001      * @param array $params array of sql parameters
1002      * @param string $sort an order to sort the results in (optional, a valid SQL ORDER BY parameter).
1003      * @param string $fields a comma separated list of fields to return (optional, by default all fields are returned).
1004      * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
1005      * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
1006      * @return moodle_recordset instance
1007      * @throws dml_exception if error
1008      */
1009     public function get_recordset_select($table, $select, array $params=null, $sort='', $fields='*', $limitfrom=0, $limitnum=0) {
1010         $sql = "SELECT $fields FROM {".$table."}";
1011         if ($select) {
1012             $sql .= " WHERE $select";
1013         }
1014         if ($sort) {
1015             $sql .= " ORDER BY $sort";
1016         }
1017         return $this->get_recordset_sql($sql, $params, $limitfrom, $limitnum);
1018     }
1020     /**
1021      * Get a number of records as a moodle_recordset using a SQL statement.
1022      *
1023      * Since this method is a little less readable, use of it should be restricted to
1024      * code where it's possible there might be large datasets being returned.  For known
1025      * small datasets use get_records_sql - it leads to simpler code.
1026      *
1027      * The return type is as for @see function get_recordset.
1028      *
1029      * @param string $sql the SQL select query to execute.
1030      * @param array $params array of sql parameters
1031      * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
1032      * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
1033      * @return moodle_recordset instance
1034      * @throws dml_exception if error
1035      */
1036     public abstract function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0);
1038     /**
1039      * Get a number of records as an array of objects where all the given conditions met.
1040      *
1041      * If the query succeeds and returns at least one record, the
1042      * return value is an array of objects, one object for each
1043      * record found. The array key is the value from the first
1044      * column of the result set. The object associated with that key
1045      * has a member variable for each column of the results.
1046      *
1047      * @param string $table the table to query.
1048      * @param array $conditions optional array $fieldname=>requestedvalue with AND in between
1049      * @param string $sort an order to sort the results in (optional, a valid SQL ORDER BY parameter).
1050      * @param string $fields a comma separated list of fields to return (optional, by default
1051      *   all fields are returned). The first field will be used as key for the
1052      *   array so must be a unique field such as 'id'.
1053      * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
1054      * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
1055      * @return array of objects indexed by first column
1056      * @throws dml_exception if error
1057      */
1058     public function get_records($table, array $conditions=null, $sort='', $fields='*', $limitfrom=0, $limitnum=0) {
1059         list($select, $params) = $this->where_clause($table, $conditions);
1060         return $this->get_records_select($table, $select, $params, $sort, $fields, $limitfrom, $limitnum);
1061     }
1063     /**
1064      * Get a number of records as an array of objects where one field match one list of values.
1065      *
1066      * Return value as for @see function get_records.
1067      *
1068      * @param string $table The database table to be checked against.
1069      * @param string $field The field to search
1070      * @param string $values array of values
1071      * @param string $sort Sort order (as valid SQL sort parameter)
1072      * @param string $fields A comma separated list of fields to be returned from the chosen table. If specified,
1073      *   the first field should be a unique one such as 'id' since it will be used as a key in the associative
1074      *   array.
1075      * @return array of objects indexed by first column
1076      * @throws dml_exception if error
1077      */
1078     public function get_records_list($table, $field, array $values, $sort='', $fields='*', $limitfrom=0, $limitnum=0) {
1079         list($select, $params) = $this->where_clause_list($field, $values);
1080         if (empty($select)) {
1081             // nothing to return
1082             return array();
1083         }
1084         return $this->get_records_select($table, $select, $params, $sort, $fields, $limitfrom, $limitnum);
1085     }
1087     /**
1088      * Get a number of records as an array of objects which match a particular WHERE clause.
1089      *
1090      * Return value as for @see function get_records.
1091      *
1092      * @param string $table the table to query.
1093      * @param string $select A fragment of SQL to be used in a where clause in the SQL call.
1094      * @param array $params array of sql parameters
1095      * @param string $sort an order to sort the results in (optional, a valid SQL ORDER BY parameter).
1096      * @param string $fields a comma separated list of fields to return
1097      *   (optional, by default all fields are returned). The first field will be used as key for the
1098      *   array so must be a unique field such as 'id'.
1099      * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
1100      * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
1101      * @return array of objects indexed by first column
1102      * @throws dml_exception if error
1103      */
1104     public function get_records_select($table, $select, array $params=null, $sort='', $fields='*', $limitfrom=0, $limitnum=0) {
1105         if ($select) {
1106             $select = "WHERE $select";
1107         }
1108         if ($sort) {
1109             $sort = " ORDER BY $sort";
1110         }
1111         return $this->get_records_sql("SELECT $fields FROM {" . $table . "} $select $sort", $params, $limitfrom, $limitnum);
1112     }
1114     /**
1115      * Get a number of records as an array of objects using a SQL statement.
1116      *
1117      * Return value as for @see function get_records.
1118      *
1119      * @param string $sql the SQL select query to execute. The first column of this SELECT statement
1120      *   must be a unique value (usually the 'id' field), as it will be used as the key of the
1121      *   returned array.
1122      * @param array $params array of sql parameters
1123      * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
1124      * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
1125      * @return array of objects indexed by first column
1126      * @throws dml_exception if error
1127      */
1128     public abstract function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0);
1130     /**
1131      * Get the first two columns from a number of records as an associative array where all the given conditions met.
1132      *
1133      * Arguments as for @see function get_recordset.
1134      *
1135      * If no errors occur the return value
1136      * is an associative whose keys come from the first field of each record,
1137      * and whose values are the corresponding second fields.
1138      * False is returned if an error occurs.
1139      *
1140      * @param string $table the table to query.
1141      * @param array $conditions optional array $fieldname=>requestedvalue with AND in between
1142      * @param string $sort an order to sort the results in (optional, a valid SQL ORDER BY parameter).
1143      * @param string $fields a comma separated list of fields to return - the number of fields should be 2!
1144      * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
1145      * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
1146      * @return array an associative array
1147      * @throws dml_exception if error
1148      */
1149     public function get_records_menu($table, array $conditions=null, $sort='', $fields='*', $limitfrom=0, $limitnum=0) {
1150         $menu = array();
1151         if ($records = $this->get_records($table, $conditions, $sort, $fields, $limitfrom, $limitnum)) {
1152             foreach ($records as $record) {
1153                 $record = (array)$record;
1154                 $key   = array_shift($record);
1155                 $value = array_shift($record);
1156                 $menu[$key] = $value;
1157             }
1158         }
1159         return $menu;
1160     }
1162     /**
1163      * Get the first two columns from a number of records as an associative array which match a particular WHERE clause.
1164      *
1165      * Arguments as for @see function get_recordset_select.
1166      * Return value as for @see function get_records_menu.
1167      *
1168      * @param string $table The database table to be checked against.
1169      * @param string $select A fragment of SQL to be used in a where clause in the SQL call.
1170      * @param array $params array of sql parameters
1171      * @param string $sort Sort order (optional) - a valid SQL order parameter
1172      * @param string $fields A comma separated list of fields to be returned from the chosen table - the number of fields should be 2!
1173      * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
1174      * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
1175      * @return array an associative array
1176      * @throws dml_exception if error
1177      */
1178     public function get_records_select_menu($table, $select, array $params=null, $sort='', $fields='*', $limitfrom=0, $limitnum=0) {
1179         $menu = array();
1180         if ($records = $this->get_records_select($table, $select, $params, $sort, $fields, $limitfrom, $limitnum)) {
1181             foreach ($records as $record) {
1182                 $record = (array)$record;
1183                 $key   = array_shift($record);
1184                 $value = array_shift($record);
1185                 $menu[$key] = $value;
1186             }
1187         }
1188         return $menu;
1189     }
1191     /**
1192      * Get the first two columns from a number of records as an associative array using a SQL statement.
1193      *
1194      * Arguments as for @see function get_recordset_sql.
1195      * Return value as for @see function get_records_menu.
1196      *
1197      * @param string $sql The SQL string you wish to be executed.
1198      * @param array $params array of sql parameters
1199      * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
1200      * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
1201      * @return array an associative array
1202      * @throws dml_exception if error
1203      */
1204     public function get_records_sql_menu($sql, array $params=null, $limitfrom=0, $limitnum=0) {
1205         $menu = array();
1206         if ($records = $this->get_records_sql($sql, $params, $limitfrom, $limitnum)) {
1207             foreach ($records as $record) {
1208                 $record = (array)$record;
1209                 $key   = array_shift($record);
1210                 $value = array_shift($record);
1211                 $menu[$key] = $value;
1212             }
1213         }
1214         return $menu;
1215     }
1217     /**
1218      * Get a single database record as an object where all the given conditions met.
1219      *
1220      * @param string $table The table to select from.
1221      * @param array $conditions optional array $fieldname=>requestedvalue with AND in between
1222      * @param string $fields A comma separated list of fields to be returned from the chosen table.
1223      * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
1224      *                        IGNORE_MULTIPLE means return first, ignore multiple records found(not recommended);
1225      *                        MUST_EXIST means throw exception if no record or multiple records found
1226      * @return mixed a fieldset object containing the first matching record, false or exception if error not found depending on mode
1227      * @throws dml_exception if error
1228      */
1229     public function get_record($table, array $conditions, $fields='*', $strictness=IGNORE_MISSING) {
1230         list($select, $params) = $this->where_clause($table, $conditions);
1231         return $this->get_record_select($table, $select, $params, $fields, $strictness);
1232     }
1234     /**
1235      * Get a single database record as an object which match a particular WHERE clause.
1236      *
1237      * @param string $table The database table to be checked against.
1238      * @param string $select A fragment of SQL to be used in a where clause in the SQL call.
1239      * @param array $params array of sql parameters
1240      * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
1241      *                        IGNORE_MULTIPLE means return first, ignore multiple records found(not recommended);
1242      *                        MUST_EXIST means throw exception if no record or multiple records found
1243      * @return mixed a fieldset object containing the first matching record, false or exception if error not found depending on mode
1244      * @throws dml_exception if error
1245      */
1246     public function get_record_select($table, $select, array $params=null, $fields='*', $strictness=IGNORE_MISSING) {
1247         if ($select) {
1248             $select = "WHERE $select";
1249         }
1250         try {
1251             return $this->get_record_sql("SELECT $fields FROM {" . $table . "} $select", $params, $strictness);
1252         } catch (dml_missing_record_exception $e) {
1253             // create new exception which will contain correct table name
1254             throw new dml_missing_record_exception($table, $e->sql, $e->params);
1255         }
1256     }
1258     /**
1259      * Get a single database record as an object using a SQL statement.
1260      *
1261      * The SQL statement should normally only return one record.
1262      * It is recommended to use get_records_sql() if more matches possible!
1263      *
1264      * @param string $sql The SQL string you wish to be executed, should normally only return one record.
1265      * @param array $params array of sql parameters
1266      * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
1267      *                        IGNORE_MULTIPLE means return first, ignore multiple records found(not recommended);
1268      *                        MUST_EXIST means throw exception if no record or multiple records found
1269      * @return mixed a fieldset object containing the first matching record, false or exception if error not found depending on mode
1270      * @throws dml_exception if error
1271      */
1272     public function get_record_sql($sql, array $params=null, $strictness=IGNORE_MISSING) {
1273         $strictness = (int)$strictness; // we support true/false for BC reasons too
1274         if ($strictness == IGNORE_MULTIPLE) {
1275             $count = 1;
1276         } else {
1277             $count = 0;
1278         }
1279         if (!$records = $this->get_records_sql($sql, $params, 0, $count)) {
1280             // not found
1281             if ($strictness == MUST_EXIST) {
1282                 throw new dml_missing_record_exception('', $sql, $params);
1283             }
1284             return false;
1285         }
1287         if (count($records) > 1) {
1288             if ($strictness == MUST_EXIST) {
1289                 throw new dml_multiple_records_exception($sql, $params);
1290             }
1291             debugging('Error: mdb->get_record() found more than one record!');
1292         }
1294         $return = reset($records);
1295         return $return;
1296     }
1298     /**
1299      * Get a single field value from a table record where all the given conditions met.
1300      *
1301      * @param string $table the table to query.
1302      * @param string $return the field to return the value of.
1303      * @param array $conditions optional array $fieldname=>requestedvalue with AND in between
1304      * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
1305      *                        IGNORE_MULTIPLE means return first, ignore multiple records found(not recommended);
1306      *                        MUST_EXIST means throw exception if no record or multiple records found
1307      * @return mixed the specified value false if not found
1308      * @throws dml_exception if error
1309      */
1310     public function get_field($table, $return, array $conditions, $strictness=IGNORE_MISSING) {
1311         list($select, $params) = $this->where_clause($table, $conditions);
1312         return $this->get_field_select($table, $return, $select, $params, $strictness);
1313     }
1315     /**
1316      * Get a single field value from a table record which match a particular WHERE clause.
1317      *
1318      * @param string $table the table to query.
1319      * @param string $return the field to return the value of.
1320      * @param string $select A fragment of SQL to be used in a where clause returning one row with one column
1321      * @param array $params array of sql parameters
1322      * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
1323      *                        IGNORE_MULTIPLE means return first, ignore multiple records found(not recommended);
1324      *                        MUST_EXIST means throw exception if no record or multiple records found
1325      * @return mixed the specified value false if not found
1326      * @throws dml_exception if error
1327      */
1328     public function get_field_select($table, $return, $select, array $params=null, $strictness=IGNORE_MISSING) {
1329         if ($select) {
1330             $select = "WHERE $select";
1331         }
1332         try {
1333             return $this->get_field_sql("SELECT $return FROM {" . $table . "} $select", $params, $strictness);
1334         } catch (dml_missing_record_exception $e) {
1335             // create new exception which will contain correct table name
1336             throw new dml_missing_record_exception($table, $e->sql, $e->params);
1337         }
1338     }
1340     /**
1341      * Get a single field value (first field) using a SQL statement.
1342      *
1343      * @param string $table the table to query.
1344      * @param string $return the field to return the value of.
1345      * @param string $sql The SQL query returning one row with one column
1346      * @param array $params array of sql parameters
1347      * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
1348      *                        IGNORE_MULTIPLE means return first, ignore multiple records found(not recommended);
1349      *                        MUST_EXIST means throw exception if no record or multiple records found
1350      * @return mixed the specified value false if not found
1351      * @throws dml_exception if error
1352      */
1353     public function get_field_sql($sql, array $params=null, $strictness=IGNORE_MISSING) {
1354         if (!$record = $this->get_record_sql($sql, $params, $strictness)) {
1355             return false;
1356         }
1358         $record = (array)$record;
1359         return reset($record); // first column
1360     }
1362     /**
1363      * Selects records and return values of chosen field as an array which match a particular WHERE clause.
1364      *
1365      * @param string $table the table to query.
1366      * @param string $return the field we are intered in
1367      * @param string $select A fragment of SQL to be used in a where clause in the SQL call.
1368      * @param array $params array of sql parameters
1369      * @return array of values
1370      * @throws dml_exception if error
1371      */
1372     public function get_fieldset_select($table, $return, $select, array $params=null) {
1373         if ($select) {
1374             $select = "WHERE $select";
1375         }
1376         return $this->get_fieldset_sql("SELECT $return FROM {" . $table . "} $select", $params);
1377     }
1379     /**
1380      * Selects records and return values (first field) as an array using a SQL statement.
1381      *
1382      * @param string $sql The SQL query
1383      * @param array $params array of sql parameters
1384      * @return array of values
1385      * @throws dml_exception if error
1386      */
1387     public abstract function get_fieldset_sql($sql, array $params=null);
1389     /**
1390      * Insert new record into database, as fast as possible, no safety checks, lobs not supported.
1391      * @param string $table name
1392      * @param mixed $params data record as object or array
1393      * @param bool $returnit return it of inserted record
1394      * @param bool $bulk true means repeated inserts expected
1395      * @param bool $customsequence true if 'id' included in $params, disables $returnid
1396      * @return bool|int true or new id
1397      * @throws dml_exception if error
1398      */
1399     public abstract function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false);
1401     /**
1402      * Insert a record into a table and return the "id" field if required.
1403      *
1404      * Some conversions and safety checks are carried out. Lobs are supported.
1405      * If the return ID isn't required, then this just reports success as true/false.
1406      * $data is an object containing needed data
1407      * @param string $table The database table to be inserted into
1408      * @param object $data A data object with values for one or more fields in the record
1409      * @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.
1410      * @return bool|int true or new id
1411      * @throws dml_exception if error
1412      */
1413     public abstract function insert_record($table, $dataobject, $returnid=true, $bulk=false);
1415     /**
1416      * Import a record into a table, id field is required.
1417      * Safety checks are NOT carried out. Lobs are supported.
1418      *
1419      * @param string $table name of database table to be inserted into
1420      * @param object $dataobject A data object with values for one or more fields in the record
1421      * @return bool true
1422      * @throws dml_exception if error
1423      */
1424     public abstract function import_record($table, $dataobject);
1426     /**
1427      * Update record in database, as fast as possible, no safety checks, lobs not supported.
1428      * @param string $table name
1429      * @param mixed $params data record as object or array
1430      * @param bool true means repeated updates expected
1431      * @return bool true
1432      * @throws dml_exception if error
1433      */
1434     public abstract function update_record_raw($table, $params, $bulk=false);
1436     /**
1437      * Update a record in a table
1438      *
1439      * $dataobject is an object containing needed data
1440      * Relies on $dataobject having a variable "id" to
1441      * specify the record to update
1442      *
1443      * @param string $table The database table to be checked against.
1444      * @param object $dataobject An object with contents equal to fieldname=>fieldvalue. Must have an entry for 'id' to map to the table specified.
1445      * @param bool true means repeated updates expected
1446      * @return bool true
1447      * @throws dml_exception if error
1448      */
1449     public abstract function update_record($table, $dataobject, $bulk=false);
1452     /**
1453      * Set a single field in every table record where all the given conditions met.
1454      *
1455      * @param string $table The database table to be checked against.
1456      * @param string $newfield the field to set.
1457      * @param string $newvalue the value to set the field to.
1458      * @param array $conditions optional array $fieldname=>requestedvalue with AND in between
1459      * @return bool true
1460      * @throws dml_exception if error
1461      */
1462     public function set_field($table, $newfield, $newvalue, array $conditions=null) {
1463         list($select, $params) = $this->where_clause($table, $conditions);
1464         return $this->set_field_select($table, $newfield, $newvalue, $select, $params);
1465     }
1467     /**
1468      * Set a single field in every table record which match a particular WHERE clause.
1469      *
1470      * @param string $table The database table to be checked against.
1471      * @param string $newfield the field to set.
1472      * @param string $newvalue the value to set the field to.
1473      * @param string $select A fragment of SQL to be used in a where clause in the SQL call.
1474      * @param array $params array of sql parameters
1475      * @return bool true
1476      * @throws dml_exception if error
1477      */
1478     public abstract function set_field_select($table, $newfield, $newvalue, $select, array $params=null);
1481     /**
1482      * Count the records in a table where all the given conditions met.
1483      *
1484      * @param string $table The table to query.
1485      * @param array $conditions optional array $fieldname=>requestedvalue with AND in between
1486      * @return int The count of records returned from the specified criteria.
1487      * @throws dml_exception if error
1488      */
1489     public function count_records($table, array $conditions=null) {
1490         list($select, $params) = $this->where_clause($table, $conditions);
1491         return $this->count_records_select($table, $select, $params);
1492     }
1494     /**
1495      * Count the records in a table which match a particular WHERE clause.
1496      *
1497      * @param string $table The database table to be checked against.
1498      * @param string $select A fragment of SQL to be used in a WHERE clause in the SQL call.
1499      * @param array $params array of sql parameters
1500      * @param string $countitem The count string to be used in the SQL call. Default is COUNT('x').
1501      * @return int The count of records returned from the specified criteria.
1502      * @throws dml_exception if error
1503      */
1504     public function count_records_select($table, $select, array $params=null, $countitem="COUNT('x')") {
1505         if ($select) {
1506             $select = "WHERE $select";
1507         }
1508         return $this->count_records_sql("SELECT $countitem FROM {" . $table . "} $select", $params);
1509     }
1511     /**
1512      * Get the result of a SQL SELECT COUNT(...) query.
1513      *
1514      * Given a query that counts rows, return that count. (In fact,
1515      * given any query, return the first field of the first record
1516      * returned. However, this method should only be used for the
1517      * intended purpose.) If an error occurs, 0 is returned.
1518      *
1519      * @param string $sql The SQL string you wish to be executed.
1520      * @param array $params array of sql parameters
1521      * @return int the count
1522      * @throws dml_exception if error
1523      */
1524     public function count_records_sql($sql, array $params=null) {
1525         if ($count = $this->get_field_sql($sql, $params)) {
1526             return $count;
1527         } else {
1528             return 0;
1529         }
1530     }
1532     /**
1533      * Test whether a record exists in a table where all the given conditions met.
1534      *
1535      * The record to test is specified by giving up to three fields that must
1536      * equal the corresponding values.
1537      *
1538      * @param string $table The table to check.
1539      * @param array $conditions optional array $fieldname=>requestedvalue with AND in between
1540      * @return bool true if a matching record exists, else false.
1541      * @throws dml_exception if error
1542      */
1543     public function record_exists($table, array $conditions) {
1544         list($select, $params) = $this->where_clause($table, $conditions);
1545         return $this->record_exists_select($table, $select, $params);
1546     }
1548     /**
1549      * Test whether any records exists in a table which match a particular WHERE clause.
1550      *
1551      * @param string $table The database table to be checked against.
1552      * @param string $select A fragment of SQL to be used in a WHERE clause in the SQL call.
1553      * @param array $params array of sql parameters
1554      * @return bool true if a matching record exists, else false.
1555      * @throws dml_exception if error
1556      */
1557     public function record_exists_select($table, $select, array $params=null) {
1558         if ($select) {
1559             $select = "WHERE $select";
1560         }
1561         return $this->record_exists_sql("SELECT 'x' FROM {" . $table . "} $select", $params);
1562     }
1564     /**
1565      * Test whether a SQL SELECT statement returns any records.
1566      *
1567      * This function returns true if the SQL statement executes
1568      * without any errors and returns at least one record.
1569      *
1570      * @param string $sql The SQL statement to execute.
1571      * @param array $params array of sql parameters
1572      * @return bool true if the SQL executes without errors and returns at least one record.
1573      * @throws dml_exception if error
1574      */
1575     public function record_exists_sql($sql, array $params=null) {
1576         $mrs = $this->get_recordset_sql($sql, $params, 0, 1);
1577         $return = $mrs->valid();
1578         $mrs->close();
1579         return $return;
1580     }
1582     /**
1583      * Delete the records from a table where all the given conditions met.
1584      * If conditions not specified, table is truncated.
1585      *
1586      * @param string $table the table to delete from.
1587      * @param array $conditions optional array $fieldname=>requestedvalue with AND in between
1588      * @return bool true.
1589      * @throws dml_exception if error
1590      */
1591     public function delete_records($table, array $conditions=null) {
1592         if (is_null($conditions)) {
1593             return $this->execute("TRUNCATE TABLE {".$table."}");
1594         }
1595         list($select, $params) = $this->where_clause($table, $conditions);
1596         return $this->delete_records_select($table, $select, $params);
1597     }
1599     /**
1600      * Delete the records from a table where one field match one list of values.
1601      *
1602      * @param string $table the table to delete from.
1603      * @param string $field The field to search
1604      * @param array $values array of values
1605      * @return bool true.
1606      * @throws dml_exception if error
1607      */
1608     public function delete_records_list($table, $field, array $values) {
1609         list($select, $params) = $this->where_clause_list($field, $values);
1610         if (empty($select)) {
1611             // nothing to delete
1612             return true;
1613         }
1614         return $this->delete_records_select($table, $select, $params);
1615     }
1617     /**
1618      * Delete one or more records from a table which match a particular WHERE clause.
1619      *
1620      * @param string $table The database table to be checked against.
1621      * @param string $select A fragment of SQL to be used in a where clause in the SQL call (used to define the selection criteria).
1622      * @param array $params array of sql parameters
1623      * @return bool true.
1624      * @throws dml_exception if error
1625      */
1626     public abstract function delete_records_select($table, $select, array $params=null);
1630 /// sql constructs
1631     /**
1632      * Returns the FROM clause required by some DBs in all SELECT statements.
1633      *
1634      * To be used in queries not having FROM clause to provide cross_db
1635      * Most DBs don't need it, hence the default is ''
1636      * @return string
1637      */
1638     public function sql_null_from_clause() {
1639         return '';
1640     }
1642     /**
1643      * Returns the SQL text to be used in order to perform one bitwise AND operation
1644      * between 2 integers.
1645      *
1646      * NOTE: The SQL result is a number and can not be used directly in
1647      *       SQL condition, please compare it to some number to get a bool!!
1648      *
1649      * @param int $int1 first integer in the operation
1650      * @param int $int2 second integer in the operation
1651      * @return string the piece of SQL code to be used in your statement
1652      */
1653     public function sql_bitand($int1, $int2) {
1654         return '((' . $int1 . ') & (' . $int2 . '))';
1655     }
1657     /**
1658      * Returns the SQL text to be used in order to perform one bitwise NOT operation
1659      * with 1 integer.
1660      *
1661      * @param int $int1 integer in the operation
1662      * @return string the piece of SQL code to be used in your statement.
1663      */
1664     public function sql_bitnot($int1) {
1665         return '(~(' . $int1 . '))';
1666     }
1668     /**
1669      * Returns the SQL text to be used in order to perform one bitwise OR operation
1670      * between 2 integers.
1671      *
1672      * NOTE: The SQL result is a number and can not be used directly in
1673      *       SQL condition, please compare it to some number to get a bool!!
1674      *
1675      * @param int $int1 first integer in the operation
1676      * @param int $int2 second integer in the operation
1677      * @return string the piece of SQL code to be used in your statement.
1678      */
1679     public function sql_bitor($int1, $int2) {
1680         return '((' . $int1 . ') | (' . $int2 . '))';
1681     }
1683     /**
1684      * Returns the SQL text to be used in order to perform one bitwise XOR operation
1685      * between 2 integers.
1686      *
1687      * NOTE: The SQL result is a number and can not be used directly in
1688      *       SQL condition, please compare it to some number to get a bool!!
1689      *
1690      * @param int $int1 first integer in the operation
1691      * @param int $int2 second integer in the operation
1692      * @return string the piece of SQL code to be used in your statement.
1693      */
1694     public function sql_bitxor($int1, $int2) {
1695         return '((' . $int1 . ') ^ (' . $int2 . '))';
1696     }
1698     /**
1699      * Returns the SQL text to be used in order to perform module '%'
1700      * operation - remainder after division
1701      *
1702      * @param int $int1 first integer in the operation
1703      * @param int $int2 second integer in the operation
1704      * @return string the piece of SQL code to be used in your statement.
1705      */
1706     public function sql_modulo($int1, $int2) {
1707         return '((' . $int1 . ') % (' . $int2 . '))';
1708     }
1710     /**
1711      * Returns the correct CEIL expression applied to fieldname.
1712      *
1713      * @param string $fieldname the field (or expression) we are going to ceil
1714      * @return string the piece of SQL code to be used in your ceiling statement
1715      * Most DB use CEIL(), hence it's the default.
1716      */
1717     public function sql_ceil($fieldname) {
1718         return ' CEIL(' . $fieldname . ')';
1719     }
1721     /**
1722      * Returns the SQL to be used in order to CAST one CHAR column to INTEGER.
1723      *
1724      * Be aware that the CHAR column you're trying to cast contains really
1725      * int values or the RDBMS will throw an error!
1726      *
1727      * @param string $fieldname the name of the field to be casted
1728      * @param bool $text to specify if the original column is one TEXT (CLOB) column (true). Defaults to false.
1729      * @return string the piece of SQL code to be used in your statement.
1730      */
1731     public function sql_cast_char2int($fieldname, $text=false) {
1732         return ' ' . $fieldname . ' ';
1733     }
1735     /**
1736      * Returns the SQL to be used in order to CAST one CHAR column to REAL number.
1737      *
1738      * Be aware that the CHAR column you're trying to cast contains really
1739      * numbers or the RDBMS will throw an error!
1740      *
1741      * @param string $fieldname the name of the field to be casted
1742      * @param bool $text to specify if the original column is one TEXT (CLOB) column (true). Defaults to false.
1743      * @return string the piece of SQL code to be used in your statement.
1744      */
1745     public function sql_cast_char2real($fieldname, $text=false) {
1746         return ' ' . $fieldname . ' ';
1747     }
1749     /**
1750      * Returns the SQL to be used in order to an UNSIGNED INTEGER column to SIGNED.
1751      *
1752      * (Only MySQL needs this. MySQL things that 1 * -1 = 18446744073709551615
1753      * if the 1 comes from an unsigned column).
1754      *
1755      * @param string $fieldname the name of the field to be cast
1756      * @return string the piece of SQL code to be used in your statement.
1757      */
1758     public function sql_cast_2signed($fieldname) {
1759         return ' ' . $fieldname . ' ';
1760     }
1762     /**
1763      * Returns the SQL text to be used to compare one TEXT (clob) column with
1764      * one varchar column, because some RDBMS doesn't support such direct
1765      * comparisons.
1766      *
1767      * @param string $fieldname the name of the TEXT field we need to order by
1768      * @param int $numchars of chars to use for the ordering (defaults to 32)
1769      * @return string the piece of SQL code to be used in your statement.
1770      */
1771     public function sql_compare_text($fieldname, $numchars=32) {
1772         return $this->sql_order_by_text($fieldname, $numchars);
1773     }
1775     /**
1776      * Returns 'LIKE' part of a query.
1777      *
1778      * @param string $fieldname usually name of the table column
1779      * @param string $param usually bound query parameter (?, :named)
1780      * @param bool $casesensitive use case sensitive search
1781      * @param bool $accensensitive use accent sensitive search (not all databases support accent insensitive)
1782      * @param bool $notlike true means "NOT LIKE"
1783      * @param string $escapechar escape char for '%' and '_'
1784      * @return string SQL code fragment
1785      */
1786     public function sql_like($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notlike = false, $escapechar = '\\') {
1787         if (strpos($param, '%') !== false) {
1788             debugging('Potential SQL injection detected, sql_like() expects bound parameters (? or :named)');
1789         }
1790         $LIKE = $notlike ? 'NOT LIKE' : 'LIKE';
1791         // by default ignore any sensitiveness - each database does it in a different way
1792         return "$fieldname $LIKE $param ESCAPE '$escapechar'";
1793     }
1795     /**
1796      * Escape sql LIKE special characters.
1797      * @param string $text
1798      * @param string $escapechar
1799      * @return string
1800      */
1801     public function sql_like_escape($text, $escapechar = '\\') {
1802         $text = str_replace('_', $escapechar.'_', $text);
1803         $text = str_replace('%', $escapechar.'%', $text);
1804         return $text;
1805     }
1807     /**
1808      * Returns the proper SQL to do LIKE in a case-insensitive way.
1809      *
1810      * Note the LIKE are case sensitive for Oracle. Oracle 10g is required to use
1811      * the case insensitive search using regexp_like() or NLS_COMP=LINGUISTIC :-(
1812      * See http://docs.moodle.org/en/XMLDB_Problems#Case-insensitive_searches
1813      *
1814      * @deprecated
1815      * @return string
1816      */
1817     public function sql_ilike() {
1818         debugging('sql_ilike() is deprecated, please use sql_like() instead');
1819         return 'LIKE';
1820     }
1822     /**
1823      * Returns the proper SQL to do CONCAT between the elements passed
1824      * Can take many parameters
1825      *
1826      * This function accepts variable number of string parameters.
1827      *
1828      * @return string
1829      */
1830     public abstract function sql_concat();
1832     /**
1833      * Returns the proper SQL to do CONCAT between the elements passed
1834      * with a given separator
1835      *
1836      * @param string $separator
1837      * @param array  $elements
1838      * @return string
1839      */
1840     public abstract function sql_concat_join($separator="' '", $elements=array());
1842     /**
1843      * Returns the proper SQL (for the dbms in use) to concatenate $firstname and $lastname
1844      * TODO: Does this really need to be here? Eloy 20070727.
1845      * TODO: we definitely do not! (skodak)
1846      *
1847      * @param string $first User's first name
1848      * @param string $last User's last name
1849      * @return string
1850      */
1851     function sql_fullname($first='firstname', $last='lastname') {
1852         return $this->sql_concat($first, "' '", $last);
1853     }
1855     /**
1856      * Returns the SQL text to be used to order by one TEXT (clob) column, because
1857      * some RDBMS doesn't support direct ordering of such fields.
1858      *
1859      * Note that the use or queries being ordered by TEXT columns must be minimised,
1860      * because it's really slooooooow.
1861      *
1862      * @param string $fieldname the name of the TEXT field we need to order by
1863      * @param string $numchars of chars to use for the ordering (defaults to 32)
1864      * @return string the piece of SQL code to be used in your statement.
1865      */
1866     public function sql_order_by_text($fieldname, $numchars=32) {
1867         return $fieldname;
1868     }
1870     /**
1871      * Returns the SQL text to be used to calculate the length in characters of one expression.
1872      * @param string $fieldname or expression to calculate its length in characters.
1873      * @return string the piece of SQL code to be used in the statement.
1874      */
1875     public function sql_length($fieldname) {
1876         return ' LENGTH(' . $fieldname . ')';
1877     }
1879     /**
1880      * Returns the proper substr() SQL text used to extract substrings from DB
1881      * NOTE: this was originally returning only function name
1882      *
1883      * @param string $expr some string field, no aggregates
1884      * @param mixed $start integer or expression evaluating to int (1 based value; first char has index 1)
1885      * @param mixed $length optional integer or expression evaluating to int
1886      * @return string sql fragment
1887      */
1888     public function sql_substr($expr, $start, $length=false) {
1889         if (count(func_get_args()) < 2) {
1890             throw new coding_exception('moodle_database::sql_substr() requires at least two parameters', 'Originally this function was only returning name of SQL substring function, it now requires all parameters.');
1891         }
1892         if ($length === false) {
1893             return "SUBSTR($expr, $start)";
1894         } else {
1895             return "SUBSTR($expr, $start, $length)";
1896         }
1897     }
1899     /**
1900      * Returns the SQL for returning searching one string for the location of another.
1901      * Note, there is no guarantee which order $needle, $haystack will be in
1902      * the resulting SQL, so when using this method, and both arguments contain
1903      * placeholders, you should use named placeholders.
1904      * @param string $needle the SQL expression that will be searched for.
1905      * @param string $haystack the SQL expression that will be searched in.
1906      * @return string the required SQL
1907      */
1908     public function sql_position($needle, $haystack) {
1909         // Implementation using standard SQL.
1910         return "POSITION(($needle) IN ($haystack))";
1911     }
1913     /**
1914      * Returns the empty string char used by every supported DB. To be used when
1915      * we are searching for that values in our queries. Only Oracle uses this
1916      * for now (will be out, once we migrate to proper NULLs if that days arrives)
1917      * @return string
1918      */
1919     function sql_empty() {
1920         return '';
1921     }
1923     /**
1924      * Returns the proper SQL to know if one field is empty.
1925      *
1926      * Note that the function behavior strongly relies on the
1927      * parameters passed describing the field so, please,  be accurate
1928      * when specifying them.
1929      *
1930      * Also, note that this function is not suitable to look for
1931      * fields having NULL contents at all. It's all for empty values!
1932      *
1933      * This function should be applied in all the places where conditions of
1934      * the type:
1935      *
1936      *     ... AND fieldname = '';
1937      *
1938      * are being used. Final result should be:
1939      *
1940      *     ... AND ' . sql_isempty('tablename', 'fieldname', true/false, true/false);
1941      *
1942      * (see parameters description below)
1943      *
1944      * @param string $tablename name of the table (without prefix). Not used for now but can be
1945      *                          necessary in the future if we want to use some introspection using
1946      *                          meta information against the DB. /// TODO ///
1947      * @param string $fieldname name of the field we are going to check
1948      * @param boolean $nullablefield to specify if the field us nullable (true) or no (false) in the DB
1949      * @param boolean $textfield to specify if it is a text (also called clob) field (true) or a varchar one (false)
1950      * @return string the sql code to be added to check for empty values
1951      */
1952     public function sql_isempty($tablename, $fieldname, $nullablefield, $textfield) {
1953         return " ($fieldname = '') ";
1954     }
1956     /**
1957      * Returns the proper SQL to know if one field is not empty.
1958      *
1959      * Note that the function behavior strongly relies on the
1960      * parameters passed describing the field so, please,  be accurate
1961      * when specifying them.
1962      *
1963      * This function should be applied in all the places where conditions of
1964      * the type:
1965      *
1966      *     ... AND fieldname != '';
1967      *
1968      * are being used. Final result should be:
1969      *
1970      *     ... AND ' . sql_isnotempty('tablename', 'fieldname', true/false, true/false);
1971      *
1972      * (see parameters description below)
1973      *
1974      * @param string $tablename name of the table (without prefix). Not used for now but can be
1975      *                          necessary in the future if we want to use some introspection using
1976      *                          meta information against the DB. /// TODO ///
1977      * @param string $fieldname name of the field we are going to check
1978      * @param boolean $nullablefield to specify if the field us nullable (true) or no (false) in the DB
1979      * @param boolean $textfield to specify if it is a text (also called clob) field (true) or a varchar one (false)
1980      * @return string the sql code to be added to check for non empty values
1981      */
1982     public function sql_isnotempty($tablename, $fieldname, $nullablefield, $textfield) {
1983         return ' ( NOT ' . $this->sql_isempty($tablename, $fieldname, $nullablefield, $textfield) . ') ';
1984     }
1986     /**
1987      * Does this driver suppoer regex syntax when searching
1988      * @return bool
1989      */
1990     public function sql_regex_supported() {
1991         return false;
1992     }
1994     /**
1995      * Return regex positive or negative match sql
1996      * @param bool $positivematch
1997      * @return string or empty if not supported
1998      */
1999     public function sql_regex($positivematch=true) {
2000         return '';
2001     }
2003 /// transactions
2005     /**
2006      * Are transactions supported?
2007      * It is not responsible to run productions servers
2008      * on databases without transaction support ;-)
2009      *
2010      * Override in driver if needed.
2011      *
2012      * @return bool
2013      */
2014     protected function transactions_supported() {
2015         // protected for now, this might be changed to public if really necessary
2016         return true;
2017     }
2019     /**
2020      * Returns true if transaction in progress
2021      * @return bool
2022      */
2023     public function is_transaction_started() {
2024         return !empty($this->transactions);
2025     }
2027     /**
2028      * Throws exception if transaction in progress.
2029      * This test does not force rollback of active transactions.
2030      * @return void
2031      */
2032     public function transactions_forbidden() {
2033         if ($this->is_transaction_started()) {
2034             throw new dml_transaction_exception('This code can not be excecuted in transaction');
2035         }
2036     }
2038     /**
2039      * On DBs that support it, switch to transaction mode and begin a transaction
2040      * you'll need to ensure you call allow_commit() on the returned object
2041      * or your changes *will* be lost.
2042      *
2043      * this is _very_ useful for massive updates
2044      *
2045      * Delegated database transactions can be nested, but only one actual database
2046      * transaction is used for the outer-most delegated transaction. This method
2047      * returns a transaction object which you should keep until the end of the
2048      * delegated transaction. The actual database transaction will
2049      * only be committed if all the nested delegated transactions commit
2050      * successfully. If any part of the transaction rolls back then the whole
2051      * thing is rolled back.
2052      *
2053      * @return moodle_transaction
2054      */
2055     public function start_delegated_transaction() {
2056         $transaction = new moodle_transaction($this);
2057         $this->transactions[] = $transaction;
2058         if (count($this->transactions) == 1) {
2059             $this->begin_transaction();
2060         }
2061         return $transaction;
2062     }
2064     /**
2065      * Driver specific start of real database transaction,
2066      * this can not be used directly in code.
2067      * @return void
2068      */
2069     protected abstract function begin_transaction();
2071     /**
2072      * Indicates delegated transaction finished successfully.
2073      * The real database transaction is committed only if
2074      * all delegated transactions committed.
2075      * @return void
2076      */
2077     public function commit_delegated_transaction(moodle_transaction $transaction) {
2078         if ($transaction->is_disposed()) {
2079             throw new dml_transaction_exception('Transactions already disposed', $transaction);
2080         }
2081         // mark as disposed so that it can not be used again
2082         $transaction->dispose();
2084         if (empty($this->transactions)) {
2085             throw new dml_transaction_exception('Transaction not started', $transaction);
2086         }
2088         if ($this->force_rollback) {
2089             throw new dml_transaction_exception('Tried to commit transaction after lower level rollback', $transaction);
2090         }
2092         if ($transaction !== $this->transactions[count($this->transactions) - 1]) {
2093             // one incorrect commit at any level rollbacks everything
2094             $this->force_rollback = true;
2095             throw new dml_transaction_exception('Invalid transaction commit attempt', $transaction);
2096         }
2098         if (count($this->transactions) == 1) {
2099             // only commit the top most level
2100             $this->commit_transaction();
2101         }
2102         array_pop($this->transactions);
2103     }
2105     /**
2106      * Driver specific commit of real database transaction,
2107      * this can not be used directly in code.
2108      * @return void
2109      */
2110     protected abstract function commit_transaction();
2112     /**
2113      * Call when delegated transaction failed, this rolls back
2114      * all delegated transactions up to the top most level.
2115      *
2116      * In many cases you do not need to call this method manually,
2117      * because all open delegated transactions are rolled back
2118      * automatically if exceptions not caught.
2119      *
2120      * @param moodle_transaction $transaction
2121      * @param Exception $e exception that caused the problem
2122      * @return void - does not return, exception is rethrown
2123      */
2124     public function rollback_delegated_transaction(moodle_transaction $transaction, Exception $e) {
2125         if ($transaction->is_disposed()) {
2126             throw new dml_transaction_exception('Transactions already disposed', $transaction);
2127         }
2128         // mark as disposed so that it can not be used again
2129         $transaction->dispose();
2131         // one rollback at any level rollbacks everything
2132         $this->force_rollback = true;
2134         if (empty($this->transactions) or $transaction !== $this->transactions[count($this->transactions) - 1]) {
2135             // this may or may not be a coding problem, better just rethrow the exception,
2136             // because we do not want to loose the original $e
2137             throw $e;
2138         }
2140         if (count($this->transactions) == 1) {
2141             // only rollback the top most level
2142             $this->rollback_transaction();
2143         }
2144         array_pop($this->transactions);
2145         if (empty($this->transactions)) {
2146             // finally top most level rolled back
2147             $this->force_rollback = false;
2148         }
2149         throw $e;
2150     }
2152     /**
2153      * Driver specific abort of real database transaction,
2154      * this can not be used directly in code.
2155      * @return void
2156      */
2157     protected abstract function rollback_transaction();
2159     /**
2160      * Force rollback of all delegated transaction.
2161      * Does not trow any exceptions and does not log anything.
2162      *
2163      * This method should be used only from default exception handlers and other
2164      * core code.
2165      *
2166      * @return void
2167      */
2168     public function force_transaction_rollback() {
2169         if ($this->transactions) {
2170             try {
2171                 $this->rollback_transaction();
2172             } catch (dml_exception $e) {
2173                 // ignore any sql errors here, the connection might be broken
2174             }
2175         }
2177         // now enable transactions again
2178         $this->transactions = array(); // unfortunately all unfinished exceptions are kept in memory
2179         $this->force_rollback = false;
2180     }
2182 /// session locking
2183     /**
2184      * Is session lock supported in this driver?
2185      * @return bool
2186      */
2187     public function session_lock_supported() {
2188         return false;
2189     }
2191     /**
2192      * Obtain session lock
2193      * @param int $rowid id of the row with session record
2194      * @return bool success
2195      */
2196     public function get_session_lock($rowid) {
2197         $this->used_for_db_sessions = true;
2198     }
2200     /**
2201      * Release session lock
2202      * @param int $rowid id of the row with session record
2203      * @return bool success
2204      */
2205     public function release_session_lock($rowid) {
2206     }
2208 /// performance and logging
2209     /**
2210      * Returns number of reads done by this database
2211      * @return int
2212      */
2213     public function perf_get_reads() {
2214         return $this->reads;
2215     }
2217     /**
2218      * Returns number of writes done by this database
2219      * @return int
2220      */
2221     public function perf_get_writes() {
2222         return $this->writes;
2223     }
2225     /**
2226      * Returns number of queries done by this database
2227      * @return int
2228      */
2229     public function perf_get_queries() {
2230         return $this->writes + $this->reads;
2231     }