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