Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/lib/dml/mysqli_native_moodle_database.php
Go to the documentation of this file.
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 }
 All Data Structures Namespaces Files Functions Variables Enumerations