|
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/database_column_info.php'); 00031 require_once($CFG->libdir.'/dml/moodle_recordset.php'); 00032 require_once($CFG->libdir.'/dml/moodle_transaction.php'); 00033 00035 00037 define('SQL_PARAMS_NAMED', 1); 00038 00040 define('SQL_PARAMS_QM', 2); 00041 00043 define('SQL_PARAMS_DOLLAR', 4); 00044 00045 00047 define('SQL_QUERY_SELECT', 1); 00048 00050 define('SQL_QUERY_INSERT', 2); 00051 00053 define('SQL_QUERY_UPDATE', 3); 00054 00056 define('SQL_QUERY_STRUCTURE', 4); 00057 00059 define('SQL_QUERY_AUX', 5); 00060 00064 abstract class moodle_database { 00065 00067 protected $database_manager; 00069 protected $temptables; 00071 protected $columns = array(); // I wish we had a shared memory cache for this :-( 00073 protected $tables = null; 00074 00075 // db connection options 00077 protected $dbhost; 00079 protected $dbuser; 00081 protected $dbpass; 00083 protected $dbname; 00085 protected $prefix; 00086 00088 protected $dboptions; 00089 00091 protected $external; 00092 00094 protected $reads = 0; 00096 protected $writes = 0; 00097 00099 protected $debug = 0; 00100 00102 protected $last_sql; 00104 protected $last_params; 00106 protected $last_type; 00108 protected $last_extrainfo; 00110 protected $last_time; 00112 private $loggingquery = false; 00113 00115 protected $used_for_db_sessions = false; 00116 00118 private $transactions = array(); 00120 private $force_rollback = false; 00121 00123 private $fix_sql_params_i; 00125 private $inorequaluniqueindex = 1; // guarantees unique parameters in each request 00126 00132 public function __construct($external=false) { 00133 $this->external = $external; 00134 } 00135 00139 public function __destruct() { 00140 $this->dispose(); 00141 } 00142 00148 public abstract function driver_installed(); 00149 00155 public function get_prefix() { 00156 return $this->prefix; 00157 } 00158 00166 public static function get_driver_instance($type, $library, $external = false) { 00167 global $CFG; 00168 00169 $classname = $type.'_'.$library.'_moodle_database'; 00170 $libfile = "$CFG->libdir/dml/$classname.php"; 00171 00172 if (!file_exists($libfile)) { 00173 return null; 00174 } 00175 00176 require_once($libfile); 00177 return new $classname($external); 00178 } 00179 00185 public abstract function get_dbfamily(); 00186 00192 protected abstract function get_dbtype(); 00193 00199 protected abstract function get_dblibrary(); 00200 00206 public abstract function get_name(); 00207 00213 public abstract function get_configuration_help(); 00214 00220 public abstract function get_configuration_hints(); 00221 00226 public function export_dbconfig() { 00227 $cfg = new stdClass(); 00228 $cfg->dbtype = $this->get_dbtype(); 00229 $cfg->dblibrary = $this->get_dblibrary(); 00230 $cfg->dbhost = $this->dbhost; 00231 $cfg->dbname = $this->dbname; 00232 $cfg->dbuser = $this->dbuser; 00233 $cfg->dbpass = $this->dbpass; 00234 $cfg->prefix = $this->prefix; 00235 if ($this->dboptions) { 00236 $cfg->dboptions = $this->dboptions; 00237 } 00238 00239 return $cfg; 00240 } 00241 00248 public function diagnose() { 00249 return null; 00250 } 00251 00264 public abstract function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null); 00265 00276 protected function store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) { 00277 $this->dbhost = $dbhost; 00278 $this->dbuser = $dbuser; 00279 $this->dbpass = $dbpass; 00280 $this->dbname = $dbname; 00281 $this->prefix = $prefix; 00282 $this->dboptions = (array)$dboptions; 00283 } 00284 00294 public function create_database($dbhost, $dbuser, $dbpass, $dbname, array $dboptions=null) { 00295 return false; 00296 } 00297 00304 public function dispose() { 00305 if ($this->transactions) { 00306 // this should not happen, it usually indicates wrong catching of exceptions, 00307 // because all transactions should be finished manually or in default exception handler. 00308 // unfortunately we can not access global $CFG any more and can not print debug, 00309 // the diagnostic info should be printed in footer instead 00310 $lowesttransaction = end($this->transactions); 00311 $backtrace = $lowesttransaction->get_backtrace(); 00312 00313 error_log('Potential coding error - active database transaction detected when disposing database:'."\n".format_backtrace($backtrace, true)); 00314 $this->force_transaction_rollback(); 00315 } 00316 if ($this->used_for_db_sessions) { 00317 // this is needed because we need to save session to db before closing it 00318 session_get_instance()->write_close(); 00319 $this->used_for_db_sessions = false; 00320 } 00321 if ($this->temptables) { 00322 $this->temptables->dispose(); 00323 $this->temptables = null; 00324 } 00325 if ($this->database_manager) { 00326 $this->database_manager->dispose(); 00327 $this->database_manager = null; 00328 } 00329 $this->columns = array(); 00330 $this->tables = null; 00331 } 00332 00341 protected function query_start($sql, array $params=null, $type, $extrainfo=null) { 00342 if ($this->loggingquery) { 00343 return; 00344 } 00345 $this->last_sql = $sql; 00346 $this->last_params = $params; 00347 $this->last_type = $type; 00348 $this->last_extrainfo = $extrainfo; 00349 $this->last_time = microtime(true); 00350 00351 switch ($type) { 00352 case SQL_QUERY_SELECT: 00353 case SQL_QUERY_AUX: 00354 $this->reads++; 00355 break; 00356 case SQL_QUERY_INSERT: 00357 case SQL_QUERY_UPDATE: 00358 case SQL_QUERY_STRUCTURE: 00359 $this->writes++; 00360 } 00361 00362 $this->print_debug($sql, $params); 00363 } 00364 00370 protected function query_end($result) { 00371 if ($this->loggingquery) { 00372 return; 00373 } 00374 if ($result !== false) { 00375 $this->query_log(); 00376 // free memory 00377 $this->last_sql = null; 00378 $this->last_params = null; 00379 return; 00380 } 00381 00382 // remember current info, log queries may alter it 00383 $type = $this->last_type; 00384 $sql = $this->last_sql; 00385 $params = $this->last_params; 00386 $time = microtime(true) - $this->last_time; 00387 $error = $this->get_last_error(); 00388 00389 $this->query_log($error); 00390 00391 switch ($type) { 00392 case SQL_QUERY_SELECT: 00393 case SQL_QUERY_AUX: 00394 throw new dml_read_exception($error, $sql, $params); 00395 case SQL_QUERY_INSERT: 00396 case SQL_QUERY_UPDATE: 00397 throw new dml_write_exception($error, $sql, $params); 00398 case SQL_QUERY_STRUCTURE: 00399 $this->get_manager(); // includes ddl exceptions classes ;-) 00400 throw new ddl_change_structure_exception($error, $sql); 00401 } 00402 } 00403 00409 public function query_log($error=false) { 00410 $logall = !empty($this->dboptions['logall']); 00411 $logslow = !empty($this->dboptions['logslow']) ? $this->dboptions['logslow'] : false; 00412 $logerrors = !empty($this->dboptions['logerrors']); 00413 $iserror = ($error !== false); 00414 00415 $time = microtime(true) - $this->last_time; 00416 00417 if ($logall or ($logslow and ($logslow < ($time+0.00001))) or ($iserror and $logerrors)) { 00418 $this->loggingquery = true; 00419 try { 00420 $backtrace = debug_backtrace(); 00421 if ($backtrace) { 00422 //remove query_log() 00423 array_shift($backtrace); 00424 } 00425 if ($backtrace) { 00426 //remove query_end() 00427 array_shift($backtrace); 00428 } 00429 $log = new stdClass(); 00430 $log->qtype = $this->last_type; 00431 $log->sqltext = $this->last_sql; 00432 $log->sqlparams = var_export((array)$this->last_params, true); 00433 $log->error = (int)$iserror; 00434 $log->info = $iserror ? $error : null; 00435 $log->backtrace = format_backtrace($backtrace, true); 00436 $log->exectime = $time; 00437 $log->timelogged = time(); 00438 $this->insert_record('log_queries', $log); 00439 } catch (Exception $ignored) { 00440 } 00441 $this->loggingquery = false; 00442 } 00443 } 00444 00449 public abstract function get_server_info(); 00450 00455 protected abstract function allowed_param_types(); 00456 00461 public abstract function get_last_error(); 00462 00470 protected function print_debug($sql, array $params=null, $obj=null) { 00471 if (!$this->get_debug()) { 00472 return; 00473 } 00474 if (CLI_SCRIPT) { 00475 echo "--------------------------------\n"; 00476 echo $sql."\n"; 00477 if (!is_null($params)) { 00478 echo "[".var_export($params, true)."]\n"; 00479 } 00480 echo "--------------------------------\n"; 00481 } else { 00482 echo "<hr />\n"; 00483 echo s($sql)."\n"; 00484 if (!is_null($params)) { 00485 echo "[".s(var_export($params, true))."]\n"; 00486 } 00487 echo "<hr />\n"; 00488 } 00489 } 00490 00497 protected function where_clause($table, array $conditions=null) { 00498 // We accept nulls in conditions 00499 $conditions = is_null($conditions) ? array() : $conditions; 00500 // Some checks performed under debugging only 00501 if (debugging()) { 00502 $columns = $this->get_columns($table); 00503 if (empty($columns)) { 00504 // no supported columns means most probably table does not exist 00505 throw new dml_exception('ddltablenotexist', $table); 00506 } 00507 foreach ($conditions as $key=>$value) { 00508 if (!isset($columns[$key])) { 00509 $a = new stdClass(); 00510 $a->fieldname = $key; 00511 $a->tablename = $table; 00512 throw new dml_exception('ddlfieldnotexist', $a); 00513 } 00514 $column = $columns[$key]; 00515 if ($column->meta_type == 'X') { 00516 //ok so the column is a text column. sorry no text columns in the where clause conditions 00517 throw new dml_exception('textconditionsnotallowed', $conditions); 00518 } 00519 } 00520 } 00521 00522 $allowed_types = $this->allowed_param_types(); 00523 if (empty($conditions)) { 00524 return array('', array()); 00525 } 00526 $where = array(); 00527 $params = array(); 00528 00529 foreach ($conditions as $key=>$value) { 00530 if (is_int($key)) { 00531 throw new dml_exception('invalidnumkey'); 00532 } 00533 if (is_null($value)) { 00534 $where[] = "$key IS NULL"; 00535 } else { 00536 if ($allowed_types & SQL_PARAMS_NAMED) { 00537 // Need to verify key names because they can contain, originally, 00538 // spaces and other forbidden chars when using sql_xxx() functions and friends. 00539 $normkey = trim(preg_replace('/[^a-zA-Z0-9_-]/', '_', $key), '-_'); 00540 if ($normkey !== $key) { 00541 debugging('Invalid key found in the conditions array.'); 00542 } 00543 $where[] = "$key = :$normkey"; 00544 $params[$normkey] = $value; 00545 } else { 00546 $where[] = "$key = ?"; 00547 $params[] = $value; 00548 } 00549 } 00550 } 00551 $where = implode(" AND ", $where); 00552 return array($where, $params); 00553 } 00554 00562 protected function where_clause_list($field, array $values) { 00563 $params = array(); 00564 $select = array(); 00565 $values = (array)$values; 00566 foreach ($values as $value) { 00567 if (is_bool($value)) { 00568 $value = (int)$value; 00569 } 00570 if (is_null($value)) { 00571 $select[] = "$field IS NULL"; 00572 } else { 00573 $select[] = "$field = ?"; 00574 $params[] = $value; 00575 } 00576 } 00577 $select = implode(" OR ", $select); 00578 return array($select, $params); 00579 } 00580 00591 public function get_in_or_equal($items, $type=SQL_PARAMS_QM, $prefix='param', $equal=true, $onemptyitems=false) { 00592 00593 // default behavior, throw exception on empty array 00594 if (is_array($items) and empty($items) and $onemptyitems === false) { 00595 throw new coding_exception('moodle_database::get_in_or_equal() does not accept empty arrays'); 00596 } 00597 // handle $onemptyitems on empty array of items 00598 if (is_array($items) and empty($items)) { 00599 if (is_null($onemptyitems)) { // Special case, NULL value 00600 $sql = $equal ? ' IS NULL' : ' IS NOT NULL'; 00601 return (array($sql, array())); 00602 } else { 00603 $items = array($onemptyitems); // Rest of cases, prepare $items for std processing 00604 } 00605 } 00606 00607 if ($type == SQL_PARAMS_QM) { 00608 if (!is_array($items) or count($items) == 1) { 00609 $sql = $equal ? '= ?' : '<> ?'; 00610 $items = (array)$items; 00611 $params = array_values($items); 00612 } else { 00613 if ($equal) { 00614 $sql = 'IN ('.implode(',', array_fill(0, count($items), '?')).')'; 00615 } else { 00616 $sql = 'NOT IN ('.implode(',', array_fill(0, count($items), '?')).')'; 00617 } 00618 $params = array_values($items); 00619 } 00620 00621 } else if ($type == SQL_PARAMS_NAMED) { 00622 if (empty($prefix)) { 00623 $prefix = 'param'; 00624 } 00625 00626 if (!is_array($items)){ 00627 $param = $prefix.$this->inorequaluniqueindex++; 00628 $sql = $equal ? "= :$param" : "<> :$param"; 00629 $params = array($param=>$items); 00630 } else if (count($items) == 1) { 00631 $param = $prefix.$this->inorequaluniqueindex++; 00632 $sql = $equal ? "= :$param" : "<> :$param"; 00633 $item = reset($items); 00634 $params = array($param=>$item); 00635 } else { 00636 $params = array(); 00637 $sql = array(); 00638 foreach ($items as $item) { 00639 $param = $prefix.$this->inorequaluniqueindex++; 00640 $params[$param] = $item; 00641 $sql[] = ':'.$param; 00642 } 00643 if ($equal) { 00644 $sql = 'IN ('.implode(',', $sql).')'; 00645 } else { 00646 $sql = 'NOT IN ('.implode(',', $sql).')'; 00647 } 00648 } 00649 00650 } else { 00651 throw new dml_exception('typenotimplement'); 00652 } 00653 return array($sql, $params); 00654 } 00655 00661 protected function fix_table_names($sql) { 00662 return preg_replace('/\{([a-z][a-z0-9_]*)\}/', $this->prefix.'$1', $sql); 00663 } 00664 00666 private function _fix_sql_params_dollar_callback($match) { 00667 $this->fix_sql_params_i++; 00668 return "\$".$this->fix_sql_params_i; 00669 } 00670 00677 public function fix_sql_params($sql, array $params=null) { 00678 $params = (array)$params; // mke null array if needed 00679 $allowed_types = $this->allowed_param_types(); 00680 00681 // convert table names 00682 $sql = $this->fix_table_names($sql); 00683 00684 // cast booleans to 1/0 int 00685 foreach ($params as $key => $value) { 00686 $params[$key] = is_bool($value) ? (int)$value : $value; 00687 } 00688 00689 // NICOLAS C: Fixed regexp for negative backwards lookahead of double colons. Thanks for Sam Marshall's help 00690 $named_count = preg_match_all('/(?<!:):[a-z][a-z0-9_]*/', $sql, $named_matches); // :: used in pgsql casts 00691 $dollar_count = preg_match_all('/\$[1-9][0-9]*/', $sql, $dollar_matches); 00692 $q_count = substr_count($sql, '?'); 00693 00694 $count = 0; 00695 00696 if ($named_count) { 00697 $type = SQL_PARAMS_NAMED; 00698 $count = $named_count; 00699 00700 } 00701 if ($dollar_count) { 00702 if ($count) { 00703 throw new dml_exception('mixedtypesqlparam'); 00704 } 00705 $type = SQL_PARAMS_DOLLAR; 00706 $count = $dollar_count; 00707 00708 } 00709 if ($q_count) { 00710 if ($count) { 00711 throw new dml_exception('mixedtypesqlparam'); 00712 } 00713 $type = SQL_PARAMS_QM; 00714 $count = $q_count; 00715 00716 } 00717 00718 if (!$count) { 00719 // ignore params 00720 if ($allowed_types & SQL_PARAMS_NAMED) { 00721 return array($sql, array(), SQL_PARAMS_NAMED); 00722 } else if ($allowed_types & SQL_PARAMS_QM) { 00723 return array($sql, array(), SQL_PARAMS_QM); 00724 } else { 00725 return array($sql, array(), SQL_PARAMS_DOLLAR); 00726 } 00727 } 00728 00729 if ($count > count($params)) { 00730 $a = new stdClass; 00731 $a->expected = $count; 00732 $a->actual = count($params); 00733 throw new dml_exception('invalidqueryparam', $a); 00734 } 00735 00736 $target_type = $allowed_types; 00737 00738 if ($type & $allowed_types) { // bitwise AND 00739 if ($count == count($params)) { 00740 if ($type == SQL_PARAMS_QM) { 00741 return array($sql, array_values($params), SQL_PARAMS_QM); // 0-based array required 00742 } else { 00743 //better do the validation of names below 00744 } 00745 } 00746 // needs some fixing or validation - there might be more params than needed 00747 $target_type = $type; 00748 } 00749 00750 if ($type == SQL_PARAMS_NAMED) { 00751 $finalparams = array(); 00752 foreach ($named_matches[0] as $key) { 00753 $key = trim($key, ':'); 00754 if (!array_key_exists($key, $params)) { 00755 throw new dml_exception('missingkeyinsql', $key, ''); 00756 } 00757 if (strlen($key) > 30) { 00758 throw new coding_exception( 00759 "Placeholder names must be 30 characters or shorter. '" . 00760 $key . "' is too long.", $sql); 00761 } 00762 $finalparams[$key] = $params[$key]; 00763 } 00764 if ($count != count($finalparams)) { 00765 throw new dml_exception('duplicateparaminsql'); 00766 } 00767 00768 if ($target_type & SQL_PARAMS_QM) { 00769 $sql = preg_replace('/(?<!:):[a-z][a-z0-9_]*/', '?', $sql); 00770 return array($sql, array_values($finalparams), SQL_PARAMS_QM); // 0-based required 00771 } else if ($target_type & SQL_PARAMS_NAMED) { 00772 return array($sql, $finalparams, SQL_PARAMS_NAMED); 00773 } else { // $type & SQL_PARAMS_DOLLAR 00774 //lambda-style functions eat memory - we use globals instead :-( 00775 $this->fix_sql_params_i = 0; 00776 $sql = preg_replace_callback('/(?<!:):[a-z][a-z0-9_]*/', array($this, '_fix_sql_params_dollar_callback'), $sql); 00777 return array($sql, array_values($finalparams), SQL_PARAMS_DOLLAR); // 0-based required 00778 } 00779 00780 } else if ($type == SQL_PARAMS_DOLLAR) { 00781 if ($target_type & SQL_PARAMS_DOLLAR) { 00782 return array($sql, array_values($params), SQL_PARAMS_DOLLAR); // 0-based required 00783 } else if ($target_type & SQL_PARAMS_QM) { 00784 $sql = preg_replace('/\$[0-9]+/', '?', $sql); 00785 return array($sql, array_values($params), SQL_PARAMS_QM); // 0-based required 00786 } else { //$target_type & SQL_PARAMS_NAMED 00787 $sql = preg_replace('/\$([0-9]+)/', ':param\\1', $sql); 00788 $finalparams = array(); 00789 foreach ($params as $key=>$param) { 00790 $key++; 00791 $finalparams['param'.$key] = $param; 00792 } 00793 return array($sql, $finalparams, SQL_PARAMS_NAMED); 00794 } 00795 00796 } else { // $type == SQL_PARAMS_QM 00797 if (count($params) != $count) { 00798 $params = array_slice($params, 0, $count); 00799 } 00800 00801 if ($target_type & SQL_PARAMS_QM) { 00802 return array($sql, array_values($params), SQL_PARAMS_QM); // 0-based required 00803 } else if ($target_type & SQL_PARAMS_NAMED) { 00804 $finalparams = array(); 00805 $pname = 'param0'; 00806 $parts = explode('?', $sql); 00807 $sql = array_shift($parts); 00808 foreach ($parts as $part) { 00809 $param = array_shift($params); 00810 $pname++; 00811 $sql .= ':'.$pname.$part; 00812 $finalparams[$pname] = $param; 00813 } 00814 return array($sql, $finalparams, SQL_PARAMS_NAMED); 00815 } else { // $type & SQL_PARAMS_DOLLAR 00816 //lambda-style functions eat memory - we use globals instead :-( 00817 $this->fix_sql_params_i = 0; 00818 $sql = preg_replace_callback('/\?/', array($this, '_fix_sql_params_dollar_callback'), $sql); 00819 return array($sql, array_values($params), SQL_PARAMS_DOLLAR); // 0-based required 00820 } 00821 } 00822 } 00823 00828 public abstract function get_tables($usecache=true); 00829 00834 public abstract function get_indexes($table); 00835 00842 public abstract function get_columns($table, $usecache=true); 00843 00851 protected abstract function normalise_value($column, $value); 00852 00858 public function reset_caches() { 00859 $this->columns = array(); 00860 $this->tables = null; 00861 } 00862 00868 public function get_manager() { 00869 global $CFG; 00870 00871 if (!$this->database_manager) { 00872 require_once($CFG->libdir.'/ddllib.php'); 00873 00874 $classname = $this->get_dbfamily().'_sql_generator'; 00875 require_once("$CFG->libdir/ddl/$classname.php"); 00876 $generator = new $classname($this, $this->temptables); 00877 00878 $this->database_manager = new database_manager($this, $generator); 00879 } 00880 return $this->database_manager; 00881 } 00882 00887 public function change_db_encoding() { 00888 return false; 00889 } 00890 00895 public function setup_is_unicodedb() { 00896 return true; 00897 } 00898 00904 public function set_debug($state) { 00905 $this->debug = $state; 00906 } 00907 00912 public function get_debug() { 00913 return $this->debug; 00914 } 00915 00920 public function set_logging($state) { 00921 // adodb sql logging shares one table without prefix per db - this is no longer acceptable :-( 00922 // we must create one table shared by all drivers 00923 } 00924 00931 public abstract function change_database_structure($sql); 00932 00941 public abstract function execute($sql, array $params=null); 00942 00977 public function get_recordset($table, array $conditions=null, $sort='', $fields='*', $limitfrom=0, $limitnum=0) { 00978 list($select, $params) = $this->where_clause($table, $conditions); 00979 return $this->get_recordset_select($table, $select, $params, $sort, $fields, $limitfrom, $limitnum); 00980 } 00981 01000 public function get_recordset_list($table, $field, array $values, $sort='', $fields='*', $limitfrom=0, $limitnum=0) { 01001 list($select, $params) = $this->where_clause_list($field, $values); 01002 if (empty($select)) { 01003 $select = '1 = 2'; 01004 $params = array(); 01005 } 01006 return $this->get_recordset_select($table, $select, $params, $sort, $fields, $limitfrom, $limitnum); 01007 } 01008 01027 public function get_recordset_select($table, $select, array $params=null, $sort='', $fields='*', $limitfrom=0, $limitnum=0) { 01028 $sql = "SELECT $fields FROM {".$table."}"; 01029 if ($select) { 01030 $sql .= " WHERE $select"; 01031 } 01032 if ($sort) { 01033 $sql .= " ORDER BY $sort"; 01034 } 01035 return $this->get_recordset_sql($sql, $params, $limitfrom, $limitnum); 01036 } 01037 01054 public abstract function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0); 01055 01076 public function get_records($table, array $conditions=null, $sort='', $fields='*', $limitfrom=0, $limitnum=0) { 01077 list($select, $params) = $this->where_clause($table, $conditions); 01078 return $this->get_records_select($table, $select, $params, $sort, $fields, $limitfrom, $limitnum); 01079 } 01080 01096 public function get_records_list($table, $field, array $values, $sort='', $fields='*', $limitfrom=0, $limitnum=0) { 01097 list($select, $params) = $this->where_clause_list($field, $values); 01098 if (empty($select)) { 01099 // nothing to return 01100 return array(); 01101 } 01102 return $this->get_records_select($table, $select, $params, $sort, $fields, $limitfrom, $limitnum); 01103 } 01104 01122 public function get_records_select($table, $select, array $params=null, $sort='', $fields='*', $limitfrom=0, $limitnum=0) { 01123 if ($select) { 01124 $select = "WHERE $select"; 01125 } 01126 if ($sort) { 01127 $sort = " ORDER BY $sort"; 01128 } 01129 return $this->get_records_sql("SELECT $fields FROM {" . $table . "} $select $sort", $params, $limitfrom, $limitnum); 01130 } 01131 01146 public abstract function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0); 01147 01167 public function get_records_menu($table, array $conditions=null, $sort='', $fields='*', $limitfrom=0, $limitnum=0) { 01168 $menu = array(); 01169 if ($records = $this->get_records($table, $conditions, $sort, $fields, $limitfrom, $limitnum)) { 01170 foreach ($records as $record) { 01171 $record = (array)$record; 01172 $key = array_shift($record); 01173 $value = array_shift($record); 01174 $menu[$key] = $value; 01175 } 01176 } 01177 return $menu; 01178 } 01179 01196 public function get_records_select_menu($table, $select, array $params=null, $sort='', $fields='*', $limitfrom=0, $limitnum=0) { 01197 $menu = array(); 01198 if ($records = $this->get_records_select($table, $select, $params, $sort, $fields, $limitfrom, $limitnum)) { 01199 foreach ($records as $record) { 01200 $record = (array)$record; 01201 $key = array_shift($record); 01202 $value = array_shift($record); 01203 $menu[$key] = $value; 01204 } 01205 } 01206 return $menu; 01207 } 01208 01222 public function get_records_sql_menu($sql, array $params=null, $limitfrom=0, $limitnum=0) { 01223 $menu = array(); 01224 if ($records = $this->get_records_sql($sql, $params, $limitfrom, $limitnum)) { 01225 foreach ($records as $record) { 01226 $record = (array)$record; 01227 $key = array_shift($record); 01228 $value = array_shift($record); 01229 $menu[$key] = $value; 01230 } 01231 } 01232 return $menu; 01233 } 01234 01247 public function get_record($table, array $conditions, $fields='*', $strictness=IGNORE_MISSING) { 01248 list($select, $params) = $this->where_clause($table, $conditions); 01249 return $this->get_record_select($table, $select, $params, $fields, $strictness); 01250 } 01251 01264 public function get_record_select($table, $select, array $params=null, $fields='*', $strictness=IGNORE_MISSING) { 01265 if ($select) { 01266 $select = "WHERE $select"; 01267 } 01268 try { 01269 return $this->get_record_sql("SELECT $fields FROM {" . $table . "} $select", $params, $strictness); 01270 } catch (dml_missing_record_exception $e) { 01271 // create new exception which will contain correct table name 01272 throw new dml_missing_record_exception($table, $e->sql, $e->params); 01273 } 01274 } 01275 01290 public function get_record_sql($sql, array $params=null, $strictness=IGNORE_MISSING) { 01291 $strictness = (int)$strictness; // we support true/false for BC reasons too 01292 if ($strictness == IGNORE_MULTIPLE) { 01293 $count = 1; 01294 } else { 01295 $count = 0; 01296 } 01297 if (!$records = $this->get_records_sql($sql, $params, 0, $count)) { 01298 // not found 01299 if ($strictness == MUST_EXIST) { 01300 throw new dml_missing_record_exception('', $sql, $params); 01301 } 01302 return false; 01303 } 01304 01305 if (count($records) > 1) { 01306 if ($strictness == MUST_EXIST) { 01307 throw new dml_multiple_records_exception($sql, $params); 01308 } 01309 debugging('Error: mdb->get_record() found more than one record!'); 01310 } 01311 01312 $return = reset($records); 01313 return $return; 01314 } 01315 01328 public function get_field($table, $return, array $conditions, $strictness=IGNORE_MISSING) { 01329 list($select, $params) = $this->where_clause($table, $conditions); 01330 return $this->get_field_select($table, $return, $select, $params, $strictness); 01331 } 01332 01346 public function get_field_select($table, $return, $select, array $params=null, $strictness=IGNORE_MISSING) { 01347 if ($select) { 01348 $select = "WHERE $select"; 01349 } 01350 try { 01351 return $this->get_field_sql("SELECT $return FROM {" . $table . "} $select", $params, $strictness); 01352 } catch (dml_missing_record_exception $e) { 01353 // create new exception which will contain correct table name 01354 throw new dml_missing_record_exception($table, $e->sql, $e->params); 01355 } 01356 } 01357 01371 public function get_field_sql($sql, array $params=null, $strictness=IGNORE_MISSING) { 01372 if (!$record = $this->get_record_sql($sql, $params, $strictness)) { 01373 return false; 01374 } 01375 01376 $record = (array)$record; 01377 return reset($record); // first column 01378 } 01379 01390 public function get_fieldset_select($table, $return, $select, array $params=null) { 01391 if ($select) { 01392 $select = "WHERE $select"; 01393 } 01394 return $this->get_fieldset_sql("SELECT $return FROM {" . $table . "} $select", $params); 01395 } 01396 01405 public abstract function get_fieldset_sql($sql, array $params=null); 01406 01417 public abstract function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false); 01418 01431 public abstract function insert_record($table, $dataobject, $returnid=true, $bulk=false); 01432 01442 public abstract function import_record($table, $dataobject); 01443 01452 public abstract function update_record_raw($table, $params, $bulk=false); 01453 01467 public abstract function update_record($table, $dataobject, $bulk=false); 01468 01469 01480 public function set_field($table, $newfield, $newvalue, array $conditions=null) { 01481 list($select, $params) = $this->where_clause($table, $conditions); 01482 return $this->set_field_select($table, $newfield, $newvalue, $select, $params); 01483 } 01484 01496 public abstract function set_field_select($table, $newfield, $newvalue, $select, array $params=null); 01497 01498 01507 public function count_records($table, array $conditions=null) { 01508 list($select, $params) = $this->where_clause($table, $conditions); 01509 return $this->count_records_select($table, $select, $params); 01510 } 01511 01522 public function count_records_select($table, $select, array $params=null, $countitem="COUNT('x')") { 01523 if ($select) { 01524 $select = "WHERE $select"; 01525 } 01526 return $this->count_records_sql("SELECT $countitem FROM {" . $table . "} $select", $params); 01527 } 01528 01542 public function count_records_sql($sql, array $params=null) { 01543 if ($count = $this->get_field_sql($sql, $params)) { 01544 return $count; 01545 } else { 01546 return 0; 01547 } 01548 } 01549 01561 public function record_exists($table, array $conditions) { 01562 list($select, $params) = $this->where_clause($table, $conditions); 01563 return $this->record_exists_select($table, $select, $params); 01564 } 01565 01575 public function record_exists_select($table, $select, array $params=null) { 01576 if ($select) { 01577 $select = "WHERE $select"; 01578 } 01579 return $this->record_exists_sql("SELECT 'x' FROM {" . $table . "} $select", $params); 01580 } 01581 01593 public function record_exists_sql($sql, array $params=null) { 01594 $mrs = $this->get_recordset_sql($sql, $params, 0, 1); 01595 $return = $mrs->valid(); 01596 $mrs->close(); 01597 return $return; 01598 } 01599 01609 public function delete_records($table, array $conditions=null) { 01610 // truncate is drop/create (DDL), not transactional safe, 01611 // so we don't use the shortcut within them. MDL-29198 01612 if (is_null($conditions) && empty($this->transactions)) { 01613 return $this->execute("TRUNCATE TABLE {".$table."}"); 01614 } 01615 list($select, $params) = $this->where_clause($table, $conditions); 01616 return $this->delete_records_select($table, $select, $params); 01617 } 01618 01628 public function delete_records_list($table, $field, array $values) { 01629 list($select, $params) = $this->where_clause_list($field, $values); 01630 if (empty($select)) { 01631 // nothing to delete 01632 return true; 01633 } 01634 return $this->delete_records_select($table, $select, $params); 01635 } 01636 01646 public abstract function delete_records_select($table, $select, array $params=null); 01647 01648 01649 01651 01658 public function sql_null_from_clause() { 01659 return ''; 01660 } 01661 01673 public function sql_bitand($int1, $int2) { 01674 return '((' . $int1 . ') & (' . $int2 . '))'; 01675 } 01676 01684 public function sql_bitnot($int1) { 01685 return '(~(' . $int1 . '))'; 01686 } 01687 01699 public function sql_bitor($int1, $int2) { 01700 return '((' . $int1 . ') | (' . $int2 . '))'; 01701 } 01702 01714 public function sql_bitxor($int1, $int2) { 01715 return '((' . $int1 . ') ^ (' . $int2 . '))'; 01716 } 01717 01726 public function sql_modulo($int1, $int2) { 01727 return '((' . $int1 . ') % (' . $int2 . '))'; 01728 } 01729 01737 public function sql_ceil($fieldname) { 01738 return ' CEIL(' . $fieldname . ')'; 01739 } 01740 01751 public function sql_cast_char2int($fieldname, $text=false) { 01752 return ' ' . $fieldname . ' '; 01753 } 01754 01765 public function sql_cast_char2real($fieldname, $text=false) { 01766 return ' ' . $fieldname . ' '; 01767 } 01768 01778 public function sql_cast_2signed($fieldname) { 01779 return ' ' . $fieldname . ' '; 01780 } 01781 01791 public function sql_compare_text($fieldname, $numchars=32) { 01792 return $this->sql_order_by_text($fieldname, $numchars); 01793 } 01794 01806 public function sql_like($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notlike = false, $escapechar = '\\') { 01807 if (strpos($param, '%') !== false) { 01808 debugging('Potential SQL injection detected, sql_like() expects bound parameters (? or :named)'); 01809 } 01810 $LIKE = $notlike ? 'NOT LIKE' : 'LIKE'; 01811 // by default ignore any sensitiveness - each database does it in a different way 01812 return "$fieldname $LIKE $param ESCAPE '$escapechar'"; 01813 } 01814 01821 public function sql_like_escape($text, $escapechar = '\\') { 01822 $text = str_replace('_', $escapechar.'_', $text); 01823 $text = str_replace('%', $escapechar.'%', $text); 01824 return $text; 01825 } 01826 01837 public function sql_ilike() { 01838 debugging('sql_ilike() is deprecated, please use sql_like() instead'); 01839 return 'LIKE'; 01840 } 01841 01850 public abstract function sql_concat(); 01851 01860 public abstract function sql_concat_join($separator="' '", $elements=array()); 01861 01871 function sql_fullname($first='firstname', $last='lastname') { 01872 return $this->sql_concat($first, "' '", $last); 01873 } 01874 01886 public function sql_order_by_text($fieldname, $numchars=32) { 01887 return $fieldname; 01888 } 01889 01895 public function sql_length($fieldname) { 01896 return ' LENGTH(' . $fieldname . ')'; 01897 } 01898 01908 public function sql_substr($expr, $start, $length=false) { 01909 if (count(func_get_args()) < 2) { 01910 throw new coding_exception('moodle_database::sql_substr() requires at least two parameters', 'Originally this function was only returning name of SQL substring function, it now requires all parameters.'); 01911 } 01912 if ($length === false) { 01913 return "SUBSTR($expr, $start)"; 01914 } else { 01915 return "SUBSTR($expr, $start, $length)"; 01916 } 01917 } 01918 01928 public function sql_position($needle, $haystack) { 01929 // Implementation using standard SQL. 01930 return "POSITION(($needle) IN ($haystack))"; 01931 } 01932 01939 function sql_empty() { 01940 return ''; 01941 } 01942 01972 public function sql_isempty($tablename, $fieldname, $nullablefield, $textfield) { 01973 return " ($fieldname = '') "; 01974 } 01975 02002 public function sql_isnotempty($tablename, $fieldname, $nullablefield, $textfield) { 02003 return ' ( NOT ' . $this->sql_isempty($tablename, $fieldname, $nullablefield, $textfield) . ') '; 02004 } 02005 02010 public function sql_regex_supported() { 02011 return false; 02012 } 02013 02019 public function sql_regex($positivematch=true) { 02020 return ''; 02021 } 02022 02024 02034 protected function transactions_supported() { 02035 // protected for now, this might be changed to public if really necessary 02036 return true; 02037 } 02038 02043 public function is_transaction_started() { 02044 return !empty($this->transactions); 02045 } 02046 02052 public function transactions_forbidden() { 02053 if ($this->is_transaction_started()) { 02054 throw new dml_transaction_exception('This code can not be excecuted in transaction'); 02055 } 02056 } 02057 02075 public function start_delegated_transaction() { 02076 $transaction = new moodle_transaction($this); 02077 $this->transactions[] = $transaction; 02078 if (count($this->transactions) == 1) { 02079 $this->begin_transaction(); 02080 } 02081 return $transaction; 02082 } 02083 02089 protected abstract function begin_transaction(); 02090 02097 public function commit_delegated_transaction(moodle_transaction $transaction) { 02098 if ($transaction->is_disposed()) { 02099 throw new dml_transaction_exception('Transactions already disposed', $transaction); 02100 } 02101 // mark as disposed so that it can not be used again 02102 $transaction->dispose(); 02103 02104 if (empty($this->transactions)) { 02105 throw new dml_transaction_exception('Transaction not started', $transaction); 02106 } 02107 02108 if ($this->force_rollback) { 02109 throw new dml_transaction_exception('Tried to commit transaction after lower level rollback', $transaction); 02110 } 02111 02112 if ($transaction !== $this->transactions[count($this->transactions) - 1]) { 02113 // one incorrect commit at any level rollbacks everything 02114 $this->force_rollback = true; 02115 throw new dml_transaction_exception('Invalid transaction commit attempt', $transaction); 02116 } 02117 02118 if (count($this->transactions) == 1) { 02119 // only commit the top most level 02120 $this->commit_transaction(); 02121 } 02122 array_pop($this->transactions); 02123 } 02124 02130 protected abstract function commit_transaction(); 02131 02144 public function rollback_delegated_transaction(moodle_transaction $transaction, Exception $e) { 02145 if ($transaction->is_disposed()) { 02146 throw new dml_transaction_exception('Transactions already disposed', $transaction); 02147 } 02148 // mark as disposed so that it can not be used again 02149 $transaction->dispose(); 02150 02151 // one rollback at any level rollbacks everything 02152 $this->force_rollback = true; 02153 02154 if (empty($this->transactions) or $transaction !== $this->transactions[count($this->transactions) - 1]) { 02155 // this may or may not be a coding problem, better just rethrow the exception, 02156 // because we do not want to loose the original $e 02157 throw $e; 02158 } 02159 02160 if (count($this->transactions) == 1) { 02161 // only rollback the top most level 02162 $this->rollback_transaction(); 02163 } 02164 array_pop($this->transactions); 02165 if (empty($this->transactions)) { 02166 // finally top most level rolled back 02167 $this->force_rollback = false; 02168 } 02169 throw $e; 02170 } 02171 02177 protected abstract function rollback_transaction(); 02178 02188 public function force_transaction_rollback() { 02189 if ($this->transactions) { 02190 try { 02191 $this->rollback_transaction(); 02192 } catch (dml_exception $e) { 02193 // ignore any sql errors here, the connection might be broken 02194 } 02195 } 02196 02197 // now enable transactions again 02198 $this->transactions = array(); // unfortunately all unfinished exceptions are kept in memory 02199 $this->force_rollback = false; 02200 } 02201 02203 02207 public function session_lock_supported() { 02208 return false; 02209 } 02210 02217 public function get_session_lock($rowid, $timeout) { 02218 $this->used_for_db_sessions = true; 02219 } 02220 02226 public function release_session_lock($rowid) { 02227 } 02228 02230 02234 public function perf_get_reads() { 02235 return $this->reads; 02236 } 02237 02242 public function perf_get_writes() { 02243 return $this->writes; 02244 } 02245 02250 public function perf_get_queries() { 02251 return $this->writes + $this->reads; 02252 } 02253 }