Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/lib/dml/pgsql_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/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 }
 All Data Structures Namespaces Files Functions Variables Enumerations