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