|
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/pgsql_native_moodle_recordset.php'); 00032 require_once($CFG->libdir.'/dml/pgsql_native_moodle_temptables.php'); 00033 00037 class pgsql_native_moodle_database extends moodle_database { 00038 00039 protected $pgsql = null; 00040 protected $bytea_oid = null; 00041 00042 protected $last_error_reporting; // To handle pgsql driver default verbosity 00043 00049 public function driver_installed() { 00050 if (!extension_loaded('pgsql')) { 00051 return get_string('pgsqlextensionisnotpresentinphp', 'install'); 00052 } 00053 return true; 00054 } 00055 00061 public function get_dbfamily() { 00062 return 'postgres'; 00063 } 00064 00070 protected function get_dbtype() { 00071 return 'pgsql'; 00072 } 00073 00079 protected function get_dblibrary() { 00080 return 'native'; 00081 } 00082 00088 public function get_name() { 00089 return get_string('nativepgsql', 'install'); 00090 } 00091 00097 public function get_configuration_help() { 00098 return get_string('nativepgsqlhelp', 'install'); 00099 } 00100 00106 public function get_configuration_hints() { 00107 return get_string('databasesettingssub_postgres7', 'install'); 00108 } 00109 00122 public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) { 00123 if ($prefix == '' and !$this->external) { 00124 //Enforce prefixes for everybody but mysql 00125 throw new dml_exception('prefixcannotbeempty', $this->get_dbfamily()); 00126 } 00127 00128 $driverstatus = $this->driver_installed(); 00129 00130 if ($driverstatus !== true) { 00131 throw new dml_exception('dbdriverproblem', $driverstatus); 00132 } 00133 00134 $this->store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions); 00135 00136 $pass = addcslashes($this->dbpass, "'\\"); 00137 00138 // Unix socket connections should have lower overhead 00139 if (!empty($this->dboptions['dbsocket']) and ($this->dbhost === 'localhost' or $this->dbhost === '127.0.0.1')) { 00140 $connection = "user='$this->dbuser' password='$pass' dbname='$this->dbname'"; 00141 if (strpos($this->dboptions['dbsocket'], '/') !== false) { 00142 $connection = $connection." host='".$this->dboptions['dbsocket']."'"; 00143 } 00144 } else { 00145 $this->dboptions['dbsocket'] = ''; 00146 if (empty($this->dbname)) { 00147 // probably old style socket connection - do not add port 00148 $port = ""; 00149 } else if (empty($this->dboptions['dbport'])) { 00150 $port = "port ='5432'"; 00151 } else { 00152 $port = "port ='".$this->dboptions['dbport']."'"; 00153 } 00154 $connection = "host='$this->dbhost' $port user='$this->dbuser' password='$pass' dbname='$this->dbname'"; 00155 } 00156 00157 ob_start(); 00158 if (empty($this->dboptions['dbpersist'])) { 00159 $this->pgsql = pg_connect($connection, PGSQL_CONNECT_FORCE_NEW); 00160 } else { 00161 $this->pgsql = pg_pconnect($connection, PGSQL_CONNECT_FORCE_NEW); 00162 } 00163 $dberr = ob_get_contents(); 00164 ob_end_clean(); 00165 00166 $status = pg_connection_status($this->pgsql); 00167 00168 if ($status === false or $status === PGSQL_CONNECTION_BAD) { 00169 $this->pgsql = null; 00170 throw new dml_connection_exception($dberr); 00171 } 00172 00173 $this->query_start("--pg_set_client_encoding()", null, SQL_QUERY_AUX); 00174 pg_set_client_encoding($this->pgsql, 'utf8'); 00175 $this->query_end(true); 00176 00177 // find out the bytea oid 00178 $sql = "SELECT oid FROM pg_type WHERE typname = 'bytea'"; 00179 $this->query_start($sql, null, SQL_QUERY_AUX); 00180 $result = pg_query($this->pgsql, $sql); 00181 $this->query_end($result); 00182 00183 $this->bytea_oid = pg_fetch_result($result, 0, 0); 00184 pg_free_result($result); 00185 if ($this->bytea_oid === false) { 00186 $this->pgsql = null; 00187 throw new dml_connection_exception('Can not read bytea type.'); 00188 } 00189 00190 // Connection stabilised and configured, going to instantiate the temptables controller 00191 $this->temptables = new pgsql_native_moodle_temptables($this); 00192 00193 return true; 00194 } 00195 00201 public function dispose() { 00202 parent::dispose(); // Call parent dispose to write/close session and other common stuff before closing connection 00203 if ($this->pgsql) { 00204 pg_close($this->pgsql); 00205 $this->pgsql = null; 00206 } 00207 } 00208 00209 00218 protected function query_start($sql, array $params=null, $type, $extrainfo=null) { 00219 parent::query_start($sql, $params, $type, $extrainfo); 00220 // pgsql driver tents to send debug to output, we do not need that ;-) 00221 $this->last_error_reporting = error_reporting(0); 00222 } 00223 00229 protected function query_end($result) { 00230 // reset original debug level 00231 error_reporting($this->last_error_reporting); 00232 parent::query_end($result); 00233 } 00234 00239 public function get_server_info() { 00240 static $info; 00241 if (!$info) { 00242 $this->query_start("--pg_version()", null, SQL_QUERY_AUX); 00243 $info = pg_version($this->pgsql); 00244 $this->query_end(true); 00245 } 00246 return array('description'=>$info['server'], 'version'=>$info['server']); 00247 } 00248 00249 protected function is_min_version($version) { 00250 $server = $this->get_server_info(); 00251 $server = $server['version']; 00252 return version_compare($server, $version, '>='); 00253 } 00254 00259 protected function allowed_param_types() { 00260 return SQL_PARAMS_DOLLAR; 00261 } 00262 00267 public function get_last_error() { 00268 return pg_last_error($this->pgsql); 00269 } 00270 00275 public function get_tables($usecache=true) { 00276 if ($usecache and $this->tables !== null) { 00277 return $this->tables; 00278 } 00279 $this->tables = array(); 00280 $prefix = str_replace('_', '|_', $this->prefix); 00281 // Get them from information_schema instead of catalog as far as 00282 // we want to get only own session temp objects (catalog returns all) 00283 $sql = "SELECT table_name 00284 FROM information_schema.tables 00285 WHERE table_name LIKE '$prefix%' ESCAPE '|' 00286 AND table_type IN ('BASE TABLE', 'LOCAL TEMPORARY')"; 00287 $this->query_start($sql, null, SQL_QUERY_AUX); 00288 $result = pg_query($this->pgsql, $sql); 00289 $this->query_end($result); 00290 00291 if ($result) { 00292 while ($row = pg_fetch_row($result)) { 00293 $tablename = reset($row); 00294 if (strpos($tablename, $this->prefix) !== 0) { 00295 continue; 00296 } 00297 $tablename = substr($tablename, strlen($this->prefix)); 00298 $this->tables[$tablename] = $tablename; 00299 } 00300 pg_free_result($result); 00301 } 00302 return $this->tables; 00303 } 00304 00309 public function get_indexes($table) { 00310 $indexes = array(); 00311 $tablename = $this->prefix.$table; 00312 00313 $sql = "SELECT * 00314 FROM pg_catalog.pg_indexes 00315 WHERE tablename = '$tablename'"; 00316 00317 $this->query_start($sql, null, SQL_QUERY_AUX); 00318 $result = pg_query($this->pgsql, $sql); 00319 $this->query_end($result); 00320 00321 if ($result) { 00322 while ($row = pg_fetch_assoc($result)) { 00323 if (!preg_match('/CREATE (|UNIQUE )INDEX ([^\s]+) ON '.$tablename.' USING ([^\s]+) \(([^\)]+)\)/i', $row['indexdef'], $matches)) { 00324 continue; 00325 } 00326 if ($matches[4] === 'id') { 00327 continue; 00328 } 00329 $columns = explode(',', $matches[4]); 00330 $columns = array_map(array($this, 'trim_quotes'), $columns); 00331 $indexes[$row['indexname']] = array('unique'=>!empty($matches[1]), 00332 'columns'=>$columns); 00333 } 00334 pg_free_result($result); 00335 } 00336 return $indexes; 00337 } 00338 00345 public function get_columns($table, $usecache=true) { 00346 if ($usecache and isset($this->columns[$table])) { 00347 return $this->columns[$table]; 00348 } 00349 00350 $this->columns[$table] = array(); 00351 00352 $tablename = $this->prefix.$table; 00353 00354 $sql = "SELECT a.attnum, a.attname AS field, t.typname AS type, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, d.adsrc 00355 FROM pg_catalog.pg_class c 00356 JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid 00357 JOIN pg_catalog.pg_type t ON t.oid = a.atttypid 00358 LEFT JOIN pg_catalog.pg_attrdef d ON (d.adrelid = c.oid AND d.adnum = a.attnum) 00359 WHERE relkind = 'r' AND c.relname = '$tablename' AND c.reltype > 0 AND a.attnum > 0 00360 ORDER BY a.attnum"; 00361 00362 $this->query_start($sql, null, SQL_QUERY_AUX); 00363 $result = pg_query($this->pgsql, $sql); 00364 $this->query_end($result); 00365 00366 if (!$result) { 00367 return array(); 00368 } 00369 while ($rawcolumn = pg_fetch_object($result)) { 00370 00371 $info = new stdClass(); 00372 $info->name = $rawcolumn->field; 00373 $matches = null; 00374 00375 if ($rawcolumn->type === 'varchar') { 00376 $info->type = 'varchar'; 00377 $info->meta_type = 'C'; 00378 $info->max_length = $rawcolumn->atttypmod - 4; 00379 $info->scale = null; 00380 $info->not_null = ($rawcolumn->attnotnull === 't'); 00381 $info->has_default = ($rawcolumn->atthasdef === 't'); 00382 if ($info->has_default) { 00383 $parts = explode('::', $rawcolumn->adsrc); 00384 if (count($parts) > 1) { 00385 $info->default_value = reset($parts); 00386 $info->default_value = trim($info->default_value, "'"); 00387 } else { 00388 $info->default_value = $rawcolumn->adsrc; 00389 } 00390 } else { 00391 $info->default_value = null; 00392 } 00393 $info->primary_key = false; 00394 $info->binary = false; 00395 $info->unsigned = null; 00396 $info->auto_increment= false; 00397 $info->unique = null; 00398 00399 } else if (preg_match('/int(\d)/i', $rawcolumn->type, $matches)) { 00400 $info->type = 'int'; 00401 if (strpos($rawcolumn->adsrc, 'nextval') === 0) { 00402 $info->primary_key = true; 00403 $info->meta_type = 'R'; 00404 $info->unique = true; 00405 $info->auto_increment= true; 00406 $info->has_default = false; 00407 } else { 00408 $info->primary_key = false; 00409 $info->meta_type = 'I'; 00410 $info->unique = null; 00411 $info->auto_increment= false; 00412 $info->has_default = ($rawcolumn->atthasdef === 't'); 00413 } 00414 $info->max_length = $matches[1]; 00415 $info->scale = null; 00416 $info->not_null = ($rawcolumn->attnotnull === 't'); 00417 if ($info->has_default) { 00418 $info->default_value = trim($rawcolumn->adsrc, '()'); 00419 } else { 00420 $info->default_value = null; 00421 } 00422 $info->binary = false; 00423 $info->unsigned = false; 00424 00425 } else if ($rawcolumn->type === 'numeric') { 00426 $info->type = $rawcolumn->type; 00427 $info->meta_type = 'N'; 00428 $info->primary_key = false; 00429 $info->binary = false; 00430 $info->unsigned = null; 00431 $info->auto_increment= false; 00432 $info->unique = null; 00433 $info->not_null = ($rawcolumn->attnotnull === 't'); 00434 $info->has_default = ($rawcolumn->atthasdef === 't'); 00435 if ($info->has_default) { 00436 $info->default_value = trim($rawcolumn->adsrc, '()'); 00437 } else { 00438 $info->default_value = null; 00439 } 00440 $info->max_length = $rawcolumn->atttypmod >> 16; 00441 $info->scale = ($rawcolumn->atttypmod & 0xFFFF) - 4; 00442 00443 } else if (preg_match('/float(\d)/i', $rawcolumn->type, $matches)) { 00444 $info->type = 'float'; 00445 $info->meta_type = 'N'; 00446 $info->primary_key = false; 00447 $info->binary = false; 00448 $info->unsigned = null; 00449 $info->auto_increment= false; 00450 $info->unique = null; 00451 $info->not_null = ($rawcolumn->attnotnull === 't'); 00452 $info->has_default = ($rawcolumn->atthasdef === 't'); 00453 if ($info->has_default) { 00454 $info->default_value = trim($rawcolumn->adsrc, '()'); 00455 } else { 00456 $info->default_value = null; 00457 } 00458 // just guess expected number of deciaml places :-( 00459 if ($matches[1] == 8) { 00460 // total 15 digits 00461 $info->max_length = 8; 00462 $info->scale = 7; 00463 } else { 00464 // total 6 digits 00465 $info->max_length = 4; 00466 $info->scale = 2; 00467 } 00468 00469 } else if ($rawcolumn->type === 'text') { 00470 $info->type = $rawcolumn->type; 00471 $info->meta_type = 'X'; 00472 $info->max_length = -1; 00473 $info->scale = null; 00474 $info->not_null = ($rawcolumn->attnotnull === 't'); 00475 $info->has_default = ($rawcolumn->atthasdef === 't'); 00476 if ($info->has_default) { 00477 $parts = explode('::', $rawcolumn->adsrc); 00478 if (count($parts) > 1) { 00479 $info->default_value = reset($parts); 00480 $info->default_value = trim($info->default_value, "'"); 00481 } else { 00482 $info->default_value = $rawcolumn->adsrc; 00483 } 00484 } else { 00485 $info->default_value = null; 00486 } 00487 $info->primary_key = false; 00488 $info->binary = false; 00489 $info->unsigned = null; 00490 $info->auto_increment= false; 00491 $info->unique = null; 00492 00493 } else if ($rawcolumn->type === 'bytea') { 00494 $info->type = $rawcolumn->type; 00495 $info->meta_type = 'B'; 00496 $info->max_length = -1; 00497 $info->scale = null; 00498 $info->not_null = ($rawcolumn->attnotnull === 't'); 00499 $info->has_default = false; 00500 $info->default_value = null; 00501 $info->primary_key = false; 00502 $info->binary = true; 00503 $info->unsigned = null; 00504 $info->auto_increment= false; 00505 $info->unique = null; 00506 00507 } 00508 00509 $this->columns[$table][$info->name] = new database_column_info($info); 00510 } 00511 00512 pg_free_result($result); 00513 00514 return $this->columns[$table]; 00515 } 00516 00524 protected function normalise_value($column, $value) { 00525 if (is_bool($value)) { // Always, convert boolean to int 00526 $value = (int)$value; 00527 00528 } else if ($column->meta_type === 'B') { // BLOB detected, we return 'blob' array instead of raw value to allow 00529 if (!is_null($value)) { // binding/executing code later to know about its nature 00530 $value = array('blob' => $value); 00531 } 00532 00533 } else if ($value === '') { 00534 if ($column->meta_type === 'I' or $column->meta_type === 'F' or $column->meta_type === 'N') { 00535 $value = 0; // prevent '' problems in numeric fields 00536 } 00537 } 00538 return $value; 00539 } 00540 00545 public function setup_is_unicodedb() { 00547 $sql = "SHOW server_encoding"; 00548 $this->query_start($sql, null, SQL_QUERY_AUX); 00549 $result = pg_query($this->pgsql, $sql); 00550 $this->query_end($result); 00551 00552 if (!$result) { 00553 return false; 00554 } 00555 $rawcolumn = pg_fetch_object($result); 00556 $encoding = $rawcolumn->server_encoding; 00557 pg_free_result($result); 00558 00559 return (strtoupper($encoding) == 'UNICODE' || strtoupper($encoding) == 'UTF8'); 00560 } 00561 00568 public function change_database_structure($sql) { 00569 $this->reset_caches(); 00570 00571 $this->query_start($sql, null, SQL_QUERY_STRUCTURE); 00572 $result = pg_query($this->pgsql, $sql); 00573 $this->query_end($result); 00574 00575 pg_free_result($result); 00576 return true; 00577 } 00578 00587 public function execute($sql, array $params=null) { 00588 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 00589 00590 if (strpos($sql, ';') !== false) { 00591 throw new coding_exception('moodle_database::execute() Multiple sql statements found or bound parameters not used properly in query!'); 00592 } 00593 00594 $this->query_start($sql, $params, SQL_QUERY_UPDATE); 00595 $result = pg_query_params($this->pgsql, $sql, $params); 00596 $this->query_end($result); 00597 00598 pg_free_result($result); 00599 return true; 00600 } 00601 00618 public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) { 00619 $limitfrom = (int)$limitfrom; 00620 $limitnum = (int)$limitnum; 00621 $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom; 00622 $limitnum = ($limitnum < 0) ? 0 : $limitnum; 00623 if ($limitfrom or $limitnum) { 00624 if ($limitnum < 1) { 00625 $limitnum = "ALL"; 00626 } else if (PHP_INT_MAX - $limitnum < $limitfrom) { 00627 // this is a workaround for weird max int problem 00628 $limitnum = "ALL"; 00629 } 00630 $sql .= " LIMIT $limitnum OFFSET $limitfrom"; 00631 } 00632 00633 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 00634 00635 $this->query_start($sql, $params, SQL_QUERY_SELECT); 00636 $result = pg_query_params($this->pgsql, $sql, $params); 00637 $this->query_end($result); 00638 00639 return $this->create_recordset($result); 00640 } 00641 00642 protected function create_recordset($result) { 00643 return new pgsql_native_moodle_recordset($result, $this->bytea_oid); 00644 } 00645 00660 public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) { 00661 $limitfrom = (int)$limitfrom; 00662 $limitnum = (int)$limitnum; 00663 $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom; 00664 $limitnum = ($limitnum < 0) ? 0 : $limitnum; 00665 if ($limitfrom or $limitnum) { 00666 if ($limitnum < 1) { 00667 $limitnum = "ALL"; 00668 } else if (PHP_INT_MAX - $limitnum < $limitfrom) { 00669 // this is a workaround for weird max int problem 00670 $limitnum = "ALL"; 00671 } 00672 $sql .= " LIMIT $limitnum OFFSET $limitfrom"; 00673 } 00674 00675 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 00676 $this->query_start($sql, $params, SQL_QUERY_SELECT); 00677 $result = pg_query_params($this->pgsql, $sql, $params); 00678 $this->query_end($result); 00679 00680 // find out if there are any blobs 00681 $numrows = pg_num_fields($result); 00682 $blobs = array(); 00683 for($i=0; $i<$numrows; $i++) { 00684 $type_oid = pg_field_type_oid($result, $i); 00685 if ($type_oid == $this->bytea_oid) { 00686 $blobs[] = pg_field_name($result, $i); 00687 } 00688 } 00689 00690 $rows = pg_fetch_all($result); 00691 pg_free_result($result); 00692 00693 $return = array(); 00694 if ($rows) { 00695 foreach ($rows as $row) { 00696 $id = reset($row); 00697 if ($blobs) { 00698 foreach ($blobs as $blob) { 00699 // note: in PostgreSQL 9.0 the returned blobs are hexencoded by default - see http://www.postgresql.org/docs/9.0/static/runtime-config-client.html#GUC-BYTEA-OUTPUT 00700 $row[$blob] = $row[$blob] !== null ? pg_unescape_bytea($row[$blob]) : null; 00701 } 00702 } 00703 if (isset($return[$id])) { 00704 $colname = key($row); 00705 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); 00706 } 00707 $return[$id] = (object)$row; 00708 } 00709 } 00710 00711 return $return; 00712 } 00713 00722 public function get_fieldset_sql($sql, array $params=null) { 00723 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 00724 00725 $this->query_start($sql, $params, SQL_QUERY_SELECT); 00726 $result = pg_query_params($this->pgsql, $sql, $params); 00727 $this->query_end($result); 00728 00729 $return = pg_fetch_all_columns($result, 0); 00730 pg_free_result($result); 00731 00732 return $return; 00733 } 00734 00745 public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) { 00746 if (!is_array($params)) { 00747 $params = (array)$params; 00748 } 00749 00750 $returning = ""; 00751 00752 if ($customsequence) { 00753 if (!isset($params['id'])) { 00754 throw new coding_exception('moodle_database::insert_record_raw() id field must be specified if custom sequences used.'); 00755 } 00756 $returnid = false; 00757 } else { 00758 if ($returnid) { 00759 $returning = "RETURNING id"; 00760 unset($params['id']); 00761 } else { 00762 unset($params['id']); 00763 } 00764 } 00765 00766 if (empty($params)) { 00767 throw new coding_exception('moodle_database::insert_record_raw() no fields found.'); 00768 } 00769 00770 $fields = implode(',', array_keys($params)); 00771 $values = array(); 00772 $count = count($params); 00773 for ($i=1; $i<=$count; $i++) { 00774 $values[] = "\$".$i; 00775 } 00776 $values = implode(',', $values); 00777 00778 $sql = "INSERT INTO {$this->prefix}$table ($fields) VALUES($values) $returning"; 00779 $this->query_start($sql, $params, SQL_QUERY_INSERT); 00780 $result = pg_query_params($this->pgsql, $sql, $params); 00781 $this->query_end($result); 00782 00783 if ($returning !== "") { 00784 $row = pg_fetch_assoc($result); 00785 $params['id'] = reset($row); 00786 } 00787 pg_free_result($result); 00788 00789 if (!$returnid) { 00790 return true; 00791 } 00792 00793 return (int)$params['id']; 00794 } 00795 00808 public function insert_record($table, $dataobject, $returnid=true, $bulk=false) { 00809 $dataobject = (array)$dataobject; 00810 00811 $columns = $this->get_columns($table); 00812 $cleaned = array(); 00813 $blobs = array(); 00814 00815 foreach ($dataobject as $field=>$value) { 00816 if ($field === 'id') { 00817 continue; 00818 } 00819 if (!isset($columns[$field])) { 00820 continue; 00821 } 00822 $column = $columns[$field]; 00823 $normalised_value = $this->normalise_value($column, $value); 00824 if (is_array($normalised_value) && array_key_exists('blob', $normalised_value)) { 00825 $cleaned[$field] = '@#BLOB#@'; 00826 $blobs[$field] = $normalised_value['blob']; 00827 } else { 00828 $cleaned[$field] = $normalised_value; 00829 } 00830 } 00831 00832 if (empty($blobs)) { 00833 return $this->insert_record_raw($table, $cleaned, $returnid, $bulk); 00834 } 00835 00836 $id = $this->insert_record_raw($table, $cleaned, true, $bulk); 00837 00838 foreach ($blobs as $key=>$value) { 00839 $value = pg_escape_bytea($this->pgsql, $value); 00840 $sql = "UPDATE {$this->prefix}$table SET $key = '$value'::bytea WHERE id = $id"; 00841 $this->query_start($sql, NULL, SQL_QUERY_UPDATE); 00842 $result = pg_query($this->pgsql, $sql); 00843 $this->query_end($result); 00844 if ($result !== false) { 00845 pg_free_result($result); 00846 } 00847 } 00848 00849 return ($returnid ? $id : true); 00850 00851 } 00852 00862 public function import_record($table, $dataobject) { 00863 $dataobject = (array)$dataobject; 00864 00865 $columns = $this->get_columns($table); 00866 $cleaned = array(); 00867 $blobs = array(); 00868 00869 foreach ($dataobject as $field=>$value) { 00870 if (!isset($columns[$field])) { 00871 continue; 00872 } 00873 if ($columns[$field]->meta_type === 'B') { 00874 if (!is_null($value)) { 00875 $cleaned[$field] = '@#BLOB#@'; 00876 $blobs[$field] = $value; 00877 continue; 00878 } 00879 } 00880 00881 $cleaned[$field] = $value; 00882 } 00883 00884 $this->insert_record_raw($table, $cleaned, false, true, true); 00885 $id = $dataobject['id']; 00886 00887 foreach ($blobs as $key=>$value) { 00888 $value = pg_escape_bytea($this->pgsql, $value); 00889 $sql = "UPDATE {$this->prefix}$table SET $key = '$value'::bytea WHERE id = $id"; 00890 $this->query_start($sql, NULL, SQL_QUERY_UPDATE); 00891 $result = pg_query($this->pgsql, $sql); 00892 $this->query_end($result); 00893 if ($result !== false) { 00894 pg_free_result($result); 00895 } 00896 } 00897 00898 return true; 00899 } 00900 00909 public function update_record_raw($table, $params, $bulk=false) { 00910 $params = (array)$params; 00911 00912 if (!isset($params['id'])) { 00913 throw new coding_exception('moodle_database::update_record_raw() id field must be specified.'); 00914 } 00915 $id = $params['id']; 00916 unset($params['id']); 00917 00918 if (empty($params)) { 00919 throw new coding_exception('moodle_database::update_record_raw() no fields found.'); 00920 } 00921 00922 $i = 1; 00923 00924 $sets = array(); 00925 foreach ($params as $field=>$value) { 00926 $sets[] = "$field = \$".$i++; 00927 } 00928 00929 $params[] = $id; // last ? in WHERE condition 00930 00931 $sets = implode(',', $sets); 00932 $sql = "UPDATE {$this->prefix}$table SET $sets WHERE id=\$".$i; 00933 00934 $this->query_start($sql, $params, SQL_QUERY_UPDATE); 00935 $result = pg_query_params($this->pgsql, $sql, $params); 00936 $this->query_end($result); 00937 00938 pg_free_result($result); 00939 return true; 00940 } 00941 00955 public function update_record($table, $dataobject, $bulk=false) { 00956 $dataobject = (array)$dataobject; 00957 00958 $columns = $this->get_columns($table); 00959 $cleaned = array(); 00960 $blobs = array(); 00961 00962 foreach ($dataobject as $field=>$value) { 00963 if (!isset($columns[$field])) { 00964 continue; 00965 } 00966 $column = $columns[$field]; 00967 $normalised_value = $this->normalise_value($column, $value); 00968 if (is_array($normalised_value) && array_key_exists('blob', $normalised_value)) { 00969 $cleaned[$field] = '@#BLOB#@'; 00970 $blobs[$field] = $normalised_value['blob']; 00971 } else { 00972 $cleaned[$field] = $normalised_value; 00973 } 00974 } 00975 00976 $this->update_record_raw($table, $cleaned, $bulk); 00977 00978 if (empty($blobs)) { 00979 return true; 00980 } 00981 00982 $id = (int)$dataobject['id']; 00983 00984 foreach ($blobs as $key=>$value) { 00985 $value = pg_escape_bytea($this->pgsql, $value); 00986 $sql = "UPDATE {$this->prefix}$table SET $key = '$value'::bytea WHERE id = $id"; 00987 $this->query_start($sql, NULL, SQL_QUERY_UPDATE); 00988 $result = pg_query($this->pgsql, $sql); 00989 $this->query_end($result); 00990 00991 pg_free_result($result); 00992 } 00993 00994 return true; 00995 } 00996 01008 public function set_field_select($table, $newfield, $newvalue, $select, array $params=null) { 01009 01010 if ($select) { 01011 $select = "WHERE $select"; 01012 } 01013 if (is_null($params)) { 01014 $params = array(); 01015 } 01016 list($select, $params, $type) = $this->fix_sql_params($select, $params); 01017 $i = count($params)+1; 01018 01020 $columns = $this->get_columns($table); 01021 $column = $columns[$newfield]; 01022 01023 $normalised_value = $this->normalise_value($column, $newvalue); 01024 if (is_array($normalised_value) && array_key_exists('blob', $normalised_value)) { 01026 $normalised_value = pg_escape_bytea($this->pgsql, $normalised_value['blob']); 01027 $sql = "UPDATE {$this->prefix}$table SET $newfield = '$normalised_value'::bytea $select"; 01028 $this->query_start($sql, NULL, SQL_QUERY_UPDATE); 01029 $result = pg_query_params($this->pgsql, $sql, $params); 01030 $this->query_end($result); 01031 pg_free_result($result); 01032 return true; 01033 } 01034 01035 if (is_null($normalised_value)) { 01036 $newfield = "$newfield = NULL"; 01037 } else { 01038 $newfield = "$newfield = \$".$i; 01039 $params[] = $normalised_value; 01040 } 01041 $sql = "UPDATE {$this->prefix}$table SET $newfield $select"; 01042 01043 $this->query_start($sql, $params, SQL_QUERY_UPDATE); 01044 $result = pg_query_params($this->pgsql, $sql, $params); 01045 $this->query_end($result); 01046 01047 pg_free_result($result); 01048 01049 return true; 01050 } 01051 01061 public function delete_records_select($table, $select, array $params=null) { 01062 if ($select) { 01063 $select = "WHERE $select"; 01064 } 01065 $sql = "DELETE FROM {$this->prefix}$table $select"; 01066 01067 list($sql, $params, $type) = $this->fix_sql_params($sql, $params); 01068 01069 $this->query_start($sql, $params, SQL_QUERY_UPDATE); 01070 $result = pg_query_params($this->pgsql, $sql, $params); 01071 $this->query_end($result); 01072 01073 pg_free_result($result); 01074 01075 return true; 01076 } 01077 01089 public function sql_like($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notlike = false, $escapechar = '\\') { 01090 if (strpos($param, '%') !== false) { 01091 debugging('Potential SQL injection detected, sql_ilike() expects bound parameters (? or :named)'); 01092 } 01093 $escapechar = pg_escape_string($this->pgsql, $escapechar); // prevents problems with C-style escapes of enclosing '\' 01094 01095 // postgresql does not support accent insensitive text comparisons, sorry 01096 if ($casesensitive) { 01097 $LIKE = $notlike ? 'NOT LIKE' : 'LIKE'; 01098 } else { 01099 $LIKE = $notlike ? 'NOT ILIKE' : 'ILIKE'; 01100 } 01101 return "$fieldname $LIKE $param ESCAPE '$escapechar'"; 01102 } 01103 01104 public function sql_ilike() { 01105 debugging('sql_ilike() is deprecated, please use sql_like() instead'); 01106 return 'ILIKE'; 01107 } 01108 01109 public function sql_bitxor($int1, $int2) { 01110 return '((' . $int1 . ') # (' . $int2 . '))'; 01111 } 01112 01113 public function sql_cast_char2int($fieldname, $text=false) { 01114 return ' CAST(' . $fieldname . ' AS INT) '; 01115 } 01116 01117 public function sql_cast_char2real($fieldname, $text=false) { 01118 return " $fieldname::real "; 01119 } 01120 01121 public function sql_concat() { 01122 $arr = func_get_args(); 01123 $s = implode(' || ', $arr); 01124 if ($s === '') { 01125 return " '' "; 01126 } 01127 // Add always empty string element so integer-exclusive concats 01128 // will work without needing to cast each element explicity 01129 return " '' || $s "; 01130 } 01131 01132 public function sql_concat_join($separator="' '", $elements=array()) { 01133 for ($n=count($elements)-1; $n > 0 ; $n--) { 01134 array_splice($elements, $n, 0, $separator); 01135 } 01136 $s = implode(' || ', $elements); 01137 if ($s === '') { 01138 return " '' "; 01139 } 01140 return " $s "; 01141 } 01142 01143 public function sql_regex_supported() { 01144 return true; 01145 } 01146 01147 public function sql_regex($positivematch=true) { 01148 return $positivematch ? '~*' : '!~*'; 01149 } 01150 01152 public function session_lock_supported() { 01153 return true; 01154 } 01155 01162 public function get_session_lock($rowid, $timeout) { 01163 // NOTE: there is a potential locking problem for database running 01164 // multiple instances of moodle, we could try to use pg_advisory_lock(int, int), 01165 // luckily there is not a big chance that they would collide 01166 if (!$this->session_lock_supported()) { 01167 return; 01168 } 01169 01170 parent::get_session_lock($rowid, $timeout); 01171 01172 $timeoutmilli = $timeout * 1000; 01173 01174 $sql = "SET statement_timeout TO $timeoutmilli"; 01175 $this->query_start($sql, null, SQL_QUERY_AUX); 01176 $result = pg_query($this->pgsql, $sql); 01177 $this->query_end($result); 01178 01179 if ($result) { 01180 pg_free_result($result); 01181 } 01182 01183 $sql = "SELECT pg_advisory_lock($rowid)"; 01184 $this->query_start($sql, null, SQL_QUERY_AUX); 01185 $start = time(); 01186 $result = pg_query($this->pgsql, $sql); 01187 $end = time(); 01188 try { 01189 $this->query_end($result); 01190 } catch (dml_exception $ex) { 01191 if ($end - $start >= $timeout) { 01192 throw new dml_sessionwait_exception(); 01193 } else { 01194 throw $ex; 01195 } 01196 } 01197 01198 if ($result) { 01199 pg_free_result($result); 01200 } 01201 01202 $sql = "SET statement_timeout TO DEFAULT"; 01203 $this->query_start($sql, null, SQL_QUERY_AUX); 01204 $result = pg_query($this->pgsql, $sql); 01205 $this->query_end($result); 01206 01207 if ($result) { 01208 pg_free_result($result); 01209 } 01210 } 01211 01212 public function release_session_lock($rowid) { 01213 if (!$this->session_lock_supported()) { 01214 return; 01215 } 01216 parent::release_session_lock($rowid); 01217 01218 $sql = "SELECT pg_advisory_unlock($rowid)"; 01219 $this->query_start($sql, null, SQL_QUERY_AUX); 01220 $result = pg_query($this->pgsql, $sql); 01221 $this->query_end($result); 01222 01223 if ($result) { 01224 pg_free_result($result); 01225 } 01226 } 01227 01229 01234 protected function begin_transaction() { 01235 $sql = "BEGIN ISOLATION LEVEL READ COMMITTED"; 01236 $this->query_start($sql, NULL, SQL_QUERY_AUX); 01237 $result = pg_query($this->pgsql, $sql); 01238 $this->query_end($result); 01239 01240 pg_free_result($result); 01241 } 01242 01248 protected function commit_transaction() { 01249 $sql = "COMMIT"; 01250 $this->query_start($sql, NULL, SQL_QUERY_AUX); 01251 $result = pg_query($this->pgsql, $sql); 01252 $this->query_end($result); 01253 01254 pg_free_result($result); 01255 } 01256 01262 protected function rollback_transaction() { 01263 $sql = "ROLLBACK"; 01264 $this->query_start($sql, NULL, SQL_QUERY_AUX); 01265 $result = pg_query($this->pgsql, $sql); 01266 $this->query_end($result); 01267 01268 pg_free_result($result); 01269 } 01270 01279 private function trim_quotes($str) { 01280 return trim(trim($str), "'\""); 01281 } 01282 }