2009-06-12 08:14:29 +00:00
< ? php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Native pgsql class representing moodle database interface .
*
2012-06-05 10:31:03 +02:00
* @ package core_dml
2009-06-12 08:14:29 +00:00
* @ copyright 2008 Petr Skoda ( http :// skodak . org )
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
*/
2008-10-26 22:36:13 +00:00
2010-07-25 12:57:24 +00:00
defined ( 'MOODLE_INTERNAL' ) || die ();
2012-06-05 10:31:03 +02:00
require_once ( __DIR__ . '/moodle_database.php' );
require_once ( __DIR__ . '/pgsql_native_moodle_recordset.php' );
require_once ( __DIR__ . '/pgsql_native_moodle_temptables.php' );
2008-10-26 22:36:13 +00:00
/**
* Native pgsql class representing moodle database interface .
2012-01-20 14:39:49 +08:00
*
2012-06-05 10:31:03 +02:00
* @ package core_dml
2012-01-20 14:39:49 +08:00
* @ copyright 2008 Petr Skoda ( http :// skodak . org )
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
2008-10-26 22:36:13 +00:00
*/
class pgsql_native_moodle_database extends moodle_database {
2014-01-24 13:47:22 +08:00
/** @var resource $pgsql database resource */
2008-10-27 15:31:11 +00:00
protected $pgsql = null ;
2008-10-26 22:36:13 +00:00
2009-09-23 13:42:31 +00:00
protected $last_error_reporting ; // To handle pgsql driver default verbosity
2008-11-05 00:05:41 +00:00
2012-09-21 10:52:53 +02:00
/** @var bool savepoint hack for MDL-35506 - workaround for automatic transaction rollback on error */
protected $savepointpresent = false ;
2008-10-26 22:36:13 +00:00
/**
* Detects if all needed PHP stuff installed .
* Note : can be used before connect ()
* @ return mixed true if ok , string if something
*/
public function driver_installed () {
if ( ! extension_loaded ( 'pgsql' )) {
return get_string ( 'pgsqlextensionisnotpresentinphp' , 'install' );
}
return true ;
}
/**
* Returns database family type - describes SQL dialect
* Note : can be used before connect ()
* @ return string db family name ( mysql , postgres , mssql , oracle , etc . )
*/
public function get_dbfamily () {
return 'postgres' ;
}
/**
* Returns more specific database driver type
* Note : can be used before connect ()
2010-08-22 18:51:45 +00:00
* @ return string db type mysqli , pgsql , oci , mssql , sqlsrv
2008-10-26 22:36:13 +00:00
*/
protected function get_dbtype () {
return 'pgsql' ;
}
/**
* Returns general database library name
* Note : can be used before connect ()
2009-10-08 22:34:34 +00:00
* @ return string db type pdo , native
2008-10-26 22:36:13 +00:00
*/
protected function get_dblibrary () {
return 'native' ;
}
/**
* Returns localised database type name
* Note : can be used before connect ()
* @ return string
*/
public function get_name () {
2009-06-13 09:09:30 +00:00
return get_string ( 'nativepgsql' , 'install' );
2008-10-26 22:36:13 +00:00
}
2009-02-07 10:20:33 +00:00
/**
* Returns localised database configuration help .
* Note : can be used before connect ()
* @ return string
*/
public function get_configuration_help () {
return get_string ( 'nativepgsqlhelp' , 'install' );
}
2008-10-26 22:36:13 +00:00
/**
* Connect to db
* Must be called before other methods .
2012-01-19 10:15:11 +08:00
* @ param string $dbhost The database host .
* @ param string $dbuser The database username .
* @ param string $dbpass The database username ' s password .
* @ param string $dbname The name of the database being connected to .
2008-10-26 22:36:13 +00:00
* @ param mixed $prefix string means moodle db prefix , false used for external databases where prefix not used
* @ param array $dboptions driver specific options
2008-11-22 01:16:52 +00:00
* @ return bool true
* @ throws dml_connection_exception if error
2008-10-26 22:36:13 +00:00
*/
2008-10-27 22:21:34 +00:00
public function connect ( $dbhost , $dbuser , $dbpass , $dbname , $prefix , array $dboptions = null ) {
2008-12-02 10:07:03 +00:00
if ( $prefix == '' and ! $this -> external ) {
//Enforce prefixes for everybody but mysql
throw new dml_exception ( 'prefixcannotbeempty' , $this -> get_dbfamily ());
}
2008-11-22 01:16:52 +00:00
$driverstatus = $this -> driver_installed ();
if ( $driverstatus !== true ) {
throw new dml_exception ( 'dbdriverproblem' , $driverstatus );
}
2008-10-27 22:21:34 +00:00
$this -> store_settings ( $dbhost , $dbuser , $dbpass , $dbname , $prefix , $dboptions );
2008-10-26 22:36:13 +00:00
2008-10-28 12:03:04 +00:00
$pass = addcslashes ( $this -> dbpass , " ' \\ " );
// Unix socket connections should have lower overhead
2008-11-24 07:38:07 +00:00
if ( ! empty ( $this -> dboptions [ 'dbsocket' ]) and ( $this -> dbhost === 'localhost' or $this -> dbhost === '127.0.0.1' )) {
2008-10-28 12:03:04 +00:00
$connection = " user=' $this->dbuser ' password=' $pass ' dbname=' $this->dbname ' " ;
2011-01-14 12:11:49 +01:00
if ( strpos ( $this -> dboptions [ 'dbsocket' ], '/' ) !== false ) {
$connection = $connection . " host=' " . $this -> dboptions [ 'dbsocket' ] . " ' " ;
2014-03-30 10:06:39 +08:00
if ( ! empty ( $this -> dboptions [ 'dbport' ])) {
// Somehow non-standard port is important for sockets - see MDL-44862.
$connection = $connection . " port =' " . $this -> dboptions [ 'dbport' ] . " ' " ;
}
2011-01-14 12:11:49 +01:00
}
2008-10-28 12:03:04 +00:00
} else {
2011-01-14 12:11:49 +01:00
$this -> dboptions [ 'dbsocket' ] = '' ;
2008-11-24 07:38:07 +00:00
if ( empty ( $this -> dbname )) {
// probably old style socket connection - do not add port
$port = " " ;
} else if ( empty ( $this -> dboptions [ 'dbport' ])) {
$port = " port ='5432' " ;
2008-11-04 21:55:39 +00:00
} else {
2008-11-24 07:38:07 +00:00
$port = " port =' " . $this -> dboptions [ 'dbport' ] . " ' " ;
2008-11-04 21:55:39 +00:00
}
2008-11-24 07:38:07 +00:00
$connection = " host=' $this->dbhost ' $port user=' $this->dbuser ' password=' $pass ' dbname=' $this->dbname ' " ;
2008-10-28 12:03:04 +00:00
}
2016-06-14 13:47:36 +10:00
// ALTER USER and ALTER DATABASE are overridden by these settings.
$options = array ( '--client_encoding=utf8' , '--standard_conforming_strings=on' );
// Select schema if specified, otherwise the first one wins.
if ( ! empty ( $this -> dboptions [ 'dbschema' ])) {
$options [] = " -c search_path= " . addcslashes ( $this -> dboptions [ 'dbschema' ], " ' \\ " );
}
$connection .= " options=' " . implode ( ' ' , $options ) . " ' " ;
2008-11-22 01:16:52 +00:00
ob_start ();
2010-03-25 11:17:01 +00:00
if ( empty ( $this -> dboptions [ 'dbpersist' ])) {
2008-10-28 12:03:04 +00:00
$this -> pgsql = pg_connect ( $connection , PGSQL_CONNECT_FORCE_NEW );
} else {
$this -> pgsql = pg_pconnect ( $connection , PGSQL_CONNECT_FORCE_NEW );
}
2008-11-22 01:16:52 +00:00
$dberr = ob_get_contents ();
ob_end_clean ();
2009-11-01 11:31:16 +00:00
2008-10-26 22:36:13 +00:00
$status = pg_connection_status ( $this -> pgsql );
2008-10-28 15:37:15 +00:00
if ( $status === false or $status === PGSQL_CONNECTION_BAD ) {
2008-10-26 22:36:13 +00:00
$this -> pgsql = null ;
2008-11-22 01:16:52 +00:00
throw new dml_connection_exception ( $dberr );
2008-10-26 22:36:13 +00:00
}
2008-10-28 15:37:15 +00:00
2010-09-17 20:32:34 +00:00
// Connection stabilised and configured, going to instantiate the temptables controller
2010-04-21 16:12:27 +00:00
$this -> temptables = new pgsql_native_moodle_temptables ( $this );
2008-10-26 22:36:13 +00:00
return true ;
}
/**
* Close database connection and release all resources
* and memory ( especially circular memory references ) .
* Do NOT use connect () again , create a new instance if needed .
*/
public function dispose () {
2010-05-21 18:32:44 +00:00
parent :: dispose (); // Call parent dispose to write/close session and other common stuff before closing connection
2008-10-26 22:36:13 +00:00
if ( $this -> pgsql ) {
pg_close ( $this -> pgsql );
$this -> pgsql = null ;
}
}
2008-11-05 00:05:41 +00:00
/**
* Called before each db query .
* @ param string $sql
* @ param array array of parameters
* @ param int $type type of query
* @ param mixed $extrainfo driver specific extra information
* @ return void
*/
protected function query_start ( $sql , array $params = null , $type , $extrainfo = null ) {
parent :: query_start ( $sql , $params , $type , $extrainfo );
// pgsql driver tents to send debug to output, we do not need that ;-)
2009-09-23 13:42:31 +00:00
$this -> last_error_reporting = error_reporting ( 0 );
2008-11-05 00:05:41 +00:00
}
/**
* Called immediately after each db query .
* @ param mixed db specific result
* @ return void
*/
protected function query_end ( $result ) {
2009-09-23 13:42:31 +00:00
// reset original debug level
error_reporting ( $this -> last_error_reporting );
2012-09-21 10:52:53 +02:00
try {
parent :: query_end ( $result );
2012-09-26 10:39:28 +02:00
if ( $this -> savepointpresent and $this -> last_type != SQL_QUERY_AUX and $this -> last_type != SQL_QUERY_SELECT ) {
$res = @ pg_query ( $this -> pgsql , " RELEASE SAVEPOINT moodle_pg_savepoint; SAVEPOINT moodle_pg_savepoint " );
2012-09-21 10:52:53 +02:00
if ( $res ) {
pg_free_result ( $res );
}
}
} catch ( Exception $e ) {
if ( $this -> savepointpresent ) {
2012-09-26 10:39:28 +02:00
$res = @ pg_query ( $this -> pgsql , " ROLLBACK TO SAVEPOINT moodle_pg_savepoint; SAVEPOINT moodle_pg_savepoint " );
2012-09-21 10:52:53 +02:00
if ( $res ) {
pg_free_result ( $res );
}
}
throw $e ;
}
2008-11-05 00:05:41 +00:00
}
2008-10-26 22:36:13 +00:00
/**
* Returns database server info array
2012-01-19 10:15:11 +08:00
* @ return array Array containing 'description' and 'version' info
2008-10-26 22:36:13 +00:00
*/
public function get_server_info () {
static $info ;
if ( ! $info ) {
2008-10-29 23:55:16 +00:00
$this -> query_start ( " --pg_version() " , null , SQL_QUERY_AUX );
2008-10-26 22:36:13 +00:00
$info = pg_version ( $this -> pgsql );
2008-10-29 23:55:16 +00:00
$this -> query_end ( true );
2008-10-26 22:36:13 +00:00
}
return array ( 'description' => $info [ 'server' ], 'version' => $info [ 'server' ]);
}
/**
* Returns supported query parameter types
2012-01-19 10:15:11 +08:00
* @ return int bitmask of accepted SQL_PARAMS_ *
2008-10-26 22:36:13 +00:00
*/
protected function allowed_param_types () {
return SQL_PARAMS_DOLLAR ;
}
/**
* Returns last error reported by database engine .
2010-08-22 19:06:06 +00:00
* @ return string error message
2008-10-26 22:36:13 +00:00
*/
public function get_last_error () {
return pg_last_error ( $this -> pgsql );
}
/**
2012-01-19 10:15:11 +08:00
* Return tables in database WITHOUT current prefix .
* @ param bool $usecache if true , returns list of cached tables .
2008-10-26 22:36:13 +00:00
* @ return array of table names in lowercase and without prefix
*/
2009-01-12 18:10:50 +00:00
public function get_tables ( $usecache = true ) {
if ( $usecache and $this -> tables !== null ) {
return $this -> tables ;
}
$this -> tables = array ();
2011-08-07 13:19:02 +02:00
$prefix = str_replace ( '_' , '|_' , $this -> prefix );
2013-01-08 17:12:31 +01:00
$sql = " SELECT c.relname
FROM pg_catalog . pg_class c
JOIN pg_catalog . pg_namespace as ns ON ns . oid = c . relnamespace
WHERE c . relname LIKE '$prefix%' ESCAPE '|'
2012-07-02 16:14:28 +01:00
AND c . relkind = 'r'
2013-01-08 17:12:31 +01:00
AND ( ns . nspname = current_schema () OR ns . oid = pg_my_temp_schema ()) " ;
2008-10-29 23:55:16 +00:00
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = pg_query ( $this -> pgsql , $sql );
$this -> query_end ( $result );
2008-11-04 21:55:39 +00:00
2008-10-29 23:55:16 +00:00
if ( $result ) {
2008-10-26 22:36:13 +00:00
while ( $row = pg_fetch_row ( $result )) {
$tablename = reset ( $row );
2014-01-28 15:59:49 +08:00
if ( $this -> prefix !== false && $this -> prefix !== '' ) {
2012-03-18 20:50:24 +01:00
if ( strpos ( $tablename , $this -> prefix ) !== 0 ) {
continue ;
}
$tablename = substr ( $tablename , strlen ( $this -> prefix ));
2008-10-26 22:36:13 +00:00
}
2009-01-12 18:10:50 +00:00
$this -> tables [ $tablename ] = $tablename ;
2008-10-26 22:36:13 +00:00
}
pg_free_result ( $result );
}
2009-01-12 18:10:50 +00:00
return $this -> tables ;
2008-10-26 22:36:13 +00:00
}
/**
2012-01-19 10:15:11 +08:00
* Return table indexes - everything lowercased .
* @ param string $table The table we want to get indexes from .
2008-10-26 22:36:13 +00:00
* @ return array of arrays
*/
public function get_indexes ( $table ) {
$indexes = array ();
$tablename = $this -> prefix . $table ;
2013-01-08 17:12:31 +01:00
$sql = " SELECT i.*
FROM pg_catalog . pg_indexes i
JOIN pg_catalog . pg_namespace as ns ON ns . nspname = i . schemaname
WHERE i . tablename = '$tablename'
AND ( i . schemaname = current_schema () OR ns . oid = pg_my_temp_schema ()) " ;
2008-10-29 23:55:16 +00:00
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = pg_query ( $this -> pgsql , $sql );
$this -> query_end ( $result );
if ( $result ) {
2008-10-26 22:36:13 +00:00
while ( $row = pg_fetch_assoc ( $result )) {
if ( ! preg_match ( '/CREATE (|UNIQUE )INDEX ([^\s]+) ON ' . $tablename . ' USING ([^\s]+) \(([^\)]+)\)/i' , $row [ 'indexdef' ], $matches )) {
continue ;
}
if ( $matches [ 4 ] === 'id' ) {
continue ;
}
2008-10-27 12:33:09 +00:00
$columns = explode ( ',' , $matches [ 4 ]);
2012-05-20 10:50:14 +02:00
foreach ( $columns as $k => $column ) {
$column = trim ( $column );
if ( $pos = strpos ( $column , ' ' )) {
// index type is separated by space
$column = substr ( $column , 0 , $pos );
}
$columns [ $k ] = $this -> trim_quotes ( $column );
}
2010-06-16 15:46:08 +00:00
$indexes [ $row [ 'indexname' ]] = array ( 'unique' =>! empty ( $matches [ 1 ]),
2008-10-27 12:33:09 +00:00
'columns' => $columns );
2008-10-26 22:36:13 +00:00
}
pg_free_result ( $result );
}
return $indexes ;
}
/**
2010-05-21 18:32:44 +00:00
* Returns detailed information about columns in table . This information is cached internally .
2008-10-26 22:36:13 +00:00
* @ param string $table name
* @ param bool $usecache
2014-01-18 16:03:31 +08:00
* @ return database_column_info [] array of database_column_info objects indexed with column names
2008-10-26 22:36:13 +00:00
*/
public function get_columns ( $table , $usecache = true ) {
2012-08-14 11:38:13 +12:00
if ( $usecache ) {
2016-03-15 14:41:48 +08:00
if ( $this -> temptables -> is_temptable ( $table )) {
if ( $data = $this -> get_temp_tables_cache () -> get ( $table )) {
return $data ;
}
} else {
if ( $data = $this -> get_metacache () -> get ( $table )) {
return $data ;
}
2012-08-14 11:38:13 +12:00
}
2008-10-26 22:36:13 +00:00
}
2012-08-14 11:38:13 +12:00
$structure = array ();
2008-10-26 22:36:13 +00:00
$tablename = $this -> prefix . $table ;
$sql = " SELECT a.attnum, a.attname AS field, t.typname AS type, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, d.adsrc
FROM pg_catalog . pg_class c
2013-01-08 17:12:31 +01:00
JOIN pg_catalog . pg_namespace as ns ON ns . oid = c . relnamespace
2008-10-28 15:37:15 +00:00
JOIN pg_catalog . pg_attribute a ON a . attrelid = c . oid
2008-10-26 22:36:13 +00:00
JOIN pg_catalog . pg_type t ON t . oid = a . atttypid
LEFT JOIN pg_catalog . pg_attrdef d ON ( d . adrelid = c . oid AND d . adnum = a . attnum )
WHERE relkind = 'r' AND c . relname = '$tablename' AND c . reltype > 0 AND a . attnum > 0
2013-01-08 17:12:31 +01:00
AND ( ns . nspname = current_schema () OR ns . oid = pg_my_temp_schema ())
2008-10-26 22:36:13 +00:00
ORDER BY a . attnum " ;
2008-10-29 23:55:16 +00:00
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = pg_query ( $this -> pgsql , $sql );
$this -> query_end ( $result );
if ( ! $result ) {
2008-10-26 22:36:13 +00:00
return array ();
}
while ( $rawcolumn = pg_fetch_object ( $result )) {
2010-09-21 07:57:42 +00:00
$info = new stdClass ();
2008-10-26 22:36:13 +00:00
$info -> name = $rawcolumn -> field ;
$matches = null ;
if ( $rawcolumn -> type === 'varchar' ) {
$info -> type = 'varchar' ;
$info -> meta_type = 'C' ;
$info -> max_length = $rawcolumn -> atttypmod - 4 ;
$info -> scale = null ;
2008-10-27 21:57:45 +00:00
$info -> not_null = ( $rawcolumn -> attnotnull === 't' );
$info -> has_default = ( $rawcolumn -> atthasdef === 't' );
2008-10-26 22:36:13 +00:00
if ( $info -> has_default ) {
$parts = explode ( '::' , $rawcolumn -> adsrc );
if ( count ( $parts ) > 1 ) {
$info -> default_value = reset ( $parts );
$info -> default_value = trim ( $info -> default_value , " ' " );
} else {
$info -> default_value = $rawcolumn -> adsrc ;
}
} else {
$info -> default_value = null ;
}
$info -> primary_key = false ;
$info -> binary = false ;
$info -> unsigned = null ;
$info -> auto_increment = false ;
$info -> unique = null ;
} else if ( preg_match ( '/int(\d)/i' , $rawcolumn -> type , $matches )) {
$info -> type = 'int' ;
if ( strpos ( $rawcolumn -> adsrc , 'nextval' ) === 0 ) {
$info -> primary_key = true ;
$info -> meta_type = 'R' ;
$info -> unique = true ;
$info -> auto_increment = true ;
$info -> has_default = false ;
} else {
$info -> primary_key = false ;
$info -> meta_type = 'I' ;
$info -> unique = null ;
$info -> auto_increment = false ;
2008-10-27 21:57:45 +00:00
$info -> has_default = ( $rawcolumn -> atthasdef === 't' );
2008-10-26 22:36:13 +00:00
}
2012-10-02 16:21:17 +02:00
// Return number of decimals, not bytes here.
if ( $matches [ 1 ] >= 8 ) {
$info -> max_length = 18 ;
} else if ( $matches [ 1 ] >= 4 ) {
$info -> max_length = 9 ;
} else if ( $matches [ 1 ] >= 2 ) {
$info -> max_length = 4 ;
} else if ( $matches [ 1 ] >= 1 ) {
$info -> max_length = 2 ;
} else {
$info -> max_length = 0 ;
}
2008-10-26 22:36:13 +00:00
$info -> scale = null ;
2008-10-27 21:57:45 +00:00
$info -> not_null = ( $rawcolumn -> attnotnull === 't' );
2008-10-26 22:36:13 +00:00
if ( $info -> has_default ) {
2015-12-15 13:17:49 +13:00
// PG 9.5+ uses ::<TYPE> syntax for some defaults.
$parts = explode ( '::' , $rawcolumn -> adsrc );
if ( count ( $parts ) > 1 ) {
$info -> default_value = reset ( $parts );
} else {
$info -> default_value = $rawcolumn -> adsrc ;
}
$info -> default_value = trim ( $info -> default_value , " ()' " );
2008-10-26 22:36:13 +00:00
} else {
$info -> default_value = null ;
}
$info -> binary = false ;
$info -> unsigned = false ;
} else if ( $rawcolumn -> type === 'numeric' ) {
$info -> type = $rawcolumn -> type ;
$info -> meta_type = 'N' ;
$info -> primary_key = false ;
$info -> binary = false ;
$info -> unsigned = null ;
$info -> auto_increment = false ;
$info -> unique = null ;
2008-10-27 21:57:45 +00:00
$info -> not_null = ( $rawcolumn -> attnotnull === 't' );
$info -> has_default = ( $rawcolumn -> atthasdef === 't' );
2008-10-26 22:36:13 +00:00
if ( $info -> has_default ) {
2015-12-15 13:17:49 +13:00
// PG 9.5+ uses ::<TYPE> syntax for some defaults.
$parts = explode ( '::' , $rawcolumn -> adsrc );
if ( count ( $parts ) > 1 ) {
$info -> default_value = reset ( $parts );
} else {
$info -> default_value = $rawcolumn -> adsrc ;
}
$info -> default_value = trim ( $info -> default_value , " ()' " );
2008-10-26 22:36:13 +00:00
} else {
$info -> default_value = null ;
}
$info -> max_length = $rawcolumn -> atttypmod >> 16 ;
$info -> scale = ( $rawcolumn -> atttypmod & 0xFFFF ) - 4 ;
} else if ( preg_match ( '/float(\d)/i' , $rawcolumn -> type , $matches )) {
$info -> type = 'float' ;
$info -> meta_type = 'N' ;
$info -> primary_key = false ;
$info -> binary = false ;
$info -> unsigned = null ;
$info -> auto_increment = false ;
$info -> unique = null ;
2008-10-27 21:57:45 +00:00
$info -> not_null = ( $rawcolumn -> attnotnull === 't' );
$info -> has_default = ( $rawcolumn -> atthasdef === 't' );
2008-10-26 22:36:13 +00:00
if ( $info -> has_default ) {
2015-12-15 13:17:49 +13:00
// PG 9.5+ uses ::<TYPE> syntax for some defaults.
$parts = explode ( '::' , $rawcolumn -> adsrc );
if ( count ( $parts ) > 1 ) {
$info -> default_value = reset ( $parts );
} else {
$info -> default_value = $rawcolumn -> adsrc ;
}
$info -> default_value = trim ( $info -> default_value , " ()' " );
2008-10-26 22:36:13 +00:00
} else {
$info -> default_value = null ;
}
// just guess expected number of deciaml places :-(
if ( $matches [ 1 ] == 8 ) {
// total 15 digits
$info -> max_length = 8 ;
$info -> scale = 7 ;
} else {
// total 6 digits
$info -> max_length = 4 ;
$info -> scale = 2 ;
}
} else if ( $rawcolumn -> type === 'text' ) {
$info -> type = $rawcolumn -> type ;
$info -> meta_type = 'X' ;
$info -> max_length = - 1 ;
$info -> scale = null ;
2008-10-27 21:57:45 +00:00
$info -> not_null = ( $rawcolumn -> attnotnull === 't' );
$info -> has_default = ( $rawcolumn -> atthasdef === 't' );
2008-10-26 22:36:13 +00:00
if ( $info -> has_default ) {
$parts = explode ( '::' , $rawcolumn -> adsrc );
if ( count ( $parts ) > 1 ) {
$info -> default_value = reset ( $parts );
$info -> default_value = trim ( $info -> default_value , " ' " );
} else {
$info -> default_value = $rawcolumn -> adsrc ;
}
} else {
$info -> default_value = null ;
}
$info -> primary_key = false ;
$info -> binary = false ;
$info -> unsigned = null ;
$info -> auto_increment = false ;
$info -> unique = null ;
} else if ( $rawcolumn -> type === 'bytea' ) {
$info -> type = $rawcolumn -> type ;
$info -> meta_type = 'B' ;
$info -> max_length = - 1 ;
$info -> scale = null ;
2008-10-27 21:57:45 +00:00
$info -> not_null = ( $rawcolumn -> attnotnull === 't' );
2008-10-26 22:36:13 +00:00
$info -> has_default = false ;
$info -> default_value = null ;
$info -> primary_key = false ;
$info -> binary = true ;
$info -> unsigned = null ;
$info -> auto_increment = false ;
$info -> unique = null ;
}
2012-08-14 11:38:13 +12:00
$structure [ $info -> name ] = new database_column_info ( $info );
2008-10-26 22:36:13 +00:00
}
2008-10-27 11:43:17 +00:00
pg_free_result ( $result );
2012-08-14 11:38:13 +12:00
if ( $usecache ) {
2016-03-15 14:41:48 +08:00
if ( $this -> temptables -> is_temptable ( $table )) {
$this -> get_temp_tables_cache () -> set ( $table , $structure );
} else {
$this -> get_metacache () -> set ( $table , $structure );
}
2012-08-14 11:38:13 +12:00
}
return $structure ;
2008-10-26 22:36:13 +00:00
}
2009-11-03 23:34:43 +00:00
/**
* Normalise values based in RDBMS dependencies ( booleans , LOBs ... )
*
* @ param database_column_info $column column metadata corresponding with the value we are going to normalise
* @ param mixed $value value we are going to normalise
* @ return mixed the normalised value
*/
2009-11-04 13:19:11 +00:00
protected function normalise_value ( $column , $value ) {
2016-07-01 09:12:41 +10:00
$this -> detect_objects ( $value );
2009-11-03 23:34:43 +00:00
if ( is_bool ( $value )) { // Always, convert boolean to int
$value = ( int ) $value ;
2016-06-14 13:47:36 +10:00
} else if ( $column -> meta_type === 'B' ) {
if ( ! is_null ( $value )) {
// standard_conforming_strings must be enabled, otherwise pg_escape_bytea() will double escape
// \ and produce data errors. This is set on the connection.
$value = pg_escape_bytea ( $this -> pgsql , $value );
2009-11-03 23:34:43 +00:00
}
} else if ( $value === '' ) {
2010-09-13 18:31:21 +00:00
if ( $column -> meta_type === 'I' or $column -> meta_type === 'F' or $column -> meta_type === 'N' ) {
2009-11-03 23:34:43 +00:00
$value = 0 ; // prevent '' problems in numeric fields
}
}
return $value ;
}
2008-10-26 22:36:13 +00:00
/**
* Is db in unicode mode ?
* @ return bool
*/
public function setup_is_unicodedb () {
2012-06-05 10:31:03 +02:00
// Get PostgreSQL server_encoding value
2008-10-29 23:55:16 +00:00
$sql = " SHOW server_encoding " ;
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = pg_query ( $this -> pgsql , $sql );
$this -> query_end ( $result );
if ( ! $result ) {
2008-10-26 22:36:13 +00:00
return false ;
}
$rawcolumn = pg_fetch_object ( $result );
$encoding = $rawcolumn -> server_encoding ;
pg_free_result ( $result );
return ( strtoupper ( $encoding ) == 'UNICODE' || strtoupper ( $encoding ) == 'UTF8' );
}
/**
* Do NOT use in code , to be used by database_manager only !
2014-01-20 14:06:44 +08:00
* @ param string | array $sql query
2016-03-15 14:41:48 +08:00
* @ param array | null $tablenames an array of xmldb table names affected by this request .
2008-11-21 20:09:13 +00:00
* @ return bool true
2014-01-20 14:06:44 +08:00
* @ throws ddl_change_structure_exception A DDL specific exception is thrown for any errors .
2008-10-26 22:36:13 +00:00
*/
2016-03-15 14:41:48 +08:00
public function change_database_structure ( $sql , $tablenames = null ) {
2014-01-20 14:06:44 +08:00
$this -> get_manager (); // Includes DDL exceptions classes ;-)
2014-01-24 13:47:22 +08:00
if ( is_array ( $sql )) {
$sql = implode ( " \n ; \n " , $sql );
}
if ( ! $this -> is_transaction_started ()) {
// It is better to do all or nothing, this helps with recovery...
$sql = " BEGIN ISOLATION LEVEL SERIALIZABLE; \n $sql\n ; COMMIT " ;
}
2008-10-29 23:55:16 +00:00
2014-01-20 14:06:44 +08:00
try {
2014-01-24 13:47:22 +08:00
$this -> query_start ( $sql , null , SQL_QUERY_STRUCTURE );
$result = pg_query ( $this -> pgsql , $sql );
$this -> query_end ( $result );
pg_free_result ( $result );
2014-01-20 14:06:44 +08:00
} catch ( ddl_change_structure_exception $e ) {
2014-01-24 13:47:22 +08:00
if ( ! $this -> is_transaction_started ()) {
$result = @ pg_query ( $this -> pgsql , " ROLLBACK " );
@ pg_free_result ( $result );
}
2016-03-15 14:41:48 +08:00
$this -> reset_caches ( $tablenames );
2014-01-20 14:06:44 +08:00
throw $e ;
}
2008-10-29 23:55:16 +00:00
2016-03-15 14:41:48 +08:00
$this -> reset_caches ( $tablenames );
2008-10-26 22:36:13 +00:00
return true ;
}
/**
* Execute general sql query . Should be used only when no other method suitable .
2012-02-27 12:30:11 +01:00
* Do NOT use this to make changes in db structure , use database_manager methods instead !
2008-10-26 22:36:13 +00:00
* @ param string $sql query
* @ param array $params query parameters
2008-11-21 20:09:13 +00:00
* @ return bool true
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2008-10-26 22:36:13 +00:00
*/
public function execute ( $sql , array $params = null ) {
list ( $sql , $params , $type ) = $this -> fix_sql_params ( $sql , $params );
if ( strpos ( $sql , ';' ) !== false ) {
2008-11-21 20:09:13 +00:00
throw new coding_exception ( 'moodle_database::execute() Multiple sql statements found or bound parameters not used properly in query!' );
2008-10-26 22:36:13 +00:00
}
2008-10-29 23:55:16 +00:00
$this -> query_start ( $sql , $params , SQL_QUERY_UPDATE );
2016-07-01 09:12:41 +10:00
$result = pg_query_params ( $this -> pgsql , $sql , $params );
2008-10-29 23:55:16 +00:00
$this -> query_end ( $result );
2008-10-26 22:36:13 +00:00
2008-10-27 11:43:17 +00:00
pg_free_result ( $result );
2008-10-26 22:36:13 +00:00
return true ;
}
/**
* Get a number of records as a moodle_recordset using a SQL statement .
*
* Since this method is a little less readable , use of it should be restricted to
* code where it ' s possible there might be large datasets being returned . For known
* small datasets use get_records_sql - it leads to simpler code .
*
2012-02-16 10:29:45 +08:00
* The return type is like :
* @ see function get_recordset .
2008-10-26 22:36:13 +00:00
*
* @ param string $sql the SQL select query to execute .
* @ param array $params array of sql parameters
* @ param int $limitfrom return a subset of records , starting at this point ( optional , required if $limitnum is set ) .
* @ param int $limitnum return a subset comprising this many records ( optional , required if $limitfrom is set ) .
2010-08-22 18:55:29 +00:00
* @ return moodle_recordset instance
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2008-10-26 22:36:13 +00:00
*/
public function get_recordset_sql ( $sql , array $params = null , $limitfrom = 0 , $limitnum = 0 ) {
2013-12-16 13:05:20 +08:00
list ( $limitfrom , $limitnum ) = $this -> normalise_limit_from_num ( $limitfrom , $limitnum );
2016-07-01 09:12:41 +10:00
if ( $limitnum ) {
$sql .= " LIMIT $limitnum " ;
}
if ( $limitfrom ) {
$sql .= " OFFSET $limitfrom " ;
2008-10-26 22:36:13 +00:00
}
list ( $sql , $params , $type ) = $this -> fix_sql_params ( $sql , $params );
2008-10-29 23:55:16 +00:00
$this -> query_start ( $sql , $params , SQL_QUERY_SELECT );
2008-10-26 22:36:13 +00:00
$result = pg_query_params ( $this -> pgsql , $sql , $params );
2008-10-29 23:55:16 +00:00
$this -> query_end ( $result );
2008-10-26 22:36:13 +00:00
return $this -> create_recordset ( $result );
}
protected function create_recordset ( $result ) {
2016-06-14 13:47:36 +10:00
return new pgsql_native_moodle_recordset ( $result );
2008-10-26 22:36:13 +00:00
}
/**
* Get a number of records as an array of objects using a SQL statement .
*
2012-02-16 10:29:45 +08:00
* Return value is like :
* @ see function get_records .
2008-10-26 22:36:13 +00:00
*
* @ param string $sql the SQL select query to execute . The first column of this SELECT statement
* must be a unique value ( usually the 'id' field ), as it will be used as the key of the
* returned array .
* @ param array $params array of sql parameters
* @ param int $limitfrom return a subset of records , starting at this point ( optional , required if $limitnum is set ) .
* @ param int $limitnum return a subset comprising this many records ( optional , required if $limitfrom is set ) .
2010-08-22 19:00:28 +00:00
* @ return array of objects , or empty array if no records were found
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2008-10-26 22:36:13 +00:00
*/
public function get_records_sql ( $sql , array $params = null , $limitfrom = 0 , $limitnum = 0 ) {
2013-12-16 13:05:20 +08:00
list ( $limitfrom , $limitnum ) = $this -> normalise_limit_from_num ( $limitfrom , $limitnum );
2016-07-01 09:12:41 +10:00
if ( $limitnum ) {
$sql .= " LIMIT $limitnum " ;
}
if ( $limitfrom ) {
$sql .= " OFFSET $limitfrom " ;
2008-10-26 22:36:13 +00:00
}
list ( $sql , $params , $type ) = $this -> fix_sql_params ( $sql , $params );
2008-10-29 23:55:16 +00:00
$this -> query_start ( $sql , $params , SQL_QUERY_SELECT );
2008-10-26 22:36:13 +00:00
$result = pg_query_params ( $this -> pgsql , $sql , $params );
2008-10-29 23:55:16 +00:00
$this -> query_end ( $result );
2008-10-26 22:36:13 +00:00
2008-10-27 15:31:11 +00:00
// find out if there are any blobs
2016-06-14 13:47:36 +10:00
$numfields = pg_num_fields ( $result );
2008-10-27 15:31:11 +00:00
$blobs = array ();
2016-06-14 13:47:36 +10:00
for ( $i = 0 ; $i < $numfields ; $i ++ ) {
$type = pg_field_type ( $result , $i );
if ( $type == 'bytea' ) {
2008-10-27 15:31:11 +00:00
$blobs [] = pg_field_name ( $result , $i );
}
}
2008-10-26 22:36:13 +00:00
2016-07-01 09:12:41 +10:00
$rows = pg_fetch_all ( $result );
pg_free_result ( $result );
2008-10-26 22:36:13 +00:00
$return = array ();
2016-07-01 09:12:41 +10:00
if ( $rows ) {
foreach ( $rows as $row ) {
2008-10-26 22:36:13 +00:00
$id = reset ( $row );
2008-10-27 15:31:11 +00:00
if ( $blobs ) {
foreach ( $blobs as $blob ) {
2016-06-14 13:47:36 +10:00
$row [ $blob ] = ( $row [ $blob ] !== null ? pg_unescape_bytea ( $row [ $blob ]) : null );
2008-10-27 15:31:11 +00:00
}
}
2008-11-21 22:35:21 +00:00
if ( isset ( $return [ $id ])) {
2016-07-01 09:12:41 +10:00
$colname = key ( $row );
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 );
2008-11-21 22:35:21 +00:00
}
2016-07-01 09:12:41 +10:00
$return [ $id ] = ( object ) $row ;
2008-10-26 22:36:13 +00:00
}
}
2008-10-27 15:31:11 +00:00
2008-10-26 22:36:13 +00:00
return $return ;
}
/**
* Selects records and return values ( first field ) as an array using a SQL statement .
*
* @ param string $sql The SQL query
* @ param array $params array of sql parameters
2010-08-22 19:00:28 +00:00
* @ return array of values
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2008-10-26 22:36:13 +00:00
*/
public function get_fieldset_sql ( $sql , array $params = null ) {
list ( $sql , $params , $type ) = $this -> fix_sql_params ( $sql , $params );
2008-10-29 23:55:16 +00:00
$this -> query_start ( $sql , $params , SQL_QUERY_SELECT );
2008-10-26 22:36:13 +00:00
$result = pg_query_params ( $this -> pgsql , $sql , $params );
2008-10-29 23:55:16 +00:00
$this -> query_end ( $result );
2008-10-26 22:36:13 +00:00
$return = pg_fetch_all_columns ( $result , 0 );
2016-06-14 13:47:36 +10:00
if ( pg_field_type ( $result , 0 ) == 'bytea' ) {
foreach ( $return as $key => $value ) {
$return [ $key ] = ( $value === null ? $value : pg_unescape_bytea ( $value ));
}
}
2008-10-26 22:36:13 +00:00
pg_free_result ( $result );
2008-10-28 15:37:15 +00:00
2008-10-26 22:36:13 +00:00
return $return ;
}
/**
* Insert new record into database , as fast as possible , no safety checks , lobs not supported .
* @ param string $table name
* @ param mixed $params data record as object or array
* @ param bool $returnit return it of inserted record
* @ param bool $bulk true means repeated inserts expected
* @ param bool $customsequence true if 'id' included in $params , disables $returnid
2010-08-22 19:00:28 +00:00
* @ return bool | int true or new id
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2008-10-26 22:36:13 +00:00
*/
public function insert_record_raw ( $table , $params , $returnid = true , $bulk = false , $customsequence = false ) {
if ( ! is_array ( $params )) {
$params = ( array ) $params ;
}
$returning = " " ;
if ( $customsequence ) {
if ( ! isset ( $params [ 'id' ])) {
2008-11-21 20:09:13 +00:00
throw new coding_exception ( 'moodle_database::insert_record_raw() id field must be specified if custom sequences used.' );
2008-10-26 22:36:13 +00:00
}
$returnid = false ;
} else {
if ( $returnid ) {
2009-06-03 20:53:19 +00:00
$returning = " RETURNING id " ;
unset ( $params [ 'id' ]);
2008-10-26 22:36:13 +00:00
} else {
unset ( $params [ 'id' ]);
}
}
if ( empty ( $params )) {
2008-11-21 20:09:13 +00:00
throw new coding_exception ( 'moodle_database::insert_record_raw() no fields found.' );
2008-10-26 22:36:13 +00:00
}
$fields = implode ( ',' , array_keys ( $params ));
$values = array ();
2012-03-17 18:42:30 +01:00
$i = 1 ;
foreach ( $params as $value ) {
$this -> detect_objects ( $value );
$values [] = " \$ " . $i ++ ;
2008-10-28 15:37:15 +00:00
}
2008-10-26 22:36:13 +00:00
$values = implode ( ',' , $values );
$sql = " INSERT INTO { $this -> prefix } $table ( $fields ) VALUES( $values ) $returning " ;
2008-10-29 23:55:16 +00:00
$this -> query_start ( $sql , $params , SQL_QUERY_INSERT );
2008-10-26 22:36:13 +00:00
$result = pg_query_params ( $this -> pgsql , $sql , $params );
2008-10-29 23:55:16 +00:00
$this -> query_end ( $result );
2008-10-26 22:36:13 +00:00
if ( $returning !== " " ) {
$row = pg_fetch_assoc ( $result );
$params [ 'id' ] = reset ( $row );
}
2008-10-27 11:43:17 +00:00
pg_free_result ( $result );
2008-10-26 22:36:13 +00:00
if ( ! $returnid ) {
return true ;
}
return ( int ) $params [ 'id' ];
}
/**
* Insert a record into a table and return the " id " field if required .
*
* Some conversions and safety checks are carried out . Lobs are supported .
* If the return ID isn ' t required , then this just reports success as true / false .
* $data is an object containing needed data
* @ param string $table The database table to be inserted into
* @ param object $data A data object with values for one or more fields in the record
* @ param bool $returnid Should the id of the newly created record entry be returned ? If this option is not requested then true / false is returned .
2010-08-22 19:00:28 +00:00
* @ return bool | int true or new id
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2008-10-26 22:36:13 +00:00
*/
public function insert_record ( $table , $dataobject , $returnid = true , $bulk = false ) {
2010-07-31 20:51:22 +00:00
$dataobject = ( array ) $dataobject ;
2008-10-26 22:36:13 +00:00
$columns = $this -> get_columns ( $table );
2013-08-21 20:21:59 +10:00
if ( empty ( $columns )) {
throw new dml_exception ( 'ddltablenotexist' , $table );
}
2008-10-26 22:36:13 +00:00
$cleaned = array ();
foreach ( $dataobject as $field => $value ) {
2010-07-31 20:51:22 +00:00
if ( $field === 'id' ) {
continue ;
}
2008-10-26 22:36:13 +00:00
if ( ! isset ( $columns [ $field ])) {
continue ;
}
$column = $columns [ $field ];
2016-06-14 13:47:36 +10:00
$cleaned [ $field ] = $this -> normalise_value ( $column , $value );
2008-10-26 22:36:13 +00:00
}
2016-06-14 13:47:36 +10:00
return $this -> insert_record_raw ( $table , $cleaned , $returnid , $bulk );
2008-10-26 22:36:13 +00:00
}
2014-01-18 16:03:31 +08:00
/**
* Insert multiple records into database as fast as possible .
*
* Order of inserts is maintained , but the operation is not atomic ,
* use transactions if necessary .
*
* This method is intended for inserting of large number of small objects ,
* do not use for huge objects with text or binary fields .
*
2014-05-19 17:03:04 +01:00
* @ since Moodle 2.7
2014-01-18 16:03:31 +08:00
*
* @ param string $table The database table to be inserted into
* @ param array | Traversable $dataobjects list of objects to be inserted , must be compatible with foreach
* @ return void does not return new record ids
*
* @ throws coding_exception if data objects have different structure
* @ throws dml_exception A DML specific exception is thrown for any errors .
*/
public function insert_records ( $table , $dataobjects ) {
if ( ! is_array ( $dataobjects ) and ! ( $dataobjects instanceof Traversable )) {
throw new coding_exception ( 'insert_records() passed non-traversable object' );
}
// PostgreSQL does not seem to have problems with huge queries.
$chunksize = 500 ;
if ( ! empty ( $this -> dboptions [ 'bulkinsertsize' ])) {
$chunksize = ( int ) $this -> dboptions [ 'bulkinsertsize' ];
}
$columns = $this -> get_columns ( $table , true );
$fields = null ;
$count = 0 ;
$chunk = array ();
foreach ( $dataobjects as $dataobject ) {
if ( ! is_array ( $dataobject ) and ! is_object ( $dataobject )) {
throw new coding_exception ( 'insert_records() passed invalid record object' );
}
$dataobject = ( array ) $dataobject ;
if ( $fields === null ) {
$fields = array_keys ( $dataobject );
$columns = array_intersect_key ( $columns , $dataobject );
unset ( $columns [ 'id' ]);
} else if ( $fields !== array_keys ( $dataobject )) {
throw new coding_exception ( 'All dataobjects in insert_records() must have the same structure!' );
}
$count ++ ;
$chunk [] = $dataobject ;
if ( $count === $chunksize ) {
$this -> insert_chunk ( $table , $chunk , $columns );
$chunk = array ();
$count = 0 ;
}
}
if ( $count ) {
$this -> insert_chunk ( $table , $chunk , $columns );
}
}
/**
2016-06-14 13:47:36 +10:00
* Insert records in chunks , strict param types ...
2014-01-18 16:03:31 +08:00
*
* Note : can be used only from insert_records () .
*
* @ param string $table
* @ param array $chunk
* @ param database_column_info [] $columns
*/
protected function insert_chunk ( $table , array $chunk , array $columns ) {
$i = 1 ;
$params = array ();
$values = array ();
foreach ( $chunk as $dataobject ) {
$vals = array ();
foreach ( $columns as $field => $column ) {
$params [] = $this -> normalise_value ( $column , $dataobject [ $field ]);
$vals [] = " \$ " . $i ++ ;
}
$values [] = '(' . implode ( ',' , $vals ) . ')' ;
}
$fieldssql = '(' . implode ( ',' , array_keys ( $columns )) . ')' ;
$valuessql = implode ( ',' , $values );
$sql = " INSERT INTO { $this -> prefix } $table $fieldssql VALUES $valuessql " ;
$this -> query_start ( $sql , $params , SQL_QUERY_INSERT );
$result = pg_query_params ( $this -> pgsql , $sql , $params );
$this -> query_end ( $result );
pg_free_result ( $result );
}
2008-10-26 22:36:13 +00:00
/**
* Import a record into a table , id field is required .
* Safety checks are NOT carried out . Lobs are supported .
*
* @ param string $table name of database table to be inserted into
* @ param object $dataobject A data object with values for one or more fields in the record
2008-11-21 20:09:13 +00:00
* @ return bool true
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2008-10-26 22:36:13 +00:00
*/
public function import_record ( $table , $dataobject ) {
2010-07-31 20:51:22 +00:00
$dataobject = ( array ) $dataobject ;
2008-10-26 22:36:13 +00:00
$columns = $this -> get_columns ( $table );
$cleaned = array ();
foreach ( $dataobject as $field => $value ) {
2012-03-17 18:42:30 +01:00
$this -> detect_objects ( $value );
2008-10-26 22:36:13 +00:00
if ( ! isset ( $columns [ $field ])) {
continue ;
}
2016-06-14 13:47:36 +10:00
$column = $columns [ $field ];
$cleaned [ $field ] = $this -> normalise_value ( $column , $value );
2010-09-13 18:31:21 +00:00
}
2016-06-14 13:47:36 +10:00
return $this -> insert_record_raw ( $table , $cleaned , false , true , true );
2008-10-26 22:36:13 +00:00
}
/**
* Update record in database , as fast as possible , no safety checks , lobs not supported .
* @ param string $table name
* @ param mixed $params data record as object or array
* @ param bool true means repeated updates expected
2008-11-21 20:09:13 +00:00
* @ return bool true
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2008-10-26 22:36:13 +00:00
*/
public function update_record_raw ( $table , $params , $bulk = false ) {
2010-07-31 20:51:22 +00:00
$params = ( array ) $params ;
2008-10-26 22:36:13 +00:00
if ( ! isset ( $params [ 'id' ])) {
2008-11-21 20:09:13 +00:00
throw new coding_exception ( 'moodle_database::update_record_raw() id field must be specified.' );
2008-10-26 22:36:13 +00:00
}
$id = $params [ 'id' ];
unset ( $params [ 'id' ]);
if ( empty ( $params )) {
2008-11-21 20:09:13 +00:00
throw new coding_exception ( 'moodle_database::update_record_raw() no fields found.' );
2008-10-26 22:36:13 +00:00
}
$i = 1 ;
$sets = array ();
foreach ( $params as $field => $value ) {
2012-03-17 18:42:30 +01:00
$this -> detect_objects ( $value );
2008-10-26 22:36:13 +00:00
$sets [] = " $field = \$ " . $i ++ ;
}
$params [] = $id ; // last ? in WHERE condition
$sets = implode ( ',' , $sets );
$sql = " UPDATE { $this -> prefix } $table SET $sets WHERE id= \$ " . $i ;
2008-10-29 23:55:16 +00:00
$this -> query_start ( $sql , $params , SQL_QUERY_UPDATE );
2008-10-26 22:36:13 +00:00
$result = pg_query_params ( $this -> pgsql , $sql , $params );
2008-10-29 23:55:16 +00:00
$this -> query_end ( $result );
2008-10-26 22:36:13 +00:00
2008-10-27 11:43:17 +00:00
pg_free_result ( $result );
2008-10-26 22:36:13 +00:00
return true ;
}
/**
* Update a record in a table
*
* $dataobject is an object containing needed data
* Relies on $dataobject having a variable " id " to
* specify the record to update
*
* @ param string $table The database table to be checked against .
* @ param object $dataobject An object with contents equal to fieldname => fieldvalue . Must have an entry for 'id' to map to the table specified .
* @ param bool true means repeated updates expected
2008-11-21 20:09:13 +00:00
* @ return bool true
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2008-10-26 22:36:13 +00:00
*/
public function update_record ( $table , $dataobject , $bulk = false ) {
2010-07-31 20:51:22 +00:00
$dataobject = ( array ) $dataobject ;
2008-10-26 22:36:13 +00:00
$columns = $this -> get_columns ( $table );
$cleaned = array ();
foreach ( $dataobject as $field => $value ) {
if ( ! isset ( $columns [ $field ])) {
continue ;
}
2008-10-27 15:42:52 +00:00
$column = $columns [ $field ];
2016-06-14 13:47:36 +10:00
$cleaned [ $field ] = $this -> normalise_value ( $column , $value );
2008-10-26 22:36:13 +00:00
}
2008-11-21 20:09:13 +00:00
$this -> update_record_raw ( $table , $cleaned , $bulk );
2008-10-27 15:42:52 +00:00
return true ;
2008-10-26 22:36:13 +00:00
}
/**
* Set a single field in every table record which match a particular WHERE clause .
*
* @ param string $table The database table to be checked against .
* @ param string $newfield the field to set .
* @ param string $newvalue the value to set the field to .
* @ param string $select A fragment of SQL to be used in a where clause in the SQL call .
* @ param array $params array of sql parameters
2008-11-21 20:09:13 +00:00
* @ return bool true
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2008-10-26 22:36:13 +00:00
*/
public function set_field_select ( $table , $newfield , $newvalue , $select , array $params = null ) {
2009-05-01 23:33:24 +00:00
2008-10-26 22:36:13 +00:00
if ( $select ) {
$select = " WHERE $select " ;
}
if ( is_null ( $params )) {
$params = array ();
}
list ( $select , $params , $type ) = $this -> fix_sql_params ( $select , $params );
$i = count ( $params ) + 1 ;
2012-06-05 10:31:03 +02:00
// Get column metadata
2009-05-01 23:33:24 +00:00
$columns = $this -> get_columns ( $table );
$column = $columns [ $newfield ];
2016-06-14 13:47:36 +10:00
$normalisedvalue = $this -> normalise_value ( $column , $newvalue );
2009-05-01 23:33:24 +00:00
2016-06-14 13:47:36 +10:00
$newfield = " $newfield = \$ " . $i ;
$params [] = $normalisedvalue ;
2008-10-26 22:36:13 +00:00
$sql = " UPDATE { $this -> prefix } $table SET $newfield $select " ;
2008-10-29 23:55:16 +00:00
$this -> query_start ( $sql , $params , SQL_QUERY_UPDATE );
2008-10-26 22:36:13 +00:00
$result = pg_query_params ( $this -> pgsql , $sql , $params );
2008-10-29 23:55:16 +00:00
$this -> query_end ( $result );
2008-10-26 22:36:13 +00:00
2008-10-27 11:43:17 +00:00
pg_free_result ( $result );
2008-10-26 22:36:13 +00:00
return true ;
}
/**
2016-06-14 13:47:36 +10:00
* Delete one or more records from a table which match a particular WHERE clause , lobs not supported .
2008-10-26 22:36:13 +00:00
*
* @ param string $table The database table to be checked against .
* @ param string $select A fragment of SQL to be used in a where clause in the SQL call ( used to define the selection criteria ) .
* @ param array $params array of sql parameters
2008-11-21 20:09:13 +00:00
* @ return bool true
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2008-10-26 22:36:13 +00:00
*/
public function delete_records_select ( $table , $select , array $params = null ) {
if ( $select ) {
$select = " WHERE $select " ;
}
$sql = " DELETE FROM { $this -> prefix } $table $select " ;
list ( $sql , $params , $type ) = $this -> fix_sql_params ( $sql , $params );
2008-10-29 23:55:16 +00:00
$this -> query_start ( $sql , $params , SQL_QUERY_UPDATE );
2008-10-26 22:36:13 +00:00
$result = pg_query_params ( $this -> pgsql , $sql , $params );
2008-10-29 23:55:16 +00:00
$this -> query_end ( $result );
2008-10-26 22:36:13 +00:00
2008-10-27 11:43:17 +00:00
pg_free_result ( $result );
2008-10-26 22:36:13 +00:00
return true ;
}
2010-08-24 21:50:53 +00:00
/**
* Returns 'LIKE' part of a query .
*
* @ param string $fieldname usually name of the table column
* @ param string $param usually bound query parameter ( ? , : named )
* @ param bool $casesensitive use case sensitive search
* @ param bool $accensensitive use accent sensitive search ( not all databases support accent insensitive )
2010-09-04 14:39:01 +00:00
* @ param bool $notlike true means " NOT LIKE "
2010-08-24 21:50:53 +00:00
* @ param string $escapechar escape char for '%' and '_'
* @ return string SQL code fragment
*/
2010-09-04 14:39:01 +00:00
public function sql_like ( $fieldname , $param , $casesensitive = true , $accentsensitive = true , $notlike = false , $escapechar = '\\' ) {
2010-08-24 21:50:53 +00:00
if ( strpos ( $param , '%' ) !== false ) {
2012-03-17 19:20:25 +01:00
debugging ( 'Potential SQL injection detected, sql_like() expects bound parameters (? or :named)' );
2010-08-24 21:50:53 +00:00
}
// postgresql does not support accent insensitive text comparisons, sorry
if ( $casesensitive ) {
2010-09-04 14:39:01 +00:00
$LIKE = $notlike ? 'NOT LIKE' : 'LIKE' ;
2010-08-24 21:50:53 +00:00
} else {
2010-09-04 14:39:01 +00:00
$LIKE = $notlike ? 'NOT ILIKE' : 'ILIKE' ;
2010-08-24 21:50:53 +00:00
}
2016-06-14 13:47:36 +10:00
return " $fieldname $LIKE $param ESCAPE ' $escapechar ' " ;
2010-08-24 21:50:53 +00:00
}
2008-10-26 22:36:13 +00:00
public function sql_bitxor ( $int1 , $int2 ) {
2010-09-13 08:47:22 +00:00
return '((' . $int1 . ') # (' . $int2 . '))' ;
2008-10-26 22:36:13 +00:00
}
public function sql_cast_char2int ( $fieldname , $text = false ) {
return ' CAST(' . $fieldname . ' AS INT) ' ;
}
public function sql_cast_char2real ( $fieldname , $text = false ) {
return " $fieldname ::real " ;
}
public function sql_concat () {
$arr = func_get_args ();
$s = implode ( ' || ' , $arr );
if ( $s === '' ) {
return " '' " ;
}
2010-12-22 18:50:05 +01:00
// Add always empty string element so integer-exclusive concats
2012-06-05 10:31:03 +02:00
// will work without needing to cast each element explicitly
2010-12-22 18:50:05 +01:00
return " '' || $s " ;
2008-10-26 22:36:13 +00:00
}
public function sql_concat_join ( $separator = " ' ' " , $elements = array ()) {
for ( $n = count ( $elements ) - 1 ; $n > 0 ; $n -- ) {
array_splice ( $elements , $n , 0 , $separator );
}
$s = implode ( ' || ' , $elements );
if ( $s === '' ) {
return " '' " ;
}
return " $s " ;
}
public function sql_regex_supported () {
return true ;
}
public function sql_regex ( $positivematch = true ) {
return $positivematch ? '~*' : '!~*' ;
}
2013-11-28 12:59:16 +08:00
/**
* Does this driver support tool_replace ?
*
2014-05-19 17:03:04 +01:00
* @ since Moodle 2.6 . 1
2013-11-28 12:59:16 +08:00
* @ return bool
*/
public function replace_all_text_supported () {
return true ;
}
2009-01-17 14:31:29 +00:00
public function session_lock_supported () {
2009-06-03 20:53:19 +00:00
return true ;
2009-01-17 14:31:29 +00:00
}
2011-11-06 17:10:23 +01:00
/**
* Obtain session lock
* @ param int $rowid id of the row with session record
* @ param int $timeout max allowed time to wait for the lock in seconds
* @ return bool success
*/
public function get_session_lock ( $rowid , $timeout ) {
2009-06-13 09:09:30 +00:00
// NOTE: there is a potential locking problem for database running
2009-01-17 14:31:29 +00:00
// multiple instances of moodle, we could try to use pg_advisory_lock(int, int),
// luckily there is not a big chance that they would collide
if ( ! $this -> session_lock_supported ()) {
return ;
}
2011-11-06 17:10:23 +01:00
parent :: get_session_lock ( $rowid , $timeout );
$timeoutmilli = $timeout * 1000 ;
$sql = " SET statement_timeout TO $timeoutmilli " ;
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = pg_query ( $this -> pgsql , $sql );
$this -> query_end ( $result );
if ( $result ) {
pg_free_result ( $result );
}
2009-01-17 14:31:29 +00:00
$sql = " SELECT pg_advisory_lock( $rowid ) " ;
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
2011-11-06 17:10:23 +01:00
$start = time ();
$result = pg_query ( $this -> pgsql , $sql );
$end = time ();
try {
$this -> query_end ( $result );
} catch ( dml_exception $ex ) {
if ( $end - $start >= $timeout ) {
throw new dml_sessionwait_exception ();
} else {
throw $ex ;
}
}
if ( $result ) {
pg_free_result ( $result );
}
$sql = " SET statement_timeout TO DEFAULT " ;
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
2009-01-17 14:31:29 +00:00
$result = pg_query ( $this -> pgsql , $sql );
$this -> query_end ( $result );
if ( $result ) {
pg_free_result ( $result );
}
}
public function release_session_lock ( $rowid ) {
if ( ! $this -> session_lock_supported ()) {
return ;
}
2012-10-25 17:12:53 +08:00
if ( ! $this -> used_for_db_sessions ) {
return ;
}
2009-01-17 14:31:29 +00:00
parent :: release_session_lock ( $rowid );
$sql = " SELECT pg_advisory_unlock( $rowid ) " ;
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = pg_query ( $this -> pgsql , $sql );
$this -> query_end ( $result );
if ( $result ) {
pg_free_result ( $result );
}
}
2008-10-27 20:21:04 +00:00
/**
2009-11-07 08:52:56 +00:00
* Driver specific start of real database transaction ,
* this can not be used directly in code .
* @ return void
2008-10-27 20:21:04 +00:00
*/
2009-11-07 08:52:56 +00:00
protected function begin_transaction () {
2012-09-26 10:39:28 +02:00
$this -> savepointpresent = true ;
$sql = " BEGIN ISOLATION LEVEL READ COMMITTED; SAVEPOINT moodle_pg_savepoint " ;
2008-10-29 23:55:16 +00:00
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
$result = pg_query ( $this -> pgsql , $sql );
$this -> query_end ( $result );
2008-10-27 20:21:04 +00:00
pg_free_result ( $result );
}
/**
2009-11-07 08:52:56 +00:00
* Driver specific commit of real database transaction ,
* this can not be used directly in code .
* @ return void
2008-10-27 20:21:04 +00:00
*/
2009-11-07 08:52:56 +00:00
protected function commit_transaction () {
2012-09-26 10:39:28 +02:00
$this -> savepointpresent = false ;
$sql = " RELEASE SAVEPOINT moodle_pg_savepoint; COMMIT " ;
2008-10-29 23:55:16 +00:00
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
$result = pg_query ( $this -> pgsql , $sql );
$this -> query_end ( $result );
2008-10-27 20:21:04 +00:00
pg_free_result ( $result );
}
/**
2009-11-07 08:52:56 +00:00
* Driver specific abort of real database transaction ,
* this can not be used directly in code .
* @ return void
2008-10-27 20:21:04 +00:00
*/
2009-11-07 08:52:56 +00:00
protected function rollback_transaction () {
2012-09-26 10:39:28 +02:00
$this -> savepointpresent = false ;
$sql = " RELEASE SAVEPOINT moodle_pg_savepoint; ROLLBACK " ;
2008-10-29 23:55:16 +00:00
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
$result = pg_query ( $this -> pgsql , $sql );
$this -> query_end ( $result );
2008-10-27 20:21:04 +00:00
pg_free_result ( $result );
}
2009-05-03 17:19:40 +00:00
/**
* Helper function trimming ( whitespace + quotes ) any string
* needed because PG uses to enclose with double quotes some
* fields in indexes definition and others
*
* @ param string $str string to apply whitespace + quotes trim
* @ return string trimmed string
*/
private function trim_quotes ( $str ) {
return trim ( trim ( $str ), " ' \" " );
}
2008-10-26 22:36:13 +00:00
}