3 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
20 * Native mysqli class representing moodle database interface.
24 * @copyright 2008 Petr Skoda (http://skodak.org)
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28 require_once($CFG->libdir.'/dml/moodle_database.php');
29 require_once($CFG->libdir.'/dml/mysqli_native_moodle_recordset.php');
30 require_once($CFG->libdir.'/dml/mysqli_native_moodle_temptables.php');
33 * Native mysqli class representing moodle database interface.
35 class mysqli_native_moodle_database extends moodle_database {
37 protected $mysqli = null;
38 private $temptables; // Control existing temptables (mysql_moodle_temptables object)
41 * Attempt to create the database
42 * @param string $dbhost
43 * @param string $dbuser
44 * @param string $dbpass
45 * @param string $dbname
46 * @return bool success
47 * @throws dml_exception if error
49 public function create_database($dbhost, $dbuser, $dbpass, $dbname, array $dboptions=null) {
50 $driverstatus = $this->driver_installed();
52 if ($driverstatus !== true) {
53 throw new dml_exception('dbdriverproblem', $driverstatus);
57 $conn = new mysqli($dbhost, $dbuser, $dbpass); /// Connect without db
58 $dberr = ob_get_contents();
60 $errorno = @$conn->connect_errno;
63 throw new dml_connection_exception($dberr);
66 $result = $conn->query("CREATE DATABASE $dbname DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci");
71 throw new dml_exception('cannotcreatedb');
78 * Detects if all needed PHP stuff installed.
79 * Note: can be used before connect()
80 * @return mixed true if ok, string if something
82 public function driver_installed() {
83 if (!extension_loaded('mysqli')) {
84 return get_string('mysqliextensionisnotpresentinphp', 'install');
90 * Returns database family type - describes SQL dialect
91 * Note: can be used before connect()
92 * @return string db family name (mysql, postgres, mssql, oracle, etc.)
94 public function get_dbfamily() {
99 * Returns more specific database driver type
100 * Note: can be used before connect()
101 * @return string db type mysql, mysqli, postgres7
103 protected function get_dbtype() {
108 * Returns general database library name
109 * Note: can be used before connect()
110 * @return string db type pdo, native
112 protected function get_dblibrary() {
117 * Returns localised database type name
118 * Note: can be used before connect()
121 public function get_name() {
122 return get_string('nativemysqli', 'install');
126 * Returns sql generator used for db manipulation.
127 * Used mostly in upgrade.php scripts. mysql overrides it
128 * in order to share the mysqli_native_moodle_temptables
129 * between the driver and the generator
131 * @return object database_manager instance
133 public function get_manager() {
136 if (!$this->database_manager) {
137 require_once($CFG->libdir.'/ddllib.php');
139 $classname = $this->get_dbfamily().'_sql_generator';
140 require_once("$CFG->libdir/ddl/$classname.php");
141 $generator = new $classname($this, $this->temptables);
143 $this->database_manager = new database_manager($this, $generator);
145 return $this->database_manager;
149 * Returns localised database configuration help.
150 * Note: can be used before connect()
153 public function get_configuration_help() {
154 return get_string('nativemysqlihelp', 'install');
158 * Returns localised database description
159 * Note: can be used before connect()
162 public function get_configuration_hints() {
163 return get_string('databasesettingssub_mysqli', 'install');
168 * Must be called before other methods.
169 * @param string $dbhost
170 * @param string $dbuser
171 * @param string $dbpass
172 * @param string $dbname
173 * @param mixed $prefix string means moodle db prefix, false used for external databases where prefix not used
174 * @param array $dboptions driver specific options
175 * @return bool success
177 public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) {
178 $driverstatus = $this->driver_installed();
180 if ($driverstatus !== true) {
181 throw new dml_exception('dbdriverproblem', $driverstatus);
184 $this->store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions);
185 unset($this->dboptions['dbsocket']);
188 $this->mysqli = new mysqli($dbhost, $dbuser, $dbpass, $dbname);
189 $dberr = ob_get_contents();
191 $errorno = @$this->mysqli->connect_errno;
193 if ($errorno !== 0) {
194 throw new dml_connection_exception($dberr);
197 $this->query_start("--set_charset()", null, SQL_QUERY_AUX);
198 $this->mysqli->set_charset('utf8');
199 $this->query_end(true);
201 // If available, enforce strict mode for the session. That guaranties
202 // standard behaviour under some situations, avoiding some MySQL nasty
203 // habits like truncating data or performing some transparent cast losses.
204 // With strict mode enforced, Moodle DB layer will be consistenly throwing
205 // the corresponding exceptions as expected.
206 $si = $this->get_server_info();
207 if (version_compare($si['version'], '5.0.2', '>=')) {
208 $sql = "SET SESSION sql_mode = 'STRICT_ALL_TABLES'";
209 $this->query_start($sql, null, SQL_QUERY_AUX);
210 $result = $this->mysqli->query($sql);
211 $this->query_end($result);
214 // Connection stabilished and configured, going to instantiate the temptables controller
215 $this->temptables = new mysqli_native_moodle_temptables($this);
221 * Close database connection and release all resources
222 * and memory (especially circular memory references).
223 * Do NOT use connect() again, create a new instance if needed.
225 public function dispose() {
226 parent::dispose(); // Call parent dispose to write/close session and other common stuff before clossing conn
228 $this->mysqli->close();
229 $this->mysqli = null;
234 * Returns database server info array
237 public function get_server_info() {
238 return array('description'=>$this->mysqli->server_info, 'version'=>$this->mysqli->server_info);
242 * Returns supported query parameter types
245 protected function allowed_param_types() {
246 return SQL_PARAMS_QM;
250 * Returns last error reported by database engine.
252 public function get_last_error() {
253 return $this->mysqli->error;
257 * Return tables in database WITHOUT current prefix
258 * @return array of table names in lowercase and without prefix
260 public function get_tables($usecache=true) {
261 if ($usecache and $this->tables !== null) {
262 return $this->tables;
264 $this->tables = array();
265 $sql = "SHOW TABLES";
266 $this->query_start($sql, null, SQL_QUERY_AUX);
267 $result = $this->mysqli->query($sql);
268 $this->query_end($result);
270 while ($arr = $result->fetch_assoc()) {
271 $tablename = reset($arr);
272 if ($this->prefix !== '') {
273 if (strpos($tablename, $this->prefix) !== 0) {
276 $tablename = substr($tablename, strlen($this->prefix));
278 $this->tables[$tablename] = $tablename;
283 // Add the currently available temptables
284 $this->tables = array_merge($this->tables, $this->temptables->get_temptables());
285 return $this->tables;
289 * Return table indexes - everything lowercased
290 * @return array of arrays
292 public function get_indexes($table) {
294 $sql = "SHOW INDEXES FROM {$this->prefix}$table";
295 $this->query_start($sql, null, SQL_QUERY_AUX);
296 $result = $this->mysqli->query($sql);
297 $this->query_end($result);
299 while ($res = $result->fetch_object()) {
300 if ($res->Key_name === 'PRIMARY') {
303 if (!isset($indexes[$res->Key_name])) {
304 $indexes[$res->Key_name] = array('unique'=>empty($res->Non_unique), 'columns'=>array());
306 $indexes[$res->Key_name]['columns'][$res->Seq_in_index-1] = $res->Column_name;
314 * Returns datailed information about columns in table. This information is cached internally.
315 * @param string $table name
316 * @param bool $usecache
317 * @return array array of database_column_info objects indexed with column names
319 public function get_columns($table, $usecache=true) {
320 if ($usecache and isset($this->columns[$table])) {
321 return $this->columns[$table];
324 $this->columns[$table] = array();
326 $sql = "SHOW COLUMNS FROM {$this->prefix}$table";
327 $this->query_start($sql, null, SQL_QUERY_AUX);
328 $result = $this->mysqli->query($sql);
329 $this->query_end($result);
331 if ($result === false) {
335 while ($rawcolumn = $result->fetch_assoc()) {
336 $rawcolumn = (object)array_change_key_case($rawcolumn, CASE_LOWER);
338 $info = new object();
339 $info->name = $rawcolumn->field;
342 if (preg_match('/varchar\((\d+)\)/i', $rawcolumn->type, $matches)) {
343 $info->type = 'varchar';
344 $info->meta_type = 'C';
345 $info->max_length = $matches[1];
347 $info->not_null = ($rawcolumn->null === 'NO');
348 $info->default_value = $rawcolumn->default;
349 $info->has_default = is_null($info->default_value) ? false : true;
350 $info->primary_key = ($rawcolumn->key === 'PRI');
351 $info->binary = false;
352 $info->unsigned = null;
353 $info->auto_increment= false;
354 $info->unique = null;
356 } else if (preg_match('/([a-z]*int[a-z]*)\((\d+)\)/i', $rawcolumn->type, $matches)) {
357 $info->type = $matches[1];
358 $info->primary_key = ($rawcolumn->key === 'PRI');
359 if ($info->primary_key) {
360 $info->meta_type = 'R';
361 $info->max_length = $matches[2];
363 $info->not_null = ($rawcolumn->null === 'NO');
364 $info->default_value = $rawcolumn->default;
365 $info->has_default = is_null($info->default_value) ? false : true;
366 $info->binary = false;
367 $info->unsigned = (stripos($rawcolumn->type, 'unsigned') !== false);
368 $info->auto_increment= true;
369 $info->unique = true;
371 $info->meta_type = 'I';
372 $info->max_length = $matches[2];
374 $info->not_null = ($rawcolumn->null === 'NO');
375 $info->default_value = $rawcolumn->default;
376 $info->has_default = is_null($info->default_value) ? false : true;
377 $info->binary = false;
378 $info->unsigned = (stripos($rawcolumn->type, 'unsigned') !== false);
379 $info->auto_increment= false;
380 $info->unique = null;
383 } else if (preg_match('/(decimal|double|float)\((\d+),(\d+)\)/i', $rawcolumn->type, $matches)) {
384 $info->type = $matches[1];
385 $info->meta_type = 'N';
386 $info->max_length = $matches[2];
387 $info->scale = $matches[3];
388 $info->not_null = ($rawcolumn->null === 'NO');
389 $info->default_value = $rawcolumn->default;
390 $info->has_default = is_null($info->default_value) ? false : true;
391 $info->primary_key = ($rawcolumn->key === 'PRI');
392 $info->binary = false;
393 $info->unsigned = null;
394 $info->auto_increment= false;
395 $info->unique = null;
397 } else if (preg_match('/([a-z]*text)/i', $rawcolumn->type, $matches)) {
398 $info->type = $matches[1];
399 $info->meta_type = 'X';
400 $info->max_length = -1;
402 $info->not_null = ($rawcolumn->null === 'NO');
403 $info->default_value = $rawcolumn->default;
404 $info->has_default = is_null($info->default_value) ? false : true;
405 $info->primary_key = ($rawcolumn->key === 'PRI');
406 $info->binary = false;
407 $info->unsigned = null;
408 $info->auto_increment= false;
409 $info->unique = null;
411 } else if (preg_match('/([a-z]*blob)/i', $rawcolumn->type, $matches)) {
412 $info->type = $matches[1];
413 $info->meta_type = 'B';
414 $info->max_length = -1;
416 $info->not_null = ($rawcolumn->null === 'NO');
417 $info->default_value = $rawcolumn->default;
418 $info->has_default = is_null($info->default_value) ? false : true;
419 $info->primary_key = false;
420 $info->binary = true;
421 $info->unsigned = null;
422 $info->auto_increment= false;
423 $info->unique = null;
425 } else if (preg_match('/enum\((.*)\)/i', $rawcolumn->type, $matches)) {
426 $info->type = 'enum';
427 $info->meta_type = 'C';
428 $info->enums = array();
429 $info->max_length = 0;
430 $values = $matches[1];
431 $values = explode(',', $values);
432 $textlib = textlib_get_instance();
433 foreach ($values as $val) {
434 $val = trim($val, "'");
435 $length = $textlib->strlen($val);
436 $info->enums[] = $val;
437 $info->max_length = ($info->max_length < $length) ? $length : $info->max_length;
440 $info->not_null = ($rawcolumn->null === 'NO');
441 $info->default_value = $rawcolumn->default;
442 $info->has_default = is_null($info->default_value) ? false : true;
443 $info->primary_key = ($rawcolumn->key === 'PRI');
444 $info->binary = false;
445 $info->unsigned = null;
446 $info->auto_increment= false;
447 $info->unique = null;
450 $this->columns[$table][$info->name] = new database_column_info($info);
455 return $this->columns[$table];
459 * Normalise values based in RDBMS dependencies (booleans, LOBs...)
461 * @param database_column_info $column column metadata corresponding with the value we are going to normalise
462 * @param mixed $value value we are going to normalise
463 * @return mixed the normalised value
465 protected function normalise_value($column, $value) {
466 if (is_bool($value)) { // Always, convert boolean to int
467 $value = (int)$value;
469 } else if ($value === '') {
470 if ($column->meta_type == 'I' or $column->meta_type == 'F' or $column->meta_type == 'N') {
471 $value = 0; // prevent '' problems in numeric fields
474 // workaround for problem with wrong enums in mysql - TODO: Out in Moodle 2.1
475 if (!empty($column->enums)) {
476 if (is_null($value) and !$column->not_null) {
477 // ok - nulls allowed
479 if (!in_array((string)$value, $column->enums)) {
480 throw new dml_write_exception('Enum value '.s($value).' not allowed in field '.$field.' table '.$table.'.');
488 * Is db in unicode mode?
491 public function setup_is_unicodedb() {
492 $sql = "SHOW LOCAL VARIABLES LIKE 'character_set_database'";
493 $this->query_start($sql, null, SQL_QUERY_AUX);
494 $result = $this->mysqli->query($sql);
495 $this->query_end($result);
505 * Do NOT use in code, to be used by database_manager only!
506 * @param string $sql query
508 * @throws dml_exception if error
510 public function change_database_structure($sql) {
511 $this->reset_caches();
513 $this->query_start($sql, null, SQL_QUERY_STRUCTURE);
514 $result = $this->mysqli->query($sql);
515 $this->query_end($result);
521 * Very ugly hack which emulates bound parameters in queries
522 * because prepared statements do not use query cache.
524 protected function emulate_bound_params($sql, array $params=null) {
525 if (empty($params)) {
528 /// ok, we have verified sql statement with ? and correct number of params
529 $return = strtok($sql, '?');
530 foreach ($params as $param) {
531 if (is_bool($param)) {
532 $return .= (int)$param;
533 } else if (is_null($param)) {
535 } else if (is_number($param)) { // we can not use is_numeric() because it eats leading zeros from strings like 0045646
537 } else if (is_float($param)) {
540 $param = $this->mysqli->real_escape_string($param);
541 $return .= "'$param'";
543 $return .= strtok('?');
549 * Execute general sql query. Should be used only when no other method suitable.
550 * Do NOT use this to make changes in db structure, use database_manager::execute_sql() instead!
551 * @param string $sql query
552 * @param array $params query parameters
554 * @throws dml_exception if error
556 public function execute($sql, array $params=null) {
557 list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
559 if (strpos($sql, ';') !== false) {
560 throw new coding_exception('moodle_database::execute() Multiple sql statements found or bound parameters not used properly in query!');
563 $rawsql = $this->emulate_bound_params($sql, $params);
565 $this->query_start($sql, $params, SQL_QUERY_UPDATE);
566 $result = $this->mysqli->query($rawsql);
567 $this->query_end($result);
569 if ($result === true) {
579 * Get a number of records as a moodle_recordset using a SQL statement.
581 * Since this method is a little less readable, use of it should be restricted to
582 * code where it's possible there might be large datasets being returned. For known
583 * small datasets use get_records_sql - it leads to simpler code.
585 * The return type is as for @see function get_recordset.
587 * @param string $sql the SQL select query to execute.
588 * @param array $params array of sql parameters
589 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
590 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
591 * @return mixed an moodle_recordset object
592 * @throws dml_exception if error
594 public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
595 $limitfrom = (int)$limitfrom;
596 $limitnum = (int)$limitnum;
597 $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom;
598 $limitnum = ($limitnum < 0) ? 0 : $limitnum;
600 if ($limitfrom or $limitnum) {
602 $limitnum = "18446744073709551615";
604 $sql .= " LIMIT $limitfrom, $limitnum";
607 list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
608 $rawsql = $this->emulate_bound_params($sql, $params);
610 $this->query_start($sql, $params, SQL_QUERY_SELECT);
611 // no MYSQLI_USE_RESULT here, it would block write ops on affected tables
612 $result = $this->mysqli->query($rawsql, MYSQLI_STORE_RESULT);
613 $this->query_end($result);
615 return $this->create_recordset($result);
618 protected function create_recordset($result) {
619 return new mysqli_native_moodle_recordset($result);
623 * Get a number of records as an array of objects using a SQL statement.
625 * Return value as for @see function get_records.
627 * @param string $sql the SQL select query to execute. The first column of this SELECT statement
628 * must be a unique value (usually the 'id' field), as it will be used as the key of the
630 * @param array $params array of sql parameters
631 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
632 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
633 * @return mixed an array of objects, or empty array if no records were found
634 * @throws dml_exception if error
636 public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
637 $limitfrom = (int)$limitfrom;
638 $limitnum = (int)$limitnum;
639 $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom;
640 $limitnum = ($limitnum < 0) ? 0 : $limitnum;
642 if ($limitfrom or $limitnum) {
644 $limitnum = "18446744073709551615";
646 $sql .= " LIMIT $limitfrom, $limitnum";
649 list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
650 $rawsql = $this->emulate_bound_params($sql, $params);
652 $this->query_start($sql, $params, SQL_QUERY_SELECT);
653 $result = $this->mysqli->query($rawsql, MYSQLI_STORE_RESULT);
654 $this->query_end($result);
658 while($row = $result->fetch_assoc()) {
659 $row = array_change_key_case($row, CASE_LOWER);
661 if (isset($return[$id])) {
662 $colname = key($row);
663 debugging("Did you remember to make the first column something unique in your call to get_records? Duplicate value '$id' found in column '$colname'.", DEBUG_DEVELOPER);
665 $return[$id] = (object)$row;
673 * Selects records and return values (first field) as an array using a SQL statement.
675 * @param string $sql The SQL query
676 * @param array $params array of sql parameters
677 * @return mixed array of values
678 * @throws dml_exception if error
680 public function get_fieldset_sql($sql, array $params=null) {
681 list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
682 $rawsql = $this->emulate_bound_params($sql, $params);
684 $this->query_start($sql, $params, SQL_QUERY_SELECT);
685 $result = $this->mysqli->query($rawsql, MYSQLI_STORE_RESULT);
686 $this->query_end($result);
690 while($row = $result->fetch_assoc()) {
691 $return[] = reset($row);
699 * Insert new record into database, as fast as possible, no safety checks, lobs not supported.
700 * @param string $table name
701 * @param mixed $params data record as object or array
702 * @param bool $returnit return it of inserted record
703 * @param bool $bulk true means repeated inserts expected
704 * @param bool $customsequence true if 'id' included in $params, disables $returnid
705 * @return true or new id
706 * @throws dml_exception if error
708 public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) {
709 if (!is_array($params)) {
710 $params = (array)$params;
713 if ($customsequence) {
714 if (!isset($params['id'])) {
715 throw new coding_exception('moodle_database::insert_record_raw() id field must be specified if custom sequences used.');
719 unset($params['id']);
722 if (empty($params)) {
723 throw new coding_exception('moodle_database::insert_record_raw() no fields found.');
726 $fields = implode(',', array_keys($params));
727 $qms = array_fill(0, count($params), '?');
728 $qms = implode(',', $qms);
730 $sql = "INSERT INTO {$this->prefix}$table ($fields) VALUES($qms)";
732 list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
733 $rawsql = $this->emulate_bound_params($sql, $params);
735 $this->query_start($sql, $params, SQL_QUERY_INSERT);
736 $result = $this->mysqli->query($rawsql);
737 $id = @$this->mysqli->insert_id; // must be called before query_end() which may insert log into db
738 $this->query_end($result);
741 throw new dml_write_exception('unknown error fetching inserted id');
752 * Insert a record into a table and return the "id" field if required.
754 * Some conversions and safety checks are carried out. Lobs are supported.
755 * If the return ID isn't required, then this just reports success as true/false.
756 * $data is an object containing needed data
757 * @param string $table The database table to be inserted into
758 * @param object $data A data object with values for one or more fields in the record
759 * @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.
760 * @return true or new id
761 * @throws dml_exception if error
763 public function insert_record($table, $dataobject, $returnid=true, $bulk=false) {
764 if (!is_object($dataobject)) {
765 $dataobject = (object)$dataobject;
768 $columns = $this->get_columns($table);
770 unset($dataobject->id);
773 foreach ($dataobject as $field=>$value) {
774 if (!isset($columns[$field])) {
777 $column = $columns[$field];
778 $cleaned[$field] = $this->normalise_value($column, $value);
781 return $this->insert_record_raw($table, $cleaned, $returnid, $bulk);
785 * Import a record into a table, id field is required.
786 * Safety checks are NOT carried out. Lobs are supported.
788 * @param string $table name of database table to be inserted into
789 * @param object $dataobject A data object with values for one or more fields in the record
791 * @throws dml_exception if error
793 public function import_record($table, $dataobject) {
794 $dataobject = (object)$dataobject;
796 $columns = $this->get_columns($table);
799 foreach ($dataobject as $field=>$value) {
800 if (!isset($columns[$field])) {
803 $cleaned[$field] = $value;
806 return $this->insert_record_raw($table, $cleaned, false, true, true);
810 * Update record in database, as fast as possible, no safety checks, lobs not supported.
811 * @param string $table name
812 * @param mixed $params data record as object or array
813 * @param bool true means repeated updates expected
815 * @throws dml_exception if error
817 public function update_record_raw($table, $params, $bulk=false) {
818 if (!is_array($params)) {
819 $params = (array)$params;
821 if (!isset($params['id'])) {
822 throw new coding_exception('moodle_database::update_record_raw() id field must be specified.');
825 unset($params['id']);
827 if (empty($params)) {
828 throw new coding_exception('moodle_database::update_record_raw() no fields found.');
832 foreach ($params as $field=>$value) {
833 $sets[] = "$field = ?";
836 $params[] = $id; // last ? in WHERE condition
838 $sets = implode(',', $sets);
839 $sql = "UPDATE {$this->prefix}$table SET $sets WHERE id=?";
841 list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
842 $rawsql = $this->emulate_bound_params($sql, $params);
844 $this->query_start($sql, $params, SQL_QUERY_UPDATE);
845 $result = $this->mysqli->query($rawsql);
846 $this->query_end($result);
852 * Update a record in a table
854 * $dataobject is an object containing needed data
855 * Relies on $dataobject having a variable "id" to
856 * specify the record to update
858 * @param string $table The database table to be checked against.
859 * @param object $dataobject An object with contents equal to fieldname=>fieldvalue. Must have an entry for 'id' to map to the table specified.
860 * @param bool true means repeated updates expected
862 * @throws dml_exception if error
864 public function update_record($table, $dataobject, $bulk=false) {
865 if (!is_object($dataobject)) {
866 $dataobject = (object)$dataobject;
869 $columns = $this->get_columns($table);
872 foreach ($dataobject as $field=>$value) {
873 if (!isset($columns[$field])) {
876 $column = $columns[$field];
877 $cleaned[$field] = $this->normalise_value($column, $value);
880 return $this->update_record_raw($table, $cleaned, $bulk);
884 * Set a single field in every table record which match a particular WHERE clause.
886 * @param string $table The database table to be checked against.
887 * @param string $newfield the field to set.
888 * @param string $newvalue the value to set the field to.
889 * @param string $select A fragment of SQL to be used in a where clause in the SQL call.
890 * @param array $params array of sql parameters
892 * @throws dml_exception if error
894 public function set_field_select($table, $newfield, $newvalue, $select, array $params=null) {
896 $select = "WHERE $select";
898 if (is_null($params)) {
901 list($select, $params, $type) = $this->fix_sql_params($select, $params);
903 // Get column metadata
904 $columns = $this->get_columns($table);
905 $column = $columns[$newfield];
907 $normalised_value = $this->normalise_value($column, $newvalue);
909 if (is_null($normalised_value)) {
910 $newfield = "$newfield = NULL";
912 $newfield = "$newfield = ?";
913 array_unshift($params, $normalised_value);
915 $sql = "UPDATE {$this->prefix}$table SET $newfield $select";
916 $rawsql = $this->emulate_bound_params($sql, $params);
918 $this->query_start($sql, $params, SQL_QUERY_UPDATE);
919 $result = $this->mysqli->query($rawsql);
920 $this->query_end($result);
926 * Delete one or more records from a table which match a particular WHERE clause.
928 * @param string $table The database table to be checked against.
929 * @param string $select A fragment of SQL to be used in a where clause in the SQL call (used to define the selection criteria).
930 * @param array $params array of sql parameters
932 * @throws dml_exception if error
934 public function delete_records_select($table, $select, array $params=null) {
936 $select = "WHERE $select";
938 $sql = "DELETE FROM {$this->prefix}$table $select";
940 list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
941 $rawsql = $this->emulate_bound_params($sql, $params);
943 $this->query_start($sql, $params, SQL_QUERY_UPDATE);
944 $result = $this->mysqli->query($rawsql);
945 $this->query_end($result);
950 public function sql_cast_char2int($fieldname, $text=false) {
951 return ' CAST(' . $fieldname . ' AS SIGNED) ';
954 public function sql_concat() {
955 $arr = func_get_args();
956 $s = implode(', ', $arr);
963 public function sql_concat_join($separator="' '", $elements=array()) {
964 $s = implode(', ', $elements);
969 return "CONCAT_WS($separator, $s)";
973 * Returns the SQL text to be used to calculate the length in characters of one expression.
974 * @param string fieldname or expression to calculate its length in characters.
975 * @return string the piece of SQL code to be used in the statement.
977 public function sql_length($fieldname) {
978 return ' CHAR_LENGTH(' . $fieldname . ')';
982 * Does this driver suppoer regex syntax when searching
984 public function sql_regex_supported() {
989 * Return regex positive or negative match sql
990 * @param bool $positivematch
991 * @return string or empty if not supported
993 public function sql_regex($positivematch=true) {
994 return $positivematch ? 'REGEXP' : 'NOT REGEXP';
997 public function sql_cast_2signed($fieldname) {
998 return ' CAST(' . $fieldname . ' AS SIGNED) ';
1002 public function session_lock_supported() {
1006 public function get_session_lock($rowid) {
1007 parent::get_session_lock($rowid);
1008 $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
1009 $sql = "SELECT GET_LOCK('$fullname',120)";
1010 $this->query_start($sql, null, SQL_QUERY_AUX);
1011 $result = $this->mysqli->query($sql);
1012 $this->query_end($result);
1015 $arr = $result->fetch_assoc();
1018 if (reset($arr) == 1) {
1022 $this->get_session_lock($rowid);
1027 public function release_session_lock($rowid) {
1028 parent::release_session_lock($rowid);
1029 $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid;
1030 $sql = "SELECT RELEASE_LOCK('$fullname')";
1031 $this->query_start($sql, null, SQL_QUERY_AUX);
1032 $result = $this->mysqli->query($sql);
1033 $this->query_end($result);
1042 * on DBs that support it, switch to transaction mode and begin a transaction
1043 * you'll need to ensure you call commit_sql() or your changes *will* be lost.
1045 * this is _very_ useful for massive updates
1047 public function begin_sql() {
1048 // Only will accept transactions if using InnoDB storage engine (more engines can be added easily BDB, Falcon...)
1049 $sql = "SELECT @@storage_engine";
1050 $this->query_start($sql, NULL, SQL_QUERY_AUX);
1051 $result = $this->mysqli->query($sql);
1052 $this->query_end($result);
1053 if ($rec = $result->fetch_assoc()) {
1054 if (!in_array($rec['@@storage_engine'], array('InnoDB'))) {
1062 if (!parent::begin_sql()) {
1066 $sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED";
1067 $this->query_start($sql, NULL, SQL_QUERY_AUX);
1068 $result = $this->mysqli->query($sql);
1069 $this->query_end($result);
1071 $sql = "START TRANSACTION";
1072 $this->query_start($sql, NULL, SQL_QUERY_AUX);
1073 $result = $this->mysqli->query($sql);
1074 $this->query_end($result);
1080 * on DBs that support it, commit the transaction
1082 public function commit_sql() {
1083 if (!parent::commit_sql()) {
1087 $this->query_start($sql, NULL, SQL_QUERY_AUX);
1088 $result = $this->mysqli->query($sql);
1089 $this->query_end($result);
1095 * on DBs that support it, rollback the transaction
1097 public function rollback_sql() {
1098 if (!parent::rollback_sql()) {
1102 $this->query_start($sql, NULL, SQL_QUERY_AUX);
1103 $result = $this->mysqli->query($sql);
1104 $this->query_end($result);