|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00018 00028 defined('MOODLE_INTERNAL') || die(); 00029 00030 require_once($CFG->libdir.'/dml/moodle_database.php'); 00031 require_once($CFG->libdir.'/dml/mysqli_native_moodle_recordset.php'); 00032 require_once($CFG->libdir.'/dml/mysqli_native_moodle_temptables.php'); 00033 00037 class mysqli_native_moodle_database extends moodle_database { 00038 00039 protected $mysqli = null; 00040 00041 private $transactions_supported = null; 00042 00052 public function create_database($dbhost, $dbuser, $dbpass, $dbname, array $dboptions=null) { 00053 $driverstatus = $this->driver_installed(); 00054 00055 if ($driverstatus !== true) { 00056 throw new dml_exception('dbdriverproblem', $driverstatus); 00057 } 00058 00059 if (!empty($dboptions['dbsocket']) 00060 and (strpos($dboptions['dbsocket'], '/') !== false or strpos($dboptions['dbsocket'], '\\') !== false)) { 00061 $dbsocket = $dboptions['dbsocket']; 00062 } else { 00063 $dbsocket = ini_get('mysqli.default_socket'); 00064 } 00065 if (empty($dboptions['dbport'])) { 00066 $dbport = (int)ini_get('mysqli.default_port'); 00067 } else { 00068 $dbport = (int)$dboptions['dbport']; 00069 } 00070 // verify ini.get does not return nonsense 00071 if (empty($dbport)) { 00072 $dbport = 3306; 00073 } 00074 ob_start(); 00075 $conn = new mysqli($dbhost, $dbuser, $dbpass, '', $dbport, $dbsocket); 00076 $dberr = ob_get_contents(); 00077 ob_end_clean(); 00078 $errorno = @$conn->connect_errno; 00079 00080 if ($errorno !== 0) { 00081 throw new dml_connection_exception($dberr); 00082 } 00083 00084 $result = $conn->query("CREATE DATABASE $dbname DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci"); 00085 00086 $conn->close(); 00087 00088 if (!$result) { 00089 throw new dml_exception('cannotcreatedb'); 00090 } 00091 00092 return true; 00093 } 00094 00100 public function driver_installed() { 00101 if (!extension_loaded('mysqli')) { 00102 return get_string('mysqliextensionisnotpresentinphp', 'install'); 00103 } 00104 return true; 00105 } 00106 00112 public function get_dbfamily() { 00113 return 'mysql'; 00114 } 00115 00121 protected function get_dbtype() { 00122 return 'mysqli'; 00123 } 00124 00130 protected function get_dblibrary() { 00131 return 'native'; 00132 } 00133 00143 public function get_dbengine() { 00144 if (isset($this->dboptions['dbengine'])) { 00145 return $this->dboptions['dbengine']; 00146 } 00147 00148 $engine = null; 00149 00150 if (!$this->external) { 00151 // look for current engine of our config table (the first table that gets created), 00152 // so that we create all tables with the same engine 00153 $sql = "SELECT engine FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE() AND table_name = '{$this->prefix}config'"; 00154 $this->query_start($sql, NULL, SQL_QUERY_AUX); 00155 $result = $this->mysqli->query($sql); 00156 $this->query_end($result); 00157 if ($rec = $result->fetch_assoc()) { 00158 $engine = $rec['engine']; 00159 } 00160 $result->close(); 00161 } 00162 00163 if ($engine) { 00164 return $engine; 00165 } 00166 00167 // get the default database engine 00168 $sql = "SELECT @@storage_engine"; 00169 $this->query_start($sql, NULL, SQL_QUERY_AUX); 00170 $result = $this->mysqli->query($sql); 00171 $this->query_end($result); 00172 if ($rec = $result->fetch_assoc()) { 00173 $engine = $rec['@@storage_engine']; 00174 } 00175 $result->close(); 00176 00177 if (!$this->external and $engine === 'MyISAM') { 00178 // we really do not want MyISAM for Moodle, InnoDB or XtraDB is a reasonable defaults if supported 00179 $sql = "SHOW STORAGE ENGINES"; 00180 $this->query_start($sql, NULL, SQL_QUERY_AUX); 00181 $result = $this->mysqli->query($sql); 00182 $this->query_end($result); 00183 $engines = array(); 00184 while ($res = $result->fetch_assoc()) { 00185 if ($res['Support'] === 'YES' or $res['Support'] === 'DEFAULT') { 00186 $engines[$res['Engine']] = true; 00187 } 00188 } 00189 $result->close(); 00190 if (isset($engines['InnoDB'])) { 00191 $engine = 'InnoDB'; 00192 } 00193 if (isset($engines['XtraDB'])) { 00194 $engine = 'XtraDB'; 00195 } 00196 } 00197 00198 return $engine; 00199 } 00200 00206 public function get_name() { 00207 return get_string('nativemysqli', 'install'); 00208 } 00209 00215 public function get_configuration_help() { 00216 return get_string('nativemysqlihelp', 'install'); 00217 } 00218 00224 public function get_configuration_hints() { 00225 return get_string('databasesettingssub_mysqli', 'install'); 00226 } 00227 00234 public function diagnose() { 00235 $sloppymyisamfound = false; 00236 $prefix = str_replace('_', '\\_', $this->prefix); 00237 $sql = "SHOW TABLE STATUS WHERE Name LIKE BINARY '$prefix%'"; 00238 $this->query_start($sql, null, SQL_QUERY_AUX); 00239 $result = $this->mysqli->query($sql); 00240 $this->query_end($result); 00241 if ($result) { 00242 while ($arr = $result->fetch_assoc()) { 00243 if ($arr['Engine'] === 'MyISAM') { 00244 $sloppymyisamfound = true; 00245 break; 00246 } 00247 } 00248 $result->close(); 00249 } 00250 00251 if ($sloppymyisamfound) { 00252 return get_string('myisamproblem', 'error'); 00253 } else { 00254 return null; 00255 } 00256 } 00257 00269 public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) { 00270 $driverstatus = $this->driver_installed(); 00271 00272 if ($driverstatus !== true) { 00273 throw new dml_exception('dbdriverproblem', $driverstatus); 00274 } 00275 00276 $this->store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions); 00277 00278 // dbsocket is used ONLY if host is NULL or 'localhost', 00279 // you can not disable it because it is always tried if dbhost is 'localhost' 00280 if (!empty($this->dboptions['dbsocket']) 00281 and (strpos($this->dboptions['dbsocket'], '/') !== false or strpos($this->dboptions['dbsocket'], '\\') !== false)) { 00282 $dbsocket = $this->dboptions['dbsocket']; 00283 } else { 00284 $dbsocket = ini_get('mysqli.default_socket'); 00285 } 00286 if (empty($this->dboptions['dbport'])) { 00287 $dbport = (int)ini_get('mysqli.default_port'); 00288 } else { 00289 $dbport = (int)$this->dboptions['dbport']; 00290 } 00291 // verify ini.get does not return nonsense 00292 if (empty($dbport)) { 00293 $dbport = 3306; 00294 } 00295 ob_start(); 00296 $this->mysqli = new mysqli($dbhost, $dbuser, $dbpass, $dbname, $dbport, $dbsocket); 00297 $dberr = ob_get_contents(); 00298 ob_end_clean(); 00299 $errorno = @$this->mysqli->connect_errno; 00300 00301 if ($errorno !== 0) { 00302 throw new dml_connection_exception($dberr); 00303 } 00304 00305 $this->query_start("--set_charset()", null, SQL_QUERY_AUX); 00306 $this->mysqli->set_charset('utf8'); 00307 $this->query_end(true); 00308 00309 // If available, enforce strict mode for the session. That guaranties 00310 // standard behaviour under some situations, avoiding some MySQL nasty 00311 // habits like truncating data or performing some transparent cast losses. 00312 // With strict mode enforced, Moodle DB layer will be consistently throwing 00313 // the corresponding exceptions as expected. 00314 $si = $this->get_server_info(); 00315 if (version_compare($si['version'], '5.0.2', '>=')) { 00316 $sql = "SET SESSION sql_mode = 'STRICT_ALL_TABLES'"; 00317 $this->query_start($sql, null, SQL_QUERY_AUX); 00318 $result = $this->mysqli->query($sql); 00319 $this->query_end($result); 00320 } 00321 00322 // Connection stabilished and configured, going to instantiate the temptables controller 00323 $this->temptables = new mysqli_native_moodle_temptables($this); 00324 00325 return true; 00326 } 00327 00333 public function dispose() { 00334 parent::dispose(); // Call parent dispose to write/close session and other common stuff before closing connection 00335 if ($this->mysqli) { 00336 $this->mysqli->close(); 00337 $this->mysqli = null; 00338 } 00339 } 00340 00345 public function get_server_info() { 00346 return array('description'=>$this->mysqli->server_info, 'version'=>$this->mysqli->server_info); 00347 } 00348 00353 protected function allowed_param_types() { 00354 return SQL_PARAMS_QM; 00355 } 00356 00361 public function get_last_error() { 00362 return $this->mysqli->error; 00363 } 00364 00369 public function get_tables($usecache=true) { 00370 if ($usecache and $this->tables !== null) { 00371 return $this->tables; 00372 } 00373 $this->tables = array(); 00374 $sql = "SHOW TABLES"; 00375 $this->query_start($sql, null, SQL_QUERY_AUX); 00376 $result = $this->mysqli->query($sql); 00377 $this->query_end($result); 00378 if ($result) { 00379 while ($arr = $result->fetch_assoc()) { 00380 $tablename = reset($arr); 00381 if ($this->prefix !== '') { 00382 if (strpos($tablename, $this->prefix) !== 0) { 00383 continue; 00384 } 00385 $tablename = substr($tablename, strlen($this->prefix)); 00386 } 00387 $this->tables[$tablename] = $tablename; 00388 } 00389 $result->close(); 00390 } 00391 00392 // Add the currently available temptables 00393 $this->tables = array_merge($this->tables, $this->temptables->get_temptables()); 00394 return $this->tables; 00395 } 00396 00401 public function get_indexes($table) { 00402 $indexes = array(); 00403 $sql = "SHOW INDEXES FROM {$this->prefix}$table"; 00404 $this->query_start($sql, null, SQL_QUERY_AUX); 00405 $result = $this->mysqli->query($sql); 00406 $this->query_end($result); 00407 if ($result) { 00408 while ($res = $result->fetch_object()) { 00409 if ($res->Key_name === 'PRIMARY') { 00410 continue; 00411 } 00412 if (!isset($indexes[$res->Key_name])) { 00413 $indexes[$res->Key_name] = array('unique'=>empty($res->Non_unique), 'columns'=>array()); 00414 } 00415 $indexes[$res->Key_name]['columns'][$res->Seq_in_index-1] = $res->Column_name; 00416 } 00417 $result->close(); 00418 } 00419 return $indexes; 00420 } 00421 00428 public function get_columns($table, $usecache=true) { 00429 if ($usecache and isset($this->columns[$table])) { 00430 return $this->columns[$table]; 00431 } 00432 00433 $this->columns[$table] = array(); 00434 00435 $sql = "SHOW COLUMNS FROM {$this->prefix}$table"; 00436 $this->query_start($sql, null, SQL_QUERY_AUX); 00437 $result = $this->mysqli->query($sql); 00438 $this->query_end(true); // Don't want to throw anything here ever. MDL-30147 00439 00440 if ($result === false) { 00441 return array(); 00442 } 00443 00444 while ($rawcolumn = $result->fetch_assoc()) { 00445 $rawcolumn = (object)array_change_key_case($rawcolumn, CASE_LOWER); 00446 00447 $info = new stdClass(); 00448 $info->name = $rawcolumn->field; 00449 $matches = null; 00450 00451 if (preg_match('/varchar\((\d+)\)/i', $rawcolumn->type, $matches)) { 00452 $info->type = 'varchar'; 00453 $info->meta_type = 'C'; 00454 $info->max_length = $matches[1]; 00455 $info->scale = null; 00456 $info->not_null = ($rawcolumn->null === 'NO'); 00457 $info->default_value = $rawcolumn->default; 00458 $info->has_default = is_null($info->default_value) ? false : true; 00459 $info->primary_key = ($rawcolumn->key === 'PRI'); 00460 $info->binary = false; 00461 $info->unsigned = null; 00462 $info->auto_increment= false; 00463 $info->unique = null; 00464 00465 } else if (preg_match('/([a-z]*int[a-z]*)\((\d+)\)/i', $rawcolumn->type, $matches)) { 00466 $info->type = $matches[1]; 00467 $info->primary_key = ($rawcolumn->key === 'PRI'); 00468 if ($info->primary_key) { 00469 $info->meta_type = 'R'; 00470 $info->max_length = $matches[2]; 00471 $info->scale = null; 00472 $info->not_null = ($rawcolumn->null === 'NO'); 00473 $info->default_value = $rawcolumn->default; 00474 $info->has_default = is_null($info->default_value) ? false : true; 00475 $info->binary = false; 00476 $info->unsigned = (stripos($rawcolumn->type, 'unsigned') !== false); 00477 $info->auto_increment= true; 00478 $info->unique = true; 00479 } else { 00480 $info->meta_type = 'I'; 00481 $info->max_length = $matches[2]; 00482 $info->scale = null; 00483 $info->not_null = ($rawcolumn->null === 'NO'); 00484 $info->default_value = $rawcolumn->default; 00485 $info->has_default = is_null($info->default_value) ? false : true; 00486 $info->binary = false; 00487 $info->unsigned = (stripos($rawcolumn->type, 'unsigned') !== false); 00488 $info->auto_increment= false; 00489 $info->unique = null; 00490 } 00491 00492 } else if (preg_match('/(decimal)\((\d+),(\d+)\)/i', $rawcolumn->type, $matches)) { 00493 $info->type = $matches[1]; 00494 $info->meta_type = 'N'; 00495 $info->max_length = $matches[2]; 00496 $info->scale = $matches[3]; 00497 $info->not_null = ($rawcolumn->null === 'NO'); 00498 $info->default_value = $rawcolumn->default; 00499 $info->has_default = is_null($info->default_value) ? false : true; 00500 $info->primary_key = ($rawcolumn->key === 'PRI'); 00501 $info->binary = false; 00502 $info->unsigned = (stripos($rawcolumn->type, 'unsigned') !== false); 00503 $info->auto_increment= false; 00504 $info->unique = null; 00505 00506 } else if (preg_match('/(double|float)(\((\d+),(\d+)\))?/i', $rawcolumn->type, $matches)) { 00507 $info->type = $matches[1]; 00508 $info->meta_type = 'N'; 00509 $info->max_length = isset($matches[3]) ? $matches[3] : null; 00510 $info->scale = isset($matches[4]) ? $matches[4] : null; 00511 $info->not_null = ($rawcolumn->null === 'NO'); 00512 $info->default_value = $rawcolumn->default; 00513 $info->has_default = is_null($info->default_value) ? false : true; 00514 $info->primary_key = ($rawcolumn->key === 'PRI'); 00515 $info->binary = false; 00516 $info->unsigned = (stripos($rawcolumn->type, 'unsigned') !== false); 00517 $info->auto_increment= false; 00518 $info->unique = null; 00519 00520 } else if (preg_match('/([a-z]*text)/i', $rawcolumn->type, $matches)) { 00521 $info->type = $matches[1]; 00522 $info->meta_type = 'X'; 00523 $info->max_length = -1; 00524 $info->scale = null; 00525 $info->not_null = ($rawcolumn->null === 'NO'); 00526 $info->default_value = $rawcolumn->default; 00527 $info->has_default = is_null($info->default_value) ? false : true; 00528 $info->primary_key = ($rawcolumn->key === 'PRI'); 00529 $info->binary = false; 00530 $info->unsigned = null; 00531 $info->auto_increment= false; 00532 $info->unique = null; 00533 00534 } else if (preg_match('/([a-z]*blob)/i', $rawcolumn->type, $matches)) { 00535 $info->type = $matches[1]; 00536 $info->meta_type = 'B'; 00537 $info->max_length = -1; 00538 $info->scale = null; 00539 $info->not_null = ($rawcolumn->null === 'NO'); 00540 $info->default_value = $rawcolumn->default; 00541 $info->has_default = is_null($info->default_value) ? false : true; 00542 $info->primary_key = false; 00543 $info->binary = true; 00544 $info->unsigned = null; 00545 $info->auto_increment= false; 00546 $info->unique = null; 00547 00548 } else if (preg_match('/enum\((.*)\)/i', $rawcolumn->type, $matches)) { 00549 $info->type = 'enum'; 00550 $info->meta_type = 'C'; 00551 $info->enums = array(); 00552 $info->max_length = 0; 00553 $values = $matches[1]; 00554 $values = explode(',', $values); 00555 $textlib = textlib_get_instance(); 00556 foreach ($values as $val) { 00557 $val = trim($val, "'"); 00558 $length = $textlib->strlen($val); 00559 $info->enums[] = $val; 00560 $info->max_length = ($info->max_length < $length) ? $length : $info->max_length; 00561 } 00562 $info->scale = null; 00563 $info->not_null = ($rawcolumn->null === 'NO'); 00564 $info->default_value = $rawcolumn->default; 00565 $info->has_default = is_null($info->default_value) ? false : true; 00566 $info->primary_key = ($rawcolumn->key === 'PRI'); 00567 $info->binary = false; 00568 $info->unsigned = null; 00569 $info->auto_increment= false; 00570 $info->unique = null; 00571 } 00572 00573 $this->columns[$table][$info->name] = new database_column_info($info); 00574 } 00575 00576 $result->close(); 00577 00578 return $this->columns[$table]; 00579 } 00580 00588 protected function normalise_value($column, $value) { 00589 if (is_bool($value)) { // Always, convert boolean to int 00590 $value = (int)$value; 00591 00592 } else if ($value === '') { 00593 if ($column->meta_type == 'I' or $column->meta_type == 'F' or $column->meta_type == 'N') { 00594 $value = 0; // prevent '' problems in numeric fields 00595 } 00596 // Any float value being stored in varchar or text field is converted to string to avoid 00597 // any implicit conversion by MySQL 00598 } else if (is_float($value) and ($column->meta_type == 'C' or $column->meta_type == 'X')) { 00599 $value = "$value"; 00600 } 00601 // workaround for problem with wrong enums in mysql - TODO: Out in Moodle 2.1 00602 if (!empty($column->enums)) { 00603 if (is_null($value) and !$column->not_null) { 00604 // ok - nulls allowed 00605 } else { 00606 if (!in_array((string)$value, $column->enums)) { 00607 throw new dml_write_exception('Enum value '.s($value).' not allowed in field '.$field.' table '.$table.'.'); 00608 } 00609 } 00610 } 00611 return $value; 00612 } 00613 00618 public function setup_is_unicodedb() { 00619 $sql = "SHOW LOCAL VARIABLES LIKE 'character_set_database'"; 00620 $this->query_start($sql, null, SQL_QUERY_AUX); 00621 $result = $this->mysqli->query($sql); 00622 $this->query_end($result); 00623 00624 $return = false; 00625 if ($result) { 00626 while($row = $result->fetch_assoc()) { 00627 if (isset($row['Value'])) { 00628 $return = (strtoupper($row['Value']) === 'UTF8' or strtoupper($row['Value']) === 'UTF-8'); 00629 } 00630 break; 00631 } 00632 $result->close(); 00633 } 00634 00635 if (!$return) { 00636 return false; 00637 } 00638 00639 $sql = "SHOW LOCAL VARIABLES LIKE 'collation_database'"; 00640 $this->query_start($sql, null, SQL_QUERY_AUX); 00641 $result = $this->mysqli->query($sql); 00642 $this->query_end($result); 00643 00644 $return = false; 00645 if ($result) { 00646 while($row = $result->fetch_assoc()) { 00647 if (isset($row['Value'])) { 00648 $return = (strpos($row['Value'], 'latin1') !== 0); 00649 } 00650 break; 00651 } 00652 $result->close(); 00653 } 00654 00655 return $return; 00656 } 00657 00664 public function change_database_structure($sql) { 00665 $this->reset_caches(); 00666 00667 $this->query_start($sql, null, SQL_QUERY_STRUCTURE); 00668 $result = $this->mysqli->query($sql); 00669 $this->query_end($result); 00670 00671 return true; 00672 } 00673 00678 protected function emulate_bound_params($sql, array $params=null) { 00679 if (empty($params)) { 00680 return $sql; 00681 } 00683 $parts = explode('?', $sql); 00684 $return = array_shift($parts); 00685 foreach ($params as $param) { 00686 if (is_bool($param)) { 00687 $return .= (int)$param; 00688 } else if (is_null($param)) { 00689 $return .= 'NULL'; 00690 } else if (is_number($param)) { 00691 $return .= "'".$param."'"; // we have to always use strings because mysql is using weird automatic int casting 00692 } else if (is_float($param)) { 00693 $return .= $param; 00694 } else { 00695 $param = $this->mysqli->real_escape_string($param); 00696 $return .= "'$param'"; 00697 } 00698 $return .= array_shift($parts); 00699 } 00700 return $return; 00701 } 00702 00711 public function execute($sql, array $params=null) { 00712 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 00713 00714 if (strpos($sql, ';') !== false) { 00715 throw new coding_exception('moodle_database::execute() Multiple sql statements found or bound parameters not used properly in query!'); 00716 } 00717 00718 $rawsql = $this->emulate_bound_params($sql, $params); 00719 00720 $this->query_start($sql, $params, SQL_QUERY_UPDATE); 00721 $result = $this->mysqli->query($rawsql); 00722 $this->query_end($result); 00723 00724 if ($result === true) { 00725 return true; 00726 00727 } else { 00728 $result->close(); 00729 return true; 00730 } 00731 } 00732 00749 public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) { 00750 $limitfrom = (int)$limitfrom; 00751 $limitnum = (int)$limitnum; 00752 $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom; 00753 $limitnum = ($limitnum < 0) ? 0 : $limitnum; 00754 00755 if ($limitfrom or $limitnum) { 00756 if ($limitnum < 1) { 00757 $limitnum = "18446744073709551615"; 00758 } 00759 $sql .= " LIMIT $limitfrom, $limitnum"; 00760 } 00761 00762 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 00763 $rawsql = $this->emulate_bound_params($sql, $params); 00764 00765 $this->query_start($sql, $params, SQL_QUERY_SELECT); 00766 // no MYSQLI_USE_RESULT here, it would block write ops on affected tables 00767 $result = $this->mysqli->query($rawsql, MYSQLI_STORE_RESULT); 00768 $this->query_end($result); 00769 00770 return $this->create_recordset($result); 00771 } 00772 00773 protected function create_recordset($result) { 00774 return new mysqli_native_moodle_recordset($result); 00775 } 00776 00791 public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) { 00792 $limitfrom = (int)$limitfrom; 00793 $limitnum = (int)$limitnum; 00794 $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom; 00795 $limitnum = ($limitnum < 0) ? 0 : $limitnum; 00796 00797 if ($limitfrom or $limitnum) { 00798 if ($limitnum < 1) { 00799 $limitnum = "18446744073709551615"; 00800 } 00801 $sql .= " LIMIT $limitfrom, $limitnum"; 00802 } 00803 00804 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 00805 $rawsql = $this->emulate_bound_params($sql, $params); 00806 00807 $this->query_start($sql, $params, SQL_QUERY_SELECT); 00808 $result = $this->mysqli->query($rawsql, MYSQLI_STORE_RESULT); 00809 $this->query_end($result); 00810 00811 $return = array(); 00812 00813 while($row = $result->fetch_assoc()) { 00814 $row = array_change_key_case($row, CASE_LOWER); 00815 $id = reset($row); 00816 if (isset($return[$id])) { 00817 $colname = key($row); 00818 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); 00819 } 00820 $return[$id] = (object)$row; 00821 } 00822 $result->close(); 00823 00824 return $return; 00825 } 00826 00835 public function get_fieldset_sql($sql, array $params=null) { 00836 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 00837 $rawsql = $this->emulate_bound_params($sql, $params); 00838 00839 $this->query_start($sql, $params, SQL_QUERY_SELECT); 00840 $result = $this->mysqli->query($rawsql, MYSQLI_STORE_RESULT); 00841 $this->query_end($result); 00842 00843 $return = array(); 00844 00845 while($row = $result->fetch_assoc()) { 00846 $return[] = reset($row); 00847 } 00848 $result->close(); 00849 00850 return $return; 00851 } 00852 00863 public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) { 00864 if (!is_array($params)) { 00865 $params = (array)$params; 00866 } 00867 00868 if ($customsequence) { 00869 if (!isset($params['id'])) { 00870 throw new coding_exception('moodle_database::insert_record_raw() id field must be specified if custom sequences used.'); 00871 } 00872 $returnid = false; 00873 } else { 00874 unset($params['id']); 00875 } 00876 00877 if (empty($params)) { 00878 throw new coding_exception('moodle_database::insert_record_raw() no fields found.'); 00879 } 00880 00881 $fields = implode(',', array_keys($params)); 00882 $qms = array_fill(0, count($params), '?'); 00883 $qms = implode(',', $qms); 00884 00885 $sql = "INSERT INTO {$this->prefix}$table ($fields) VALUES($qms)"; 00886 00887 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 00888 $rawsql = $this->emulate_bound_params($sql, $params); 00889 00890 $this->query_start($sql, $params, SQL_QUERY_INSERT); 00891 $result = $this->mysqli->query($rawsql); 00892 $id = @$this->mysqli->insert_id; // must be called before query_end() which may insert log into db 00893 $this->query_end($result); 00894 00895 if (!$id) { 00896 throw new dml_write_exception('unknown error fetching inserted id'); 00897 } 00898 00899 if (!$returnid) { 00900 return true; 00901 } else { 00902 return (int)$id; 00903 } 00904 } 00905 00918 public function insert_record($table, $dataobject, $returnid=true, $bulk=false) { 00919 $dataobject = (array)$dataobject; 00920 00921 $columns = $this->get_columns($table); 00922 $cleaned = array(); 00923 00924 foreach ($dataobject as $field=>$value) { 00925 if ($field === 'id') { 00926 continue; 00927 } 00928 if (!isset($columns[$field])) { 00929 continue; 00930 } 00931 $column = $columns[$field]; 00932 $cleaned[$field] = $this->normalise_value($column, $value); 00933 } 00934 00935 return $this->insert_record_raw($table, $cleaned, $returnid, $bulk); 00936 } 00937 00947 public function import_record($table, $dataobject) { 00948 $dataobject = (array)$dataobject; 00949 00950 $columns = $this->get_columns($table); 00951 $cleaned = array(); 00952 00953 foreach ($dataobject as $field=>$value) { 00954 if (!isset($columns[$field])) { 00955 continue; 00956 } 00957 $cleaned[$field] = $value; 00958 } 00959 00960 return $this->insert_record_raw($table, $cleaned, false, true, true); 00961 } 00962 00971 public function update_record_raw($table, $params, $bulk=false) { 00972 $params = (array)$params; 00973 00974 if (!isset($params['id'])) { 00975 throw new coding_exception('moodle_database::update_record_raw() id field must be specified.'); 00976 } 00977 $id = $params['id']; 00978 unset($params['id']); 00979 00980 if (empty($params)) { 00981 throw new coding_exception('moodle_database::update_record_raw() no fields found.'); 00982 } 00983 00984 $sets = array(); 00985 foreach ($params as $field=>$value) { 00986 $sets[] = "$field = ?"; 00987 } 00988 00989 $params[] = $id; // last ? in WHERE condition 00990 00991 $sets = implode(',', $sets); 00992 $sql = "UPDATE {$this->prefix}$table SET $sets WHERE id=?"; 00993 00994 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 00995 $rawsql = $this->emulate_bound_params($sql, $params); 00996 00997 $this->query_start($sql, $params, SQL_QUERY_UPDATE); 00998 $result = $this->mysqli->query($rawsql); 00999 $this->query_end($result); 01000 01001 return true; 01002 } 01003 01017 public function update_record($table, $dataobject, $bulk=false) { 01018 $dataobject = (array)$dataobject; 01019 01020 $columns = $this->get_columns($table); 01021 $cleaned = array(); 01022 01023 foreach ($dataobject as $field=>$value) { 01024 if (!isset($columns[$field])) { 01025 continue; 01026 } 01027 $column = $columns[$field]; 01028 $cleaned[$field] = $this->normalise_value($column, $value); 01029 } 01030 01031 return $this->update_record_raw($table, $cleaned, $bulk); 01032 } 01033 01045 public function set_field_select($table, $newfield, $newvalue, $select, array $params=null) { 01046 if ($select) { 01047 $select = "WHERE $select"; 01048 } 01049 if (is_null($params)) { 01050 $params = array(); 01051 } 01052 list($select, $params, $type) = $this->fix_sql_params($select, $params); 01053 01054 // Get column metadata 01055 $columns = $this->get_columns($table); 01056 $column = $columns[$newfield]; 01057 01058 $normalised_value = $this->normalise_value($column, $newvalue); 01059 01060 if (is_null($normalised_value)) { 01061 $newfield = "$newfield = NULL"; 01062 } else { 01063 $newfield = "$newfield = ?"; 01064 array_unshift($params, $normalised_value); 01065 } 01066 $sql = "UPDATE {$this->prefix}$table SET $newfield $select"; 01067 $rawsql = $this->emulate_bound_params($sql, $params); 01068 01069 $this->query_start($sql, $params, SQL_QUERY_UPDATE); 01070 $result = $this->mysqli->query($rawsql); 01071 $this->query_end($result); 01072 01073 return true; 01074 } 01075 01085 public function delete_records_select($table, $select, array $params=null) { 01086 if ($select) { 01087 $select = "WHERE $select"; 01088 } 01089 $sql = "DELETE FROM {$this->prefix}$table $select"; 01090 01091 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 01092 $rawsql = $this->emulate_bound_params($sql, $params); 01093 01094 $this->query_start($sql, $params, SQL_QUERY_UPDATE); 01095 $result = $this->mysqli->query($rawsql); 01096 $this->query_end($result); 01097 01098 return true; 01099 } 01100 01101 public function sql_cast_char2int($fieldname, $text=false) { 01102 return ' CAST(' . $fieldname . ' AS SIGNED) '; 01103 } 01104 01105 public function sql_cast_char2real($fieldname, $text=false) { 01106 return ' CAST(' . $fieldname . ' AS DECIMAL) '; 01107 } 01108 01120 public function sql_like($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notlike = false, $escapechar = '\\') { 01121 if (strpos($param, '%') !== false) { 01122 debugging('Potential SQL injection detected, sql_ilike() expects bound parameters (? or :named)'); 01123 } 01124 $escapechar = $this->mysqli->real_escape_string($escapechar); // prevents problems with C-style escapes of enclosing '\' 01125 01126 $LIKE = $notlike ? 'NOT LIKE' : 'LIKE'; 01127 if ($casesensitive) { 01128 return "$fieldname $LIKE $param COLLATE utf8_bin ESCAPE '$escapechar'"; 01129 } else { 01130 if ($accentsensitive) { 01131 return "LOWER($fieldname) $LIKE LOWER($param) COLLATE utf8_bin ESCAPE '$escapechar'"; 01132 } else { 01133 return "$fieldname $LIKE $param ESCAPE '$escapechar'"; 01134 } 01135 } 01136 } 01137 01146 public function sql_concat() { 01147 $arr = func_get_args(); 01148 $s = implode(', ', $arr); 01149 if ($s === '') { 01150 return "''"; 01151 } 01152 return "CONCAT($s)"; 01153 } 01154 01163 public function sql_concat_join($separator="' '", $elements=array()) { 01164 $s = implode(', ', $elements); 01165 01166 if ($s === '') { 01167 return "''"; 01168 } 01169 return "CONCAT_WS($separator, $s)"; 01170 } 01171 01177 public function sql_length($fieldname) { 01178 return ' CHAR_LENGTH(' . $fieldname . ')'; 01179 } 01180 01184 public function sql_regex_supported() { 01185 return true; 01186 } 01187 01193 public function sql_regex($positivematch=true) { 01194 return $positivematch ? 'REGEXP' : 'NOT REGEXP'; 01195 } 01196 01197 public function sql_cast_2signed($fieldname) { 01198 return ' CAST(' . $fieldname . ' AS SIGNED) '; 01199 } 01200 01202 public function session_lock_supported() { 01203 return true; 01204 } 01205 01212 public function get_session_lock($rowid, $timeout) { 01213 parent::get_session_lock($rowid, $timeout); 01214 01215 $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid; 01216 $sql = "SELECT GET_LOCK('$fullname', $timeout)"; 01217 $this->query_start($sql, null, SQL_QUERY_AUX); 01218 $result = $this->mysqli->query($sql); 01219 $this->query_end($result); 01220 01221 if ($result) { 01222 $arr = $result->fetch_assoc(); 01223 $result->close(); 01224 01225 if (reset($arr) == 1) { 01226 return; 01227 } else { 01228 throw new dml_sessionwait_exception(); 01229 } 01230 } 01231 } 01232 01233 public function release_session_lock($rowid) { 01234 parent::release_session_lock($rowid); 01235 $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid; 01236 $sql = "SELECT RELEASE_LOCK('$fullname')"; 01237 $this->query_start($sql, null, SQL_QUERY_AUX); 01238 $result = $this->mysqli->query($sql); 01239 $this->query_end($result); 01240 01241 if ($result) { 01242 $result->close(); 01243 } 01244 } 01245 01247 01258 protected function transactions_supported() { 01259 if (!is_null($this->transactions_supported)) { 01260 return $this->transactions_supported; 01261 } 01262 01263 // this is all just guessing, might be better to just specify it in config.php 01264 if (isset($this->dboptions['dbtransactions'])) { 01265 $this->transactions_supported = $this->dboptions['dbtransactions']; 01266 return $this->transactions_supported; 01267 } 01268 01269 $this->transactions_supported = false; 01270 01271 $engine = $this->get_dbengine(); 01272 01273 // Only will accept transactions if using compatible storage engine (more engines can be added easily BDB, Falcon...) 01274 if (in_array($engine, array('InnoDB', 'INNOBASE', 'BDB', 'XtraDB', 'Aria', 'Falcon'))) { 01275 $this->transactions_supported = true; 01276 } 01277 01278 return $this->transactions_supported; 01279 } 01280 01286 protected function begin_transaction() { 01287 if (!$this->transactions_supported()) { 01288 return; 01289 } 01290 01291 $sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"; 01292 $this->query_start($sql, NULL, SQL_QUERY_AUX); 01293 $result = $this->mysqli->query($sql); 01294 $this->query_end($result); 01295 01296 $sql = "START TRANSACTION"; 01297 $this->query_start($sql, NULL, SQL_QUERY_AUX); 01298 $result = $this->mysqli->query($sql); 01299 $this->query_end($result); 01300 } 01301 01307 protected function commit_transaction() { 01308 if (!$this->transactions_supported()) { 01309 return; 01310 } 01311 01312 $sql = "COMMIT"; 01313 $this->query_start($sql, NULL, SQL_QUERY_AUX); 01314 $result = $this->mysqli->query($sql); 01315 $this->query_end($result); 01316 } 01317 01323 protected function rollback_transaction() { 01324 if (!$this->transactions_supported()) { 01325 return; 01326 } 01327 01328 $sql = "ROLLBACK"; 01329 $this->query_start($sql, NULL, SQL_QUERY_AUX); 01330 $result = $this->mysqli->query($sql); 01331 $this->query_end($result); 01332 01333 return true; 01334 } 01335 }