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 mysqli 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-17 22:43:42 +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__ . '/mysqli_native_moodle_recordset.php' );
require_once ( __DIR__ . '/mysqli_native_moodle_temptables.php' );
2008-10-17 22:43:42 +00:00
/**
* Native mysqli 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-17 22:43:42 +00:00
*/
class mysqli_native_moodle_database extends moodle_database {
2014-01-24 13:47:22 +08:00
/** @var mysqli $mysqli */
2008-10-18 22:35:42 +00:00
protected $mysqli = null ;
2014-07-07 18:31:45 +12:00
/** @var bool is compressed row format supported cache */
protected $compressedrowformatsupported = null ;
2008-10-17 22:43:42 +00:00
2009-11-07 08:52:56 +00:00
private $transactions_supported = null ;
2008-11-30 19:29:37 +00:00
/**
* Attempt to create the database
* @ param string $dbhost
* @ param string $dbuser
* @ param string $dbpass
* @ param string $dbname
* @ return bool success
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2008-11-30 19:29:37 +00:00
*/
2009-02-07 10:20:33 +00:00
public function create_database ( $dbhost , $dbuser , $dbpass , $dbname , array $dboptions = null ) {
2009-02-09 17:51:01 +00:00
$driverstatus = $this -> driver_installed ();
if ( $driverstatus !== true ) {
throw new dml_exception ( 'dbdriverproblem' , $driverstatus );
}
2011-02-12 20:08:49 +01:00
if ( ! empty ( $dboptions [ 'dbsocket' ])
and ( strpos ( $dboptions [ 'dbsocket' ], '/' ) !== false or strpos ( $dboptions [ 'dbsocket' ], '\\' ) !== false )) {
$dbsocket = $dboptions [ 'dbsocket' ];
} else {
$dbsocket = ini_get ( 'mysqli.default_socket' );
}
if ( empty ( $dboptions [ 'dbport' ])) {
2011-02-06 21:15:40 +01:00
$dbport = ( int ) ini_get ( 'mysqli.default_port' );
2010-12-09 06:18:20 +00:00
} else {
2011-02-12 20:08:49 +01:00
$dbport = ( int ) $dboptions [ 'dbport' ];
2010-12-09 06:18:20 +00:00
}
2011-02-06 21:15:40 +01:00
// verify ini.get does not return nonsense
if ( empty ( $dbport )) {
$dbport = 3306 ;
}
2008-11-30 19:29:37 +00:00
ob_start ();
2012-06-05 10:31:03 +02:00
$conn = new mysqli ( $dbhost , $dbuser , $dbpass , '' , $dbport , $dbsocket ); // Connect without db
2008-11-30 19:29:37 +00:00
$dberr = ob_get_contents ();
ob_end_clean ();
$errorno = @ $conn -> connect_errno ;
if ( $errorno !== 0 ) {
throw new dml_connection_exception ( $dberr );
}
2017-01-10 14:50:35 +08:00
// Normally a check would be done before setting utf8mb4, but the database can be created
// before the enviroment checks are done. We'll proceed with creating the database and then do checks next.
$charset = 'utf8mb4' ;
if ( isset ( $dboptions [ 'dbcollation' ]) and ( strpos ( $dboptions [ 'dbcollation' ], 'utf8_' ) === 0
|| strpos ( $dboptions [ 'dbcollation' ], 'utf8mb4_' ) === 0 )) {
2012-07-11 16:39:20 +02:00
$collation = $dboptions [ 'dbcollation' ];
2017-01-10 14:50:35 +08:00
$collationinfo = explode ( '_' , $dboptions [ 'dbcollation' ]);
$charset = reset ( $collationinfo );
2012-07-11 16:39:20 +02:00
} else {
2017-01-10 14:50:35 +08:00
$collation = 'utf8mb4_unicode_ci' ;
2012-07-11 16:39:20 +02:00
}
2017-01-10 14:50:35 +08:00
$result = $conn -> query ( " CREATE DATABASE $dbname DEFAULT CHARACTER SET $charset DEFAULT COLLATE " . $collation );
2008-11-30 19:29:37 +00:00
$conn -> close ();
if ( ! $result ) {
throw new dml_exception ( 'cannotcreatedb' );
}
return true ;
}
2008-10-17 22:43:42 +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 ( 'mysqli' )) {
return get_string ( 'mysqliextensionisnotpresentinphp' , '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 'mysql' ;
}
/**
* 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-17 22:43:42 +00:00
*/
protected function get_dbtype () {
return 'mysqli' ;
}
/**
* 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-17 22:43:42 +00:00
*/
protected function get_dblibrary () {
return 'native' ;
}
2010-08-18 16:47:00 +00:00
/**
* Returns the current MySQL db engine .
*
* This is an ugly workaround for MySQL default engine problems ,
* Moodle is designed to work best on ACID compliant databases
* with full transaction support . Do not use MyISAM .
*
* @ return string or null MySQL engine name
*/
public function get_dbengine () {
if ( isset ( $this -> dboptions [ 'dbengine' ])) {
return $this -> dboptions [ 'dbengine' ];
}
2012-07-11 15:54:03 +02:00
if ( $this -> external ) {
return null ;
}
2010-08-18 16:47:00 +00:00
$engine = null ;
2012-07-11 15:54:03 +02:00
// Look for current engine of our config table (the first table that gets created),
// so that we create all tables with the same engine.
$sql = " SELECT engine
FROM INFORMATION_SCHEMA . TABLES
WHERE table_schema = DATABASE () AND table_name = '{$this->prefix}config' " ;
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
if ( $rec = $result -> fetch_assoc ()) {
2017-09-04 22:58:00 +02:00
// MySQL 8 BC: information_schema.* returns the fields in upper case.
$rec = array_change_key_case ( $rec , CASE_LOWER );
2012-07-11 15:54:03 +02:00
$engine = $rec [ 'engine' ];
2010-08-18 16:47:00 +00:00
}
2012-07-11 15:54:03 +02:00
$result -> close ();
2010-08-18 16:47:00 +00:00
if ( $engine ) {
2012-07-11 15:54:03 +02:00
// Cache the result to improve performance.
$this -> dboptions [ 'dbengine' ] = $engine ;
2010-08-18 16:47:00 +00:00
return $engine ;
}
2015-11-02 17:52:59 +08:00
// Get the default database engine.
$sql = " SELECT @@default_storage_engine engine " ;
2010-08-18 16:47:00 +00:00
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
if ( $rec = $result -> fetch_assoc ()) {
2015-11-02 17:52:59 +08:00
$engine = $rec [ 'engine' ];
2010-08-18 16:47:00 +00:00
}
$result -> close ();
2012-07-11 15:54:03 +02:00
if ( $engine === 'MyISAM' ) {
2010-08-18 16:47:00 +00:00
// we really do not want MyISAM for Moodle, InnoDB or XtraDB is a reasonable defaults if supported
$sql = " SHOW STORAGE ENGINES " ;
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
2010-08-19 07:09:14 +00:00
$engines = array ();
2010-08-18 16:47:00 +00:00
while ( $res = $result -> fetch_assoc ()) {
2010-08-19 07:09:14 +00:00
if ( $res [ 'Support' ] === 'YES' or $res [ 'Support' ] === 'DEFAULT' ) {
$engines [ $res [ 'Engine' ]] = true ;
2010-08-18 16:47:00 +00:00
}
}
$result -> close ();
2010-08-19 07:09:14 +00:00
if ( isset ( $engines [ 'InnoDB' ])) {
$engine = 'InnoDB' ;
}
if ( isset ( $engines [ 'XtraDB' ])) {
$engine = 'XtraDB' ;
}
2010-08-18 16:47:00 +00:00
}
2012-07-11 15:54:03 +02:00
// Cache the result to improve performance.
$this -> dboptions [ 'dbengine' ] = $engine ;
2010-08-18 16:47:00 +00:00
return $engine ;
}
2008-10-17 22:43:42 +00:00
/**
2012-07-11 16:39:20 +02:00
* Returns the current MySQL db collation .
*
* This is an ugly workaround for MySQL default collation problems .
*
* @ return string or null MySQL collation name
*/
public function get_dbcollation () {
if ( isset ( $this -> dboptions [ 'dbcollation' ])) {
return $this -> dboptions [ 'dbcollation' ];
}
if ( $this -> external ) {
return null ;
}
$collation = null ;
// Look for current collation of our config table (the first table that gets created),
// so that we create all tables with the same collation.
$sql = " SELECT collation_name
FROM INFORMATION_SCHEMA . COLUMNS
WHERE table_schema = DATABASE () AND table_name = '{$this->prefix}config' AND column_name = 'value' " ;
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
if ( $rec = $result -> fetch_assoc ()) {
2017-09-04 22:58:00 +02:00
// MySQL 8 BC: information_schema.* returns the fields in upper case.
$rec = array_change_key_case ( $rec , CASE_LOWER );
2012-07-11 16:39:20 +02:00
$collation = $rec [ 'collation_name' ];
}
$result -> close ();
2017-01-10 14:50:35 +08:00
2012-07-11 16:39:20 +02:00
if ( ! $collation ) {
// Get the default database collation, but only if using UTF-8.
$sql = " SELECT @@collation_database " ;
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
if ( $rec = $result -> fetch_assoc ()) {
2017-01-10 14:50:35 +08:00
if ( strpos ( $rec [ '@@collation_database' ], 'utf8_' ) === 0 || strpos ( $rec [ '@@collation_database' ], 'utf8mb4_' ) === 0 ) {
2012-07-11 16:39:20 +02:00
$collation = $rec [ '@@collation_database' ];
}
}
$result -> close ();
}
if ( ! $collation ) {
// We want only utf8 compatible collations.
$collation = null ;
2017-01-10 14:50:35 +08:00
$sql = " SHOW COLLATION WHERE Collation LIKE 'utf8mb4 \ _%' AND Charset = 'utf8mb4' " ;
2012-07-11 16:39:20 +02:00
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
while ( $res = $result -> fetch_assoc ()) {
$collation = $res [ 'Collation' ];
if ( strtoupper ( $res [ 'Default' ]) === 'YES' ) {
$collation = $res [ 'Collation' ];
break ;
}
}
$result -> close ();
}
// Cache the result to improve performance.
$this -> dboptions [ 'dbcollation' ] = $collation ;
return $collation ;
}
2018-05-29 00:18:52 +02:00
/**
* Tests if the Antelope file format is still supported or it has been removed .
* When removed , only Barracuda file format is supported , given the XtraDB / InnoDB engine .
*
* @ return bool True if the Antelope file format has been removed ; otherwise , false .
*/
protected function is_antelope_file_format_no_more_supported () {
// Breaking change: Antelope file format support has been removed from both MySQL and MariaDB.
// The following InnoDB file format configuration parameters were deprecated and then removed:
// - innodb_file_format
// - innodb_file_format_check
// - innodb_file_format_max
// - innodb_large_prefix
// 1. MySQL: deprecated in 5.7.7 and removed 8.0.0+.
$ismysqlge8d0d0 = ( $this -> get_dbtype () == 'mysqli' ) &&
version_compare ( $this -> get_server_info ()[ 'version' ], '8.0.0' , '>=' );
// 2. MariaDB: deprecated in 10.2.0 and removed 10.3.1+.
$ismariadbge10d3d1 = ( $this -> get_dbtype () == 'mariadb' ) &&
version_compare ( $this -> get_server_info ()[ 'version' ], '10.3.1' , '>=' );
return $ismysqlge8d0d0 || $ismariadbge10d3d1 ;
}
2014-07-07 18:31:45 +12:00
/**
* Get the row format from the database schema .
*
* @ param string $table
* @ return string row_format name or null if not known or table does not exist .
*/
2017-01-10 14:50:35 +08:00
public function get_row_format ( $table = null ) {
2014-07-07 18:31:45 +12:00
$rowformat = null ;
2017-01-10 14:50:35 +08:00
if ( isset ( $table )) {
$table = $this -> mysqli -> real_escape_string ( $table );
$sql = " SELECT row_format
FROM INFORMATION_SCHEMA . TABLES
2019-08-22 16:07:21 +08:00
WHERE table_schema = DATABASE () AND table_name = '{$this->prefix}$table' " ;
2017-01-10 14:50:35 +08:00
} else {
2018-05-29 00:18:52 +02:00
if ( $this -> is_antelope_file_format_no_more_supported ()) {
// Breaking change: Antelope file format support has been removed, only Barracuda.
2017-09-04 23:12:47 +02:00
$dbengine = $this -> get_dbengine ();
$supporteddbengines = array ( 'InnoDB' , 'XtraDB' );
if ( in_array ( $dbengine , $supporteddbengines )) {
$rowformat = 'Barracuda' ;
}
return $rowformat ;
}
2017-01-10 14:50:35 +08:00
$sql = " SHOW VARIABLES LIKE 'innodb_file_format' " ;
}
2014-07-07 18:31:45 +12:00
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
if ( $rec = $result -> fetch_assoc ()) {
2017-09-04 22:58:00 +02:00
// MySQL 8 BC: information_schema.* returns the fields in upper case.
$rec = array_change_key_case ( $rec , CASE_LOWER );
2017-01-10 14:50:35 +08:00
if ( isset ( $table )) {
$rowformat = $rec [ 'row_format' ];
} else {
2017-09-04 22:58:00 +02:00
$rowformat = $rec [ 'value' ];
2017-01-10 14:50:35 +08:00
}
2014-07-07 18:31:45 +12:00
}
$result -> close ();
return $rowformat ;
}
/**
* Is this database compatible with compressed row format ?
* This feature is necessary for support of large number of text
* columns in InnoDB / XtraDB database .
*
* @ param bool $cached use cached result
* @ return bool true if table can be created or changed to compressed row format .
*/
public function is_compressed_row_format_supported ( $cached = true ) {
if ( $cached and isset ( $this -> compressedrowformatsupported )) {
return ( $this -> compressedrowformatsupported );
}
$engine = strtolower ( $this -> get_dbengine ());
$info = $this -> get_server_info ();
if ( version_compare ( $info [ 'version' ], '5.5.0' ) < 0 ) {
// MySQL 5.1 is not supported here because we cannot read the file format.
$this -> compressedrowformatsupported = false ;
} else if ( $engine !== 'innodb' and $engine !== 'xtradb' ) {
// Other engines are not supported, most probably not compatible.
$this -> compressedrowformatsupported = false ;
2017-01-10 14:50:35 +08:00
} else if ( ! $this -> is_file_per_table_enabled ()) {
2014-07-07 18:31:45 +12:00
$this -> compressedrowformatsupported = false ;
2017-01-10 14:50:35 +08:00
} else if ( $this -> get_row_format () !== 'Barracuda' ) {
2014-07-07 18:31:45 +12:00
$this -> compressedrowformatsupported = false ;
} else {
// All the tests passed, we can safely use ROW_FORMAT=Compressed in sql statements.
$this -> compressedrowformatsupported = true ;
}
return $this -> compressedrowformatsupported ;
}
2017-01-10 14:50:35 +08:00
/**
* Check the database to see if innodb_file_per_table is on .
*
* @ return bool True if on otherwise false .
*/
public function is_file_per_table_enabled () {
if ( $filepertable = $this -> get_record_sql ( " SHOW VARIABLES LIKE 'innodb_file_per_table' " )) {
if ( $filepertable -> value == 'ON' ) {
return true ;
}
}
return false ;
}
/**
* Check the database to see if innodb_large_prefix is on .
*
* @ return bool True if on otherwise false .
*/
public function is_large_prefix_enabled () {
2018-05-29 00:18:52 +02:00
if ( $this -> is_antelope_file_format_no_more_supported ()) {
// Breaking change: Antelope file format support has been removed, only Barracuda.
2017-09-04 23:12:47 +02:00
return true ;
}
2017-01-10 14:50:35 +08:00
if ( $largeprefix = $this -> get_record_sql ( " SHOW VARIABLES LIKE 'innodb_large_prefix' " )) {
if ( $largeprefix -> value == 'ON' ) {
return true ;
}
}
return false ;
}
/**
* Determine if the row format should be set to compressed , dynamic , or default .
*
* Terrible kludge . If we 're using utf8mb4 AND we' re using InnoDB , we need to specify row format to
* be either dynamic or compressed ( default is compact ) in order to allow for bigger indexes ( MySQL
* errors #1709 and #1071).
*
* @ param string $engine The database engine being used . Will be looked up if not supplied .
* @ param string $collation The database collation to use . Will look up the current collation if not supplied .
* @ return string An sql fragment to add to sql statements .
*/
public function get_row_format_sql ( $engine = null , $collation = null ) {
if ( ! isset ( $engine )) {
$engine = $this -> get_dbengine ();
}
$engine = strtolower ( $engine );
if ( ! isset ( $collation )) {
$collation = $this -> get_dbcollation ();
}
$rowformat = '' ;
if (( $engine === 'innodb' || $engine === 'xtradb' ) && strpos ( $collation , 'utf8mb4_' ) === 0 ) {
if ( $this -> is_compressed_row_format_supported ()) {
$rowformat = " ROW_FORMAT=Compressed " ;
} else {
$rowformat = " ROW_FORMAT=Dynamic " ;
}
}
return $rowformat ;
}
2012-07-11 16:39:20 +02:00
/**
2008-10-17 22:43:42 +00:00
* Returns localised database type name
* Note : can be used before connect ()
* @ return string
*/
public function get_name () {
2008-10-25 18:37:59 +00:00
return get_string ( 'nativemysqli' , 'install' );
2008-10-17 22:43:42 +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 ( 'nativemysqlihelp' , 'install' );
}
2010-08-18 16:47:00 +00:00
/**
* Diagnose database and tables , this function is used
* to verify database and driver settings , db engine types , etc .
*
* @ return string null means everything ok , string means problem found .
*/
public function diagnose () {
$sloppymyisamfound = false ;
$prefix = str_replace ( '_' , '\\_' , $this -> prefix );
2012-06-30 15:10:41 +02:00
$sql = " SELECT COUNT('x')
FROM INFORMATION_SCHEMA . TABLES
WHERE table_schema = DATABASE ()
AND table_name LIKE BINARY '$prefix%'
AND Engine = 'MyISAM' " ;
2010-08-18 16:47:00 +00:00
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
if ( $result ) {
2012-06-30 15:10:41 +02:00
if ( $arr = $result -> fetch_assoc ()) {
$count = reset ( $arr );
if ( $count ) {
2010-08-18 16:47:00 +00:00
$sloppymyisamfound = true ;
}
}
$result -> close ();
}
if ( $sloppymyisamfound ) {
return get_string ( 'myisamproblem' , 'error' );
} else {
return null ;
}
}
2008-10-17 22:43:42 +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 . e
2008-10-17 22:43:42 +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
* @ return bool success
*/
2008-10-27 22:21:34 +00:00
public function connect ( $dbhost , $dbuser , $dbpass , $dbname , $prefix , array $dboptions = null ) {
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-17 22:43:42 +00:00
2011-01-14 10:20:04 +01:00
// dbsocket is used ONLY if host is NULL or 'localhost',
// you can not disable it because it is always tried if dbhost is 'localhost'
2011-02-06 22:11:28 +01:00
if ( ! empty ( $this -> dboptions [ 'dbsocket' ])
and ( strpos ( $this -> dboptions [ 'dbsocket' ], '/' ) !== false or strpos ( $this -> dboptions [ 'dbsocket' ], '\\' ) !== false )) {
2011-01-14 10:20:04 +01:00
$dbsocket = $this -> dboptions [ 'dbsocket' ];
} else {
$dbsocket = ini_get ( 'mysqli.default_socket' );
}
2010-12-07 07:30:35 +00:00
if ( empty ( $this -> dboptions [ 'dbport' ])) {
2011-02-06 21:15:40 +01:00
$dbport = ( int ) ini_get ( 'mysqli.default_port' );
2010-12-07 07:30:35 +00:00
} else {
$dbport = ( int ) $this -> dboptions [ 'dbport' ];
}
2011-02-06 21:15:40 +01:00
// verify ini.get does not return nonsense
if ( empty ( $dbport )) {
$dbport = 3306 ;
}
2012-11-24 10:47:28 +01:00
if ( $dbhost and ! empty ( $this -> dboptions [ 'dbpersist' ])) {
$dbhost = " p: $dbhost " ;
}
2014-10-08 13:56:31 +02:00
$this -> mysqli = @ new mysqli ( $dbhost , $dbuser , $dbpass , $dbname , $dbport , $dbsocket );
2008-11-22 01:16:52 +00:00
2014-10-08 13:56:31 +02:00
if ( $this -> mysqli -> connect_errno !== 0 ) {
$dberr = $this -> mysqli -> connect_error ;
2012-07-22 11:13:14 +02:00
$this -> mysqli = null ;
2008-11-22 01:16:52 +00:00
throw new dml_connection_exception ( $dberr );
2008-10-17 22:43:42 +00:00
}
2008-11-22 01:16:52 +00:00
2017-04-27 09:39:36 +08:00
// Disable logging until we are fully setup.
$this -> query_log_prevent ();
2017-01-10 14:50:35 +08:00
if ( isset ( $dboptions [ 'dbcollation' ])) {
$collationinfo = explode ( '_' , $dboptions [ 'dbcollation' ]);
$this -> dboptions [ 'dbcollation' ] = $dboptions [ 'dbcollation' ];
} else {
$collationinfo = explode ( '_' , $this -> get_dbcollation ());
}
$charset = reset ( $collationinfo );
2008-11-04 22:25:23 +00:00
$this -> query_start ( " --set_charset() " , null , SQL_QUERY_AUX );
2017-01-10 14:50:35 +08:00
$this -> mysqli -> set_charset ( $charset );
2008-11-04 22:25:23 +00:00
$this -> query_end ( true );
2009-08-28 11:17:47 +00:00
// If available, enforce strict mode for the session. That guaranties
// standard behaviour under some situations, avoiding some MySQL nasty
// habits like truncating data or performing some transparent cast losses.
2010-05-21 18:25:59 +00:00
// With strict mode enforced, Moodle DB layer will be consistently throwing
2009-08-28 11:17:47 +00:00
// the corresponding exceptions as expected.
$si = $this -> get_server_info ();
if ( version_compare ( $si [ 'version' ], '5.0.2' , '>=' )) {
$sql = " SET SESSION sql_mode = 'STRICT_ALL_TABLES' " ;
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
}
2016-12-21 13:54:56 +05:30
// We can enable logging now.
$this -> query_log_allow ();
2012-06-05 10:31:03 +02:00
// Connection stabilised and configured, going to instantiate the temptables controller
2009-08-31 16:14:43 +00:00
$this -> temptables = new mysqli_native_moodle_temptables ( $this );
2008-10-17 22:43:42 +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:25:59 +00:00
parent :: dispose (); // Call parent dispose to write/close session and other common stuff before closing connection
2008-10-17 22:43:42 +00:00
if ( $this -> mysqli ) {
$this -> mysqli -> close ();
$this -> mysqli = null ;
}
}
/**
* Returns database server info array
2012-01-19 10:15:11 +08:00
* @ return array Array containing 'description' and 'version' info
2008-10-17 22:43:42 +00:00
*/
public function get_server_info () {
return array ( 'description' => $this -> mysqli -> server_info , 'version' => $this -> mysqli -> server_info );
}
/**
* Returns supported query parameter types
2012-01-19 10:15:11 +08:00
* @ return int bitmask of accepted SQL_PARAMS_ *
2008-10-17 22:43:42 +00:00
*/
protected function allowed_param_types () {
return SQL_PARAMS_QM ;
}
/**
* Returns last error reported by database engine .
2010-08-22 19:06:06 +00:00
* @ return string error message
2008-10-17 22:43:42 +00:00
*/
public function get_last_error () {
return $this -> mysqli -> error ;
}
/**
* Return tables in database WITHOUT current prefix
2012-01-19 10:15:11 +08:00
* @ param bool $usecache if true , returns list of cached tables .
2008-10-17 22:43:42 +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 ();
2012-05-11 08:56:01 +02:00
$prefix = str_replace ( '_' , '\\_' , $this -> prefix );
$sql = " SHOW TABLES LIKE ' $prefix %' " ;
2008-11-04 22:25:23 +00:00
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
2012-05-10 23:35:18 +02:00
$len = strlen ( $this -> prefix );
2008-11-04 22:25:23 +00:00
if ( $result ) {
2008-10-17 22:43:42 +00:00
while ( $arr = $result -> fetch_assoc ()) {
$tablename = reset ( $arr );
2012-05-10 23:35:18 +02:00
$tablename = substr ( $tablename , $len );
2009-01-12 18:10:50 +00:00
$this -> tables [ $tablename ] = $tablename ;
2008-10-17 22:43:42 +00:00
}
$result -> close ();
}
2009-08-31 16:14:43 +00:00
// Add the currently available temptables
$this -> tables = array_merge ( $this -> tables , $this -> temptables -> get_temptables ());
2009-01-12 18:10:50 +00:00
return $this -> tables ;
2008-10-17 22:43:42 +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 .
* @ return array An associative array of indexes containing 'unique' flag and 'columns' being indexed
2008-10-17 22:43:42 +00:00
*/
public function get_indexes ( $table ) {
$indexes = array ();
2019-08-22 15:36:50 +08:00
$fixedtable = $this -> fix_table_name ( $table );
$sql = " SHOW INDEXES FROM $fixedtable " ;
2008-11-04 22:25:23 +00:00
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
2012-03-31 23:51:02 +02:00
try {
$this -> query_end ( $result );
} catch ( dml_read_exception $e ) {
return $indexes ; // table does not exist - no indexes...
}
2008-11-04 22:25:23 +00:00
if ( $result ) {
2008-10-17 22:43:42 +00:00
while ( $res = $result -> fetch_object ()) {
if ( $res -> Key_name === 'PRIMARY' ) {
continue ;
}
if ( ! isset ( $indexes [ $res -> Key_name ])) {
$indexes [ $res -> Key_name ] = array ( 'unique' => empty ( $res -> Non_unique ), 'columns' => array ());
}
$indexes [ $res -> Key_name ][ 'columns' ][ $res -> Seq_in_index - 1 ] = $res -> Column_name ;
}
$result -> close ();
}
return $indexes ;
}
/**
2010-05-21 18:25:59 +00:00
* Returns detailed information about columns in table . This information is cached internally .
2008-10-17 22:43:42 +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-17 22:43:42 +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-17 22:43:42 +00:00
}
2012-08-14 11:38:13 +12:00
$structure = array ();
2008-10-17 22:43:42 +00:00
2012-03-10 19:33:22 +01:00
$sql = " SELECT column_name, data_type, character_maximum_length, numeric_precision,
numeric_scale , is_nullable , column_type , column_default , column_key , extra
FROM information_schema . columns
2019-08-22 16:07:21 +08:00
WHERE table_name = '" . $this->prefix.$table . "'
2012-03-10 19:33:22 +01:00
AND table_schema = '" . $this->dbname . "'
ORDER BY ordinal_position " ;
2008-11-04 22:25:23 +00:00
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
2011-11-12 22:27:24 +01:00
$this -> query_end ( true ); // Don't want to throw anything here ever. MDL-30147
2008-11-04 22:25:23 +00:00
if ( $result === false ) {
2008-10-17 22:43:42 +00:00
return array ();
}
2012-03-10 19:33:22 +01:00
if ( $result -> num_rows > 0 ) {
// standard table exists
while ( $rawcolumn = $result -> fetch_assoc ()) {
2017-09-04 22:58:00 +02:00
// MySQL 8 BC: information_schema.* returns the fields in upper case.
$rawcolumn = array_change_key_case ( $rawcolumn , CASE_LOWER );
2012-03-10 19:33:22 +01:00
$info = ( object ) $this -> get_column_info (( object ) $rawcolumn );
2012-08-14 11:38:13 +12:00
$structure [ $info -> name ] = new database_column_info ( $info );
2012-03-10 19:33:22 +01:00
}
$result -> close ();
} else {
// temporary tables are not in information schema, let's try it the old way
$result -> close ();
2019-08-22 16:07:21 +08:00
$fixedtable = $this -> fix_table_name ( $table );
2019-08-22 15:36:50 +08:00
$sql = " SHOW COLUMNS FROM $fixedtable " ;
2012-03-10 19:33:22 +01:00
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( true );
if ( $result === false ) {
return array ();
}
while ( $rawcolumn = $result -> fetch_assoc ()) {
$rawcolumn = ( object ) array_change_key_case ( $rawcolumn , CASE_LOWER );
$rawcolumn -> column_name = $rawcolumn -> field ; unset ( $rawcolumn -> field );
$rawcolumn -> column_type = $rawcolumn -> type ; unset ( $rawcolumn -> type );
$rawcolumn -> character_maximum_length = null ;
$rawcolumn -> numeric_precision = null ;
$rawcolumn -> numeric_scale = null ;
$rawcolumn -> is_nullable = $rawcolumn -> null ; unset ( $rawcolumn -> null );
$rawcolumn -> column_default = $rawcolumn -> default ; unset ( $rawcolumn -> default );
2017-07-25 09:06:18 +02:00
$rawcolumn -> column_key = $rawcolumn -> key ; unset ( $rawcolumn -> key );
2012-03-10 19:33:22 +01:00
if ( preg_match ( '/(enum|varchar)\((\d+)\)/i' , $rawcolumn -> column_type , $matches )) {
$rawcolumn -> data_type = $matches [ 1 ];
$rawcolumn -> character_maximum_length = $matches [ 2 ];
} else if ( preg_match ( '/([a-z]*int[a-z]*)\((\d+)\)/i' , $rawcolumn -> column_type , $matches )) {
$rawcolumn -> data_type = $matches [ 1 ];
2012-10-10 10:30:02 +02:00
$rawcolumn -> numeric_precision = $matches [ 2 ];
$rawcolumn -> max_length = $rawcolumn -> numeric_precision ;
$type = strtoupper ( $matches [ 1 ]);
if ( $type === 'BIGINT' ) {
$maxlength = 18 ;
} else if ( $type === 'INT' or $type === 'INTEGER' ) {
$maxlength = 9 ;
} else if ( $type === 'MEDIUMINT' ) {
$maxlength = 6 ;
} else if ( $type === 'SMALLINT' ) {
$maxlength = 4 ;
} else if ( $type === 'TINYINT' ) {
$maxlength = 2 ;
2012-10-02 16:21:17 +02:00
} else {
2012-10-10 10:30:02 +02:00
// This should not happen.
$maxlength = 0 ;
}
if ( $maxlength < $rawcolumn -> max_length ) {
$rawcolumn -> max_length = $maxlength ;
2012-10-02 16:21:17 +02:00
}
2012-03-10 19:33:22 +01:00
} else if ( preg_match ( '/(decimal)\((\d+),(\d+)\)/i' , $rawcolumn -> column_type , $matches )) {
$rawcolumn -> data_type = $matches [ 1 ];
$rawcolumn -> numeric_precision = $matches [ 2 ];
$rawcolumn -> numeric_scale = $matches [ 3 ];
} else if ( preg_match ( '/(double|float)(\((\d+),(\d+)\))?/i' , $rawcolumn -> column_type , $matches )) {
$rawcolumn -> data_type = $matches [ 1 ];
$rawcolumn -> numeric_precision = isset ( $matches [ 3 ]) ? $matches [ 3 ] : null ;
$rawcolumn -> numeric_scale = isset ( $matches [ 4 ]) ? $matches [ 4 ] : null ;
} else if ( preg_match ( '/([a-z]*text)/i' , $rawcolumn -> column_type , $matches )) {
$rawcolumn -> data_type = $matches [ 1 ];
$rawcolumn -> character_maximum_length = - 1 ; // unknown
} else if ( preg_match ( '/([a-z]*blob)/i' , $rawcolumn -> column_type , $matches )) {
$rawcolumn -> data_type = $matches [ 1 ];
2008-10-17 22:43:42 +00:00
} else {
2012-03-10 19:33:22 +01:00
$rawcolumn -> data_type = $rawcolumn -> column_type ;
2008-10-17 22:43:42 +00:00
}
2012-03-10 19:33:22 +01:00
$info = $this -> get_column_info ( $rawcolumn );
2012-08-14 11:38:13 +12:00
$structure [ $info -> name ] = new database_column_info ( $info );
2012-03-10 19:33:22 +01:00
}
$result -> close ();
}
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 ;
2012-03-10 19:33:22 +01:00
}
2008-10-25 18:26:55 +00:00
2017-07-25 09:07:20 +02:00
/**
* Indicates whether column information retrieved from `information_schema.columns` has default values quoted or not .
* @ return boolean True when default values are quoted ( breaking change ); otherwise , false .
*/
protected function has_breaking_change_quoted_defaults () {
return false ;
}
2017-09-04 09:57:20 +02:00
/**
* Indicates whether SQL_MODE default value has changed in a not backward compatible way .
* @ return boolean True when SQL_MODE breaks BC ; otherwise , false .
*/
public function has_breaking_change_sqlmode () {
return false ;
}
2012-03-10 19:33:22 +01:00
/**
* Returns moodle column info for raw column from information schema .
* @ param stdClass $rawcolumn
* @ return stdClass standardised colum info
*/
private function get_column_info ( stdClass $rawcolumn ) {
$rawcolumn = ( object ) $rawcolumn ;
$info = new stdClass ();
$info -> name = $rawcolumn -> column_name ;
$info -> type = $rawcolumn -> data_type ;
$info -> meta_type = $this -> mysqltype2moodletype ( $rawcolumn -> data_type );
2017-07-25 09:07:20 +02:00
if ( $this -> has_breaking_change_quoted_defaults ()) {
$info -> default_value = trim ( $rawcolumn -> column_default , " ' " );
2017-08-03 00:13:03 +02:00
if ( $info -> default_value === 'NULL' ) {
$info -> default_value = null ;
}
2017-07-25 09:07:20 +02:00
} else {
$info -> default_value = $rawcolumn -> column_default ;
}
2017-08-03 00:13:03 +02:00
$info -> has_default = ! is_null ( $info -> default_value );
2012-03-10 19:33:22 +01:00
$info -> not_null = ( $rawcolumn -> is_nullable === 'NO' );
$info -> primary_key = ( $rawcolumn -> column_key === 'PRI' );
$info -> binary = false ;
$info -> unsigned = null ;
$info -> auto_increment = false ;
$info -> unique = null ;
$info -> scale = null ;
if ( $info -> meta_type === 'C' ) {
$info -> max_length = $rawcolumn -> character_maximum_length ;
} else if ( $info -> meta_type === 'I' ) {
if ( $info -> primary_key ) {
$info -> meta_type = 'R' ;
$info -> unique = true ;
2008-10-17 22:43:42 +00:00
}
2012-10-02 16:21:17 +02:00
// Return number of decimals, not bytes here.
2012-03-10 19:33:22 +01:00
$info -> max_length = $rawcolumn -> numeric_precision ;
2012-10-02 16:21:17 +02:00
if ( preg_match ( '/([a-z]*int[a-z]*)\((\d+)\)/i' , $rawcolumn -> column_type , $matches )) {
2012-10-10 10:30:02 +02:00
$type = strtoupper ( $matches [ 1 ]);
if ( $type === 'BIGINT' ) {
2012-10-02 16:21:17 +02:00
$maxlength = 18 ;
2012-10-10 10:30:02 +02:00
} else if ( $type === 'INT' or $type === 'INTEGER' ) {
2012-10-02 16:21:17 +02:00
$maxlength = 9 ;
2012-10-10 10:30:02 +02:00
} else if ( $type === 'MEDIUMINT' ) {
$maxlength = 6 ;
} else if ( $type === 'SMALLINT' ) {
2012-10-02 16:21:17 +02:00
$maxlength = 4 ;
2012-10-10 10:30:02 +02:00
} else if ( $type === 'TINYINT' ) {
2012-10-02 16:21:17 +02:00
$maxlength = 2 ;
} else {
2012-10-10 10:30:02 +02:00
// This should not happen.
2012-10-02 16:21:17 +02:00
$maxlength = 0 ;
}
// It is possible that display precision is different from storage type length,
// always use the smaller value to make sure our data fits.
if ( $maxlength < $info -> max_length ) {
$info -> max_length = $maxlength ;
}
}
2012-03-10 19:33:22 +01:00
$info -> unsigned = ( stripos ( $rawcolumn -> column_type , 'unsigned' ) !== false );
$info -> auto_increment = ( strpos ( $rawcolumn -> extra , 'auto_increment' ) !== false );
} else if ( $info -> meta_type === 'N' ) {
$info -> max_length = $rawcolumn -> numeric_precision ;
$info -> scale = $rawcolumn -> numeric_scale ;
$info -> unsigned = ( stripos ( $rawcolumn -> column_type , 'unsigned' ) !== false );
} else if ( $info -> meta_type === 'X' ) {
if ( " $rawcolumn->character_maximum_length " === '4294967295' ) { // watch out for PHP max int limits!
// means maximum moodle size for text column, in other drivers it may also mean unknown size
$info -> max_length = - 1 ;
} else {
$info -> max_length = $rawcolumn -> character_maximum_length ;
}
$info -> primary_key = false ;
2008-10-17 22:43:42 +00:00
2012-03-10 19:33:22 +01:00
} else if ( $info -> meta_type === 'B' ) {
$info -> max_length = - 1 ;
$info -> primary_key = false ;
$info -> binary = true ;
2008-10-17 22:43:42 +00:00
}
2012-03-10 19:33:22 +01:00
return $info ;
}
2008-11-04 22:25:23 +00:00
2012-03-10 19:33:22 +01:00
/**
* Normalise column type .
* @ param string $mysql_type
* @ return string one character
* @ throws dml_exception
*/
private function mysqltype2moodletype ( $mysql_type ) {
$type = null ;
switch ( strtoupper ( $mysql_type )) {
case 'BIT' :
$type = 'L' ;
break ;
case 'TINYINT' :
case 'SMALLINT' :
case 'MEDIUMINT' :
case 'INT' :
2012-10-10 10:30:02 +02:00
case 'INTEGER' :
2012-03-10 19:33:22 +01:00
case 'BIGINT' :
$type = 'I' ;
break ;
case 'FLOAT' :
case 'DOUBLE' :
case 'DECIMAL' :
$type = 'N' ;
break ;
case 'CHAR' :
case 'ENUM' :
case 'SET' :
case 'VARCHAR' :
$type = 'C' ;
break ;
case 'TINYTEXT' :
case 'TEXT' :
case 'MEDIUMTEXT' :
case 'LONGTEXT' :
$type = 'X' ;
break ;
case 'BINARY' :
case 'VARBINARY' :
case 'BLOB' :
case 'TINYBLOB' :
case 'MEDIUMBLOB' :
case 'LONGBLOB' :
$type = 'B' ;
break ;
case 'DATE' :
case 'TIME' :
case 'DATETIME' :
case 'TIMESTAMP' :
case 'YEAR' :
$type = 'D' ;
break ;
}
if ( ! $type ) {
throw new dml_exception ( 'invalidmysqlnativetype' , $mysql_type );
}
return $type ;
2008-10-17 22:43:42 +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 ) {
2012-03-17 18:42:30 +01: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 ;
} else if ( $value === '' ) {
if ( $column -> meta_type == 'I' or $column -> meta_type == 'F' or $column -> meta_type == 'N' ) {
$value = 0 ; // prevent '' problems in numeric fields
}
2011-05-05 17:42:41 +02:00
// Any float value being stored in varchar or text field is converted to string to avoid
// any implicit conversion by MySQL
} else if ( is_float ( $value ) and ( $column -> meta_type == 'C' or $column -> meta_type == 'X' )) {
$value = " $value " ;
2009-11-03 23:34:43 +00:00
}
return $value ;
}
2008-10-17 22:43:42 +00:00
/**
2012-12-22 18:42:55 +01:00
* Is this database compatible with utf8 ?
2008-10-17 22:43:42 +00:00
* @ return bool
*/
public function setup_is_unicodedb () {
2012-12-22 18:42:55 +01:00
// All new tables are created with this collation, we just have to make sure it is utf8 compatible,
// if config table already exists it has this collation too.
$collation = $this -> get_dbcollation ();
2010-11-17 01:58:38 +00:00
2017-01-10 14:50:35 +08:00
$collationinfo = explode ( '_' , $collation );
$charset = reset ( $collationinfo );
$sql = " SHOW COLLATION WHERE Collation =' $collation ' AND Charset = ' $charset ' " ;
2012-12-22 18:42:55 +01:00
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
2010-11-17 01:58:38 +00:00
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
2012-12-22 18:42:55 +01:00
if ( $result -> fetch_assoc ()) {
$return = true ;
} else {
$return = false ;
2010-11-17 01:58:38 +00:00
}
2012-12-22 18:42:55 +01:00
$result -> close ();
2010-11-17 01:58:38 +00:00
return $return ;
2008-10-17 22:43:42 +00:00
}
/**
* 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-17 22:43:42 +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 );
}
2008-11-04 22:25:23 +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 = $this -> mysqli -> multi_query ( $sql );
if ( $result === false ) {
$this -> query_end ( false );
}
while ( $this -> mysqli -> more_results ()) {
$result = $this -> mysqli -> next_result ();
if ( $result === false ) {
$this -> query_end ( false );
}
2014-01-20 14:06:44 +08:00
}
2014-01-24 13:47:22 +08:00
$this -> query_end ( true );
2014-01-20 14:06:44 +08:00
} catch ( ddl_change_structure_exception $e ) {
2014-01-24 13:47:22 +08:00
while ( @ $this -> mysqli -> more_results ()) {
@ $this -> mysqli -> next_result ();
}
2016-03-15 14:41:48 +08:00
$this -> reset_caches ( $tablenames );
2014-01-20 14:06:44 +08:00
throw $e ;
}
2008-11-04 22:25:23 +00:00
2016-03-15 14:41:48 +08:00
$this -> reset_caches ( $tablenames );
2008-10-25 18:01:54 +00:00
return true ;
2008-10-17 22:43:42 +00:00
}
/**
* Very ugly hack which emulates bound parameters in queries
* because prepared statements do not use query cache .
*/
protected function emulate_bound_params ( $sql , array $params = null ) {
if ( empty ( $params )) {
return $sql ;
}
2012-06-05 10:31:03 +02:00
// ok, we have verified sql statement with ? and correct number of params
2013-06-11 20:56:39 -04:00
$parts = array_reverse ( explode ( '?' , $sql ));
$return = array_pop ( $parts );
2008-10-17 22:43:42 +00:00
foreach ( $params as $param ) {
if ( is_bool ( $param )) {
$return .= ( int ) $param ;
} else if ( is_null ( $param )) {
$return .= 'NULL' ;
2010-10-04 08:47:58 +00:00
} else if ( is_number ( $param )) {
$return .= " ' " . $param . " ' " ; // we have to always use strings because mysql is using weird automatic int casting
2009-01-13 13:12:59 +00:00
} else if ( is_float ( $param )) {
2008-10-17 22:43:42 +00:00
$return .= $param ;
} else {
$param = $this -> mysqli -> real_escape_string ( $param );
$return .= " ' $param ' " ;
}
2013-06-11 20:56:39 -04:00
$return .= array_pop ( $parts );
2008-10-17 22:43:42 +00:00
}
return $return ;
}
/**
* 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-17 22:43:42 +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-17 22:43:42 +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-17 22:43:42 +00:00
}
$rawsql = $this -> emulate_bound_params ( $sql , $params );
2008-11-04 22:25:23 +00:00
$this -> query_start ( $sql , $params , SQL_QUERY_UPDATE );
2008-10-17 22:43:42 +00:00
$result = $this -> mysqli -> query ( $rawsql );
2008-11-04 22:25:23 +00:00
$this -> query_end ( $result );
2008-10-17 22:43:42 +00:00
2008-11-21 20:09:13 +00:00
if ( $result === true ) {
2008-10-17 22:43:42 +00:00
return true ;
} else {
$result -> close ();
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-17 22:43:42 +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-17 22:43:42 +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 );
2008-12-04 11:52:53 +00:00
2008-10-17 22:43:42 +00:00
if ( $limitfrom or $limitnum ) {
if ( $limitnum < 1 ) {
$limitnum = " 18446744073709551615 " ;
}
2008-10-18 22:35:42 +00:00
$sql .= " LIMIT $limitfrom , $limitnum " ;
2008-10-17 22:43:42 +00:00
}
2008-10-18 22:35:42 +00:00
list ( $sql , $params , $type ) = $this -> fix_sql_params ( $sql , $params );
$rawsql = $this -> emulate_bound_params ( $sql , $params );
2008-11-04 22:25:23 +00:00
$this -> query_start ( $sql , $params , SQL_QUERY_SELECT );
2008-10-17 22:43:42 +00:00
// no MYSQLI_USE_RESULT here, it would block write ops on affected tables
2009-05-06 10:39:51 +00:00
$result = $this -> mysqli -> query ( $rawsql , MYSQLI_STORE_RESULT );
$this -> query_end ( $result );
2008-10-17 22:43:42 +00:00
2009-05-06 10:39:51 +00:00
return $this -> create_recordset ( $result );
2008-10-17 22:43:42 +00:00
}
2012-09-15 10:49:48 +02:00
/**
* Get all records from a table .
*
* This method works around potential memory problems and may improve performance ,
* this method may block access to table until the recordset is closed .
*
* @ param string $table Name of database table .
* @ return moodle_recordset A moodle_recordset instance { @ link function get_recordset } .
* @ throws dml_exception A DML specific exception is thrown for any errors .
*/
public function export_table_recordset ( $table ) {
$sql = $this -> fix_table_names ( " SELECT * FROM { { $table } } " );
$this -> query_start ( $sql , array (), SQL_QUERY_SELECT );
// MYSQLI_STORE_RESULT may eat all memory for large tables, unfortunately MYSQLI_USE_RESULT blocks other queries.
$result = $this -> mysqli -> query ( $sql , MYSQLI_USE_RESULT );
$this -> query_end ( $result );
return $this -> create_recordset ( $result );
}
2008-10-17 22:43:42 +00:00
protected function create_recordset ( $result ) {
return new mysqli_native_moodle_recordset ( $result );
}
/**
* 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-17 22:43:42 +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-17 22:43:42 +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 );
2008-12-04 11:52:53 +00:00
2008-10-17 22:43:42 +00:00
if ( $limitfrom or $limitnum ) {
if ( $limitnum < 1 ) {
$limitnum = " 18446744073709551615 " ;
}
2008-10-18 22:35:42 +00:00
$sql .= " LIMIT $limitfrom , $limitnum " ;
2008-10-17 22:43:42 +00:00
}
2008-10-18 22:35:42 +00:00
list ( $sql , $params , $type ) = $this -> fix_sql_params ( $sql , $params );
$rawsql = $this -> emulate_bound_params ( $sql , $params );
2008-11-04 22:25:23 +00:00
$this -> query_start ( $sql , $params , SQL_QUERY_SELECT );
2008-10-17 22:43:42 +00:00
$result = $this -> mysqli -> query ( $rawsql , MYSQLI_STORE_RESULT );
2008-11-04 22:25:23 +00:00
$this -> query_end ( $result );
2008-10-17 22:43:42 +00:00
$return = array ();
2008-11-04 22:25:23 +00:00
2008-10-17 22:43:42 +00:00
while ( $row = $result -> fetch_assoc ()) {
$row = array_change_key_case ( $row , CASE_LOWER );
$id = reset ( $row );
2008-11-21 22:35:21 +00:00
if ( isset ( $return [ $id ])) {
$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-10-17 22:43:42 +00:00
$return [ $id ] = ( object ) $row ;
}
$result -> close ();
2008-11-04 22:25:23 +00:00
2008-10-17 22:43:42 +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-17 22:43:42 +00:00
*/
public function get_fieldset_sql ( $sql , array $params = null ) {
list ( $sql , $params , $type ) = $this -> fix_sql_params ( $sql , $params );
$rawsql = $this -> emulate_bound_params ( $sql , $params );
2008-11-04 22:25:23 +00:00
$this -> query_start ( $sql , $params , SQL_QUERY_SELECT );
2008-10-17 22:43:42 +00:00
$result = $this -> mysqli -> query ( $rawsql , MYSQLI_STORE_RESULT );
2008-11-04 22:25:23 +00:00
$this -> query_end ( $result );
2008-10-17 22:43:42 +00:00
$return = array ();
2008-11-04 22:25:23 +00:00
2008-10-17 22:43:42 +00:00
while ( $row = $result -> fetch_assoc ()) {
$return [] = reset ( $row );
}
$result -> close ();
2008-11-04 22:25:23 +00:00
2008-10-17 22:43:42 +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-17 22:43:42 +00:00
*/
public function insert_record_raw ( $table , $params , $returnid = true , $bulk = false , $customsequence = false ) {
if ( ! is_array ( $params )) {
$params = ( array ) $params ;
}
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-17 22:43:42 +00:00
}
$returnid = false ;
} 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-17 22:43:42 +00:00
}
$fields = implode ( ',' , array_keys ( $params ));
$qms = array_fill ( 0 , count ( $params ), '?' );
$qms = implode ( ',' , $qms );
2019-08-22 15:36:50 +08:00
$fixedtable = $this -> fix_table_name ( $table );
$sql = " INSERT INTO $fixedtable ( $fields ) VALUES( $qms ) " ;
2009-08-27 14:01:28 +00:00
list ( $sql , $params , $type ) = $this -> fix_sql_params ( $sql , $params );
2008-10-17 22:43:42 +00:00
$rawsql = $this -> emulate_bound_params ( $sql , $params );
2008-11-04 22:25:23 +00:00
$this -> query_start ( $sql , $params , SQL_QUERY_INSERT );
2008-10-17 22:43:42 +00:00
$result = $this -> mysqli -> query ( $rawsql );
2009-06-13 15:59:55 +00:00
$id = @ $this -> mysqli -> insert_id ; // must be called before query_end() which may insert log into db
2008-11-04 22:25:23 +00:00
$this -> query_end ( $result );
2008-10-17 22:43:42 +00:00
2012-04-07 12:10:51 +02:00
if ( ! $customsequence and ! $id ) {
2008-11-21 20:09:13 +00:00
throw new dml_write_exception ( 'unknown error fetching inserted id' );
2008-10-17 22:43:42 +00:00
}
if ( ! $returnid ) {
return true ;
} else {
return ( int ) $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-17 22:43:42 +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-17 22:43:42 +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-17 22:43:42 +00:00
$cleaned = array ();
foreach ( $dataobject as $field => $value ) {
2010-07-31 20:51:22 +00:00
if ( $field === 'id' ) {
continue ;
}
2008-10-17 22:43:42 +00:00
if ( ! isset ( $columns [ $field ])) {
continue ;
}
$column = $columns [ $field ];
2009-11-03 23:34:43 +00:00
$cleaned [ $field ] = $this -> normalise_value ( $column , $value );
2008-10-17 22:43:42 +00:00
}
return $this -> insert_record_raw ( $table , $cleaned , $returnid , $bulk );
}
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' );
}
// MySQL has a relatively small query length limit by default,
// make sure 'max_allowed_packet' in my.cnf is high enough
// if you change the following default...
static $chunksize = null ;
if ( $chunksize === null ) {
if ( ! empty ( $this -> dboptions [ 'bulkinsertsize' ])) {
$chunksize = ( int ) $this -> dboptions [ 'bulkinsertsize' ];
} else {
if ( PHP_INT_SIZE === 4 ) {
// Bad luck for Windows, we cannot do any maths with large numbers.
$chunksize = 5 ;
} else {
$sql = " SHOW VARIABLES LIKE 'max_allowed_packet' " ;
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
$size = 0 ;
if ( $rec = $result -> fetch_assoc ()) {
$size = $rec [ 'Value' ];
}
$result -> close ();
// Hopefully 200kb per object are enough.
$chunksize = ( int )( $size / 200000 );
if ( $chunksize > 50 ) {
$chunksize = 50 ;
}
}
}
}
$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 );
}
}
/**
* Insert records in chunks .
*
* 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 ) {
$fieldssql = '(' . implode ( ',' , array_keys ( $columns )) . ')' ;
$valuessql = '(' . implode ( ',' , array_fill ( 0 , count ( $columns ), '?' )) . ')' ;
$valuessql = implode ( ',' , array_fill ( 0 , count ( $chunk ), $valuessql ));
$params = array ();
foreach ( $chunk as $dataobject ) {
foreach ( $columns as $field => $column ) {
$params [] = $this -> normalise_value ( $column , $dataobject [ $field ]);
}
}
2019-08-22 15:36:50 +08:00
$fixedtable = $this -> fix_table_name ( $table );
$sql = " INSERT INTO $fixedtable $fieldssql VALUES $valuessql " ;
2014-01-18 16:03:31 +08:00
list ( $sql , $params , $type ) = $this -> fix_sql_params ( $sql , $params );
$rawsql = $this -> emulate_bound_params ( $sql , $params );
$this -> query_start ( $sql , $params , SQL_QUERY_INSERT );
$result = $this -> mysqli -> query ( $rawsql );
$this -> query_end ( $result );
}
2008-10-17 22:43:42 +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-17 22:43:42 +00:00
*/
public function import_record ( $table , $dataobject ) {
2010-07-31 20:51:22 +00:00
$dataobject = ( array ) $dataobject ;
2008-10-17 22:43:42 +00:00
$columns = $this -> get_columns ( $table );
$cleaned = array ();
foreach ( $dataobject as $field => $value ) {
if ( ! isset ( $columns [ $field ])) {
continue ;
}
$cleaned [ $field ] = $value ;
}
return $this -> insert_record_raw ( $table , $cleaned , false , true , true );
}
/**
* 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-17 22:43:42 +00:00
*/
public function update_record_raw ( $table , $params , $bulk = false ) {
2010-07-31 20:51:22 +00:00
$params = ( array ) $params ;
2008-10-17 22:43:42 +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-17 22:43:42 +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-17 22:43:42 +00:00
}
$sets = array ();
foreach ( $params as $field => $value ) {
$sets [] = " $field = ? " ;
}
$params [] = $id ; // last ? in WHERE condition
$sets = implode ( ',' , $sets );
2019-08-22 15:36:50 +08:00
$fixedtable = $this -> fix_table_name ( $table );
$sql = " UPDATE $fixedtable SET $sets WHERE id=? " ;
2009-08-27 14:01:28 +00:00
list ( $sql , $params , $type ) = $this -> fix_sql_params ( $sql , $params );
2008-10-17 22:43:42 +00:00
$rawsql = $this -> emulate_bound_params ( $sql , $params );
2008-11-04 22:25:23 +00:00
$this -> query_start ( $sql , $params , SQL_QUERY_UPDATE );
2008-10-17 22:43:42 +00:00
$result = $this -> mysqli -> query ( $rawsql );
2008-11-04 22:25:23 +00:00
$this -> query_end ( $result );
2008-10-17 22:43:42 +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-17 22:43:42 +00:00
*/
public function update_record ( $table , $dataobject , $bulk = false ) {
2010-07-31 20:51:22 +00:00
$dataobject = ( array ) $dataobject ;
2008-10-17 22:43:42 +00:00
$columns = $this -> get_columns ( $table );
$cleaned = array ();
foreach ( $dataobject as $field => $value ) {
if ( ! isset ( $columns [ $field ])) {
continue ;
}
2009-11-03 23:34:43 +00:00
$column = $columns [ $field ];
$cleaned [ $field ] = $this -> normalise_value ( $column , $value );
2008-10-17 22:43:42 +00:00
}
return $this -> update_record_raw ( $table , $cleaned , $bulk );
}
/**
* 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-17 22:43:42 +00:00
*/
public function set_field_select ( $table , $newfield , $newvalue , $select , array $params = null ) {
if ( $select ) {
$select = " WHERE $select " ;
}
if ( is_null ( $params )) {
$params = array ();
}
list ( $select , $params , $type ) = $this -> fix_sql_params ( $select , $params );
2009-11-03 23:34:43 +00:00
// Get column metadata
$columns = $this -> get_columns ( $table );
$column = $columns [ $newfield ];
$normalised_value = $this -> normalise_value ( $column , $newvalue );
if ( is_null ( $normalised_value )) {
2008-10-17 22:43:42 +00:00
$newfield = " $newfield = NULL " ;
} else {
$newfield = " $newfield = ? " ;
2009-11-03 23:34:43 +00:00
array_unshift ( $params , $normalised_value );
2008-10-17 22:43:42 +00:00
}
2019-08-22 15:36:50 +08:00
$fixedtable = $this -> fix_table_name ( $table );
$sql = " UPDATE $fixedtable SET $newfield $select " ;
2008-10-17 22:43:42 +00:00
$rawsql = $this -> emulate_bound_params ( $sql , $params );
2008-11-04 22:25:23 +00:00
$this -> query_start ( $sql , $params , SQL_QUERY_UPDATE );
2008-10-17 22:43:42 +00:00
$result = $this -> mysqli -> query ( $rawsql );
2008-11-04 22:25:23 +00:00
$this -> query_end ( $result );
2008-10-17 22:43:42 +00:00
return true ;
}
/**
* Delete one or more records from a table which match a particular WHERE clause .
*
* @ 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-17 22:43:42 +00:00
*/
public function delete_records_select ( $table , $select , array $params = null ) {
if ( $select ) {
$select = " WHERE $select " ;
}
2019-08-22 15:36:50 +08:00
$fixedtable = $this -> fix_table_name ( $table );
$sql = " DELETE FROM $fixedtable $select " ;
2008-10-17 22:43:42 +00:00
list ( $sql , $params , $type ) = $this -> fix_sql_params ( $sql , $params );
$rawsql = $this -> emulate_bound_params ( $sql , $params );
2008-11-04 22:25:23 +00:00
$this -> query_start ( $sql , $params , SQL_QUERY_UPDATE );
2008-10-17 22:43:42 +00:00
$result = $this -> mysqli -> query ( $rawsql );
2008-11-04 22:25:23 +00:00
$this -> query_end ( $result );
2008-10-17 22:43:42 +00:00
return true ;
}
public function sql_cast_char2int ( $fieldname , $text = false ) {
return ' CAST(' . $fieldname . ' AS SIGNED) ' ;
2010-11-18 01:27:49 +00:00
}
public function sql_cast_char2real ( $fieldname , $text = false ) {
2016-04-29 03:42:59 +02:00
// Set to 65 (max mysql 5.5 precision) with 7 as scale
// because we must ensure at least 6 decimal positions
// per casting given that postgres is casting to that scale (::real::).
// Can be raised easily but that must be done in all DBs and tests.
return ' CAST(' . $fieldname . ' AS DECIMAL(65,7)) ' ;
2008-10-17 22:43:42 +00:00
}
2016-09-13 03:38:56 +02:00
public function sql_equal ( $fieldname , $param , $casesensitive = true , $accentsensitive = true , $notequal = false ) {
$equalop = $notequal ? '<>' : '=' ;
2017-01-10 14:50:35 +08:00
$collationinfo = explode ( '_' , $this -> get_dbcollation ());
$bincollate = reset ( $collationinfo ) . '_bin' ;
2016-09-13 03:38:56 +02:00
if ( $casesensitive ) {
// Current MySQL versions do not support case sensitive and accent insensitive.
2017-01-10 14:50:35 +08:00
return " $fieldname COLLATE $bincollate $equalop $param " ;
2016-09-13 03:38:56 +02:00
} else if ( $accentsensitive ) {
// Case insensitive and accent sensitive, we can force a binary comparison once all texts are using the same case.
2017-01-10 14:50:35 +08:00
return " LOWER( $fieldname ) COLLATE $bincollate $equalop LOWER( $param ) " ;
2016-09-13 03:38:56 +02:00
} else {
// Case insensitive and accent insensitive. All collations are that way, but utf8_bin.
$collation = '' ;
if ( $this -> get_dbcollation () == 'utf8_bin' ) {
$collation = 'COLLATE utf8_unicode_ci' ;
2017-01-10 14:50:35 +08:00
} else if ( $this -> get_dbcollation () == 'utf8mb4_bin' ) {
$collation = 'COLLATE utf8mb4_unicode_ci' ;
2016-09-13 03:38:56 +02:00
}
return " $fieldname $collation $equalop $param " ;
}
}
2010-08-24 21:50:53 +00:00
/**
* Returns 'LIKE' part of a query .
*
2015-09-18 11:42:12 +08:00
* Note that mysql does not support $casesensitive = true and $accentsensitive = false .
* More information in http :// bugs . mysql . com / bug . php ? id = 19567.
*
2010-08-24 21:50:53 +00:00
* @ param string $fieldname usually name of the table column
* @ param string $param usually bound query parameter ( ? , : named )
* @ param bool $casesensitive use case sensitive search
2015-09-18 11:42:12 +08:00
* @ param bool $accensensitive use accent sensitive search ( ignored if $casesensitive is true )
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
}
$escapechar = $this -> mysqli -> real_escape_string ( $escapechar ); // prevents problems with C-style escapes of enclosing '\'
2017-01-10 14:50:35 +08:00
$collationinfo = explode ( '_' , $this -> get_dbcollation ());
$bincollate = reset ( $collationinfo ) . '_bin' ;
2010-09-04 14:39:01 +00:00
$LIKE = $notlike ? 'NOT LIKE' : 'LIKE' ;
2015-09-18 11:42:12 +08:00
2010-08-24 21:50:53 +00:00
if ( $casesensitive ) {
2015-09-18 11:42:12 +08:00
// Current MySQL versions do not support case sensitive and accent insensitive.
2017-01-10 14:50:35 +08:00
return " $fieldname $LIKE $param COLLATE $bincollate ESCAPE ' $escapechar ' " ;
2015-09-18 11:42:12 +08:00
} else if ( $accentsensitive ) {
// Case insensitive and accent sensitive, we can force a binary comparison once all texts are using the same case.
2017-01-10 14:50:35 +08:00
return " LOWER( $fieldname ) $LIKE LOWER( $param ) COLLATE $bincollate ESCAPE ' $escapechar ' " ;
2015-09-18 11:42:12 +08:00
2010-08-24 21:50:53 +00:00
} else {
2015-09-18 11:42:12 +08:00
// Case insensitive and accent insensitive.
$collation = '' ;
if ( $this -> get_dbcollation () == 'utf8_bin' ) {
// Force a case insensitive comparison if using utf8_bin.
$collation = 'COLLATE utf8_unicode_ci' ;
2017-01-10 14:50:35 +08:00
} else if ( $this -> get_dbcollation () == 'utf8mb4_bin' ) {
// Force a case insensitive comparison if using utf8mb4_bin.
$collation = 'COLLATE utf8mb4_unicode_ci' ;
2010-08-24 21:50:53 +00:00
}
2015-09-18 11:42:12 +08:00
return " $fieldname $LIKE $param $collation ESCAPE ' $escapechar ' " ;
2010-08-24 21:50:53 +00:00
}
}
2010-11-11 01:57:35 +00:00
/**
* Returns the proper SQL to do CONCAT between the elements passed
* Can take many parameters
*
* @ param string $str , ... 1 or more fields / strings to concat
*
* @ return string The concat sql
*/
2008-10-17 22:43:42 +00:00
public function sql_concat () {
$arr = func_get_args ();
2008-11-22 19:32:16 +00:00
$s = implode ( ', ' , $arr );
2008-10-17 22:43:42 +00:00
if ( $s === '' ) {
2008-10-25 18:40:35 +00:00
return " '' " ;
2008-10-17 22:43:42 +00:00
}
return " CONCAT( $s ) " ;
}
2010-11-11 01:57:35 +00:00
/**
* Returns the proper SQL to do CONCAT between the elements passed
* with a given separator
*
* @ param string $separator The string to use as the separator
* @ param array $elements An array of items to concatenate
* @ return string The concat SQL
*/
2008-10-17 22:43:42 +00:00
public function sql_concat_join ( $separator = " ' ' " , $elements = array ()) {
2008-11-22 19:32:16 +00:00
$s = implode ( ', ' , $elements );
2008-10-17 22:43:42 +00:00
if ( $s === '' ) {
2008-10-25 18:40:35 +00:00
return " '' " ;
2008-10-17 22:43:42 +00:00
}
2008-11-22 19:32:16 +00:00
return " CONCAT_WS( $separator , $s ) " ;
2008-10-17 22:43:42 +00:00
}
2009-02-15 23:17:56 +00:00
/**
* Returns the SQL text to be used to calculate the length in characters of one expression .
* @ param string fieldname or expression to calculate its length in characters .
* @ return string the piece of SQL code to be used in the statement .
*/
public function sql_length ( $fieldname ) {
return ' CHAR_LENGTH(' . $fieldname . ')' ;
}
2008-10-17 22:43:42 +00:00
/**
2010-05-21 18:25:59 +00:00
* Does this driver support regex syntax when searching
2008-10-17 22:43:42 +00:00
*/
public function sql_regex_supported () {
return true ;
}
/**
* Return regex positive or negative match sql
* @ param bool $positivematch
2017-10-12 11:17:26 +08:00
* @ param bool $casesensitive
2008-10-17 22:43:42 +00:00
* @ return string or empty if not supported
*/
2017-10-12 11:17:26 +08:00
public function sql_regex ( $positivematch = true , $casesensitive = false ) {
$collation = '' ;
if ( $casesensitive ) {
if ( substr ( $this -> get_dbcollation (), - 4 ) !== '_bin' ) {
$collationinfo = explode ( '_' , $this -> get_dbcollation ());
$collation = 'COLLATE ' . $collationinfo [ 0 ] . '_bin ' ;
}
} else {
if ( $this -> get_dbcollation () == 'utf8_bin' ) {
$collation = 'COLLATE utf8_unicode_ci ' ;
} else if ( $this -> get_dbcollation () == 'utf8mb4_bin' ) {
$collation = 'COLLATE utf8mb4_unicode_ci ' ;
}
}
return $collation . ( $positivematch ? 'REGEXP' : 'NOT REGEXP' );
2008-10-17 22:43:42 +00:00
}
2008-10-27 20:58:37 +00:00
2012-01-22 18:01:16 +01:00
/**
* Returns the SQL to be used in order to an UNSIGNED INTEGER column to SIGNED .
*
* @ deprecated since 2.3
* @ param string $fieldname The name of the field to be cast
* @ return string The piece of SQL code to be used in your statement .
*/
2009-04-10 09:33:26 +00:00
public function sql_cast_2signed ( $fieldname ) {
return ' CAST(' . $fieldname . ' AS SIGNED) ' ;
}
2014-09-03 12:14:47 +08:00
/**
* Returns the SQL that allows to find intersection of two or more queries
*
* @ since Moodle 2.8
*
* @ param array $selects array of SQL select queries , each of them only returns fields with the names from $fields
* @ param string $fields comma - separated list of fields
* @ return string SQL query that will return only values that are present in each of selects
*/
public function sql_intersect ( $selects , $fields ) {
if ( count ( $selects ) <= 1 ) {
return parent :: sql_intersect ( $selects , $fields );
}
$fields = preg_replace ( '/\s/' , '' , $fields );
static $aliascnt = 0 ;
$falias = 'intsctal' . ( $aliascnt ++ );
$rv = " SELECT $falias . " .
preg_replace ( '/,/' , ',' . $falias . '.' , $fields ) .
" FROM ( $selects[0] ) $falias " ;
for ( $i = 1 ; $i < count ( $selects ); $i ++ ) {
$alias = 'intsctal' . ( $aliascnt ++ );
$rv .= " JOIN ( " . $selects [ $i ] . " ) $alias ON " .
join ( ' AND ' ,
array_map (
2017-10-02 09:09:35 +08:00
function ( $a ) use ( $alias , $falias ) {
return $falias . '.' . $a . ' = ' . $alias . '.' . $a ;
},
2014-09-03 12:14:47 +08:00
preg_split ( '/,/' , $fields ))
);
}
return $rv ;
}
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 () {
return true ;
}
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
2012-06-05 10:31:03 +02:00
* @ return void
2011-11-06 17:10:23 +01:00
*/
public function get_session_lock ( $rowid , $timeout ) {
parent :: get_session_lock ( $rowid , $timeout );
2009-01-17 12:12:48 +00:00
$fullname = $this -> dbname . '-' . $this -> prefix . '-session-' . $rowid ;
2011-11-06 17:10:23 +01:00
$sql = " SELECT GET_LOCK(' $fullname ', $timeout ) " ;
2009-01-17 12:12:48 +00:00
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
2009-01-16 23:02:24 +00:00
$this -> query_end ( $result );
if ( $result ) {
$arr = $result -> fetch_assoc ();
$result -> close ();
if ( reset ( $arr ) == 1 ) {
2009-01-17 14:31:29 +00:00
return ;
2009-01-17 12:12:48 +00:00
} else {
2011-11-06 17:10:23 +01:00
throw new dml_sessionwait_exception ();
2009-01-16 23:02:24 +00:00
}
}
}
2009-01-17 12:12:48 +00:00
public function release_session_lock ( $rowid ) {
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 );
2009-01-17 12:12:48 +00:00
$fullname = $this -> dbname . '-' . $this -> prefix . '-session-' . $rowid ;
$sql = " SELECT RELEASE_LOCK(' $fullname ') " ;
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
2009-01-16 23:02:24 +00:00
$this -> query_end ( $result );
if ( $result ) {
$result -> close ();
}
}
2008-10-27 20:58:37 +00:00
/**
2009-11-07 08:52:56 +00:00
* Are transactions supported ?
* It is not responsible to run productions servers
* on databases without transaction support ; - )
*
* MyISAM does not support support transactions .
2008-10-27 20:58:37 +00:00
*
2010-08-18 16:47:00 +00:00
* You can override this via the dbtransactions option .
*
2009-11-07 08:52:56 +00:00
* @ return bool
2008-10-27 20:58:37 +00:00
*/
2009-11-07 08:52:56 +00:00
protected function transactions_supported () {
if ( ! is_null ( $this -> transactions_supported )) {
return $this -> transactions_supported ;
}
2010-08-18 16:47:00 +00:00
// this is all just guessing, might be better to just specify it in config.php
if ( isset ( $this -> dboptions [ 'dbtransactions' ])) {
$this -> transactions_supported = $this -> dboptions [ 'dbtransactions' ];
return $this -> transactions_supported ;
}
2009-11-07 08:52:56 +00:00
$this -> transactions_supported = false ;
2010-08-18 16:47:00 +00:00
$engine = $this -> get_dbengine ();
2009-11-07 08:52:56 +00:00
2010-08-18 16:47:00 +00:00
// Only will accept transactions if using compatible storage engine (more engines can be added easily BDB, Falcon...)
if ( in_array ( $engine , array ( 'InnoDB' , 'INNOBASE' , 'BDB' , 'XtraDB' , 'Aria' , 'Falcon' ))) {
$this -> transactions_supported = true ;
2009-09-09 16:01:31 +00:00
}
2009-11-07 08:52:56 +00:00
return $this -> transactions_supported ;
}
/**
* Driver specific start of real database transaction ,
* this can not be used directly in code .
* @ return void
*/
protected function begin_transaction () {
if ( ! $this -> transactions_supported ()) {
return ;
2009-06-12 07:55:44 +00:00
}
2009-09-09 16:01:31 +00:00
2008-11-04 22:25:23 +00:00
$sql = " SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED " ;
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
2009-09-09 16:01:31 +00:00
$sql = " START TRANSACTION " ;
2008-11-04 22:25:23 +00:00
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
2008-10-27 20:58:37 +00:00
}
/**
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:58:37 +00:00
*/
2009-11-07 08:52:56 +00:00
protected function commit_transaction () {
if ( ! $this -> transactions_supported ()) {
return ;
2009-06-12 07:55:44 +00:00
}
2009-11-07 08:52:56 +00:00
2008-11-04 22:25:23 +00:00
$sql = " COMMIT " ;
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
2008-10-27 20:58:37 +00:00
}
/**
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:58:37 +00:00
*/
2009-11-07 08:52:56 +00:00
protected function rollback_transaction () {
if ( ! $this -> transactions_supported ()) {
return ;
2009-06-12 07:55:44 +00:00
}
2009-11-07 08:52:56 +00:00
2008-11-04 22:25:23 +00:00
$sql = " ROLLBACK " ;
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
2008-10-27 20:58:37 +00:00
return true ;
}
2017-07-18 10:24:21 +08:00
/**
* Converts a table to either 'Compressed' or 'Dynamic' row format .
*
* @ param string $tablename Name of the table to convert to the new row format .
*/
public function convert_table_row_format ( $tablename ) {
$currentrowformat = $this -> get_row_format ( $tablename );
if ( $currentrowformat == 'Compact' || $currentrowformat == 'Redundant' ) {
$rowformat = ( $this -> is_compressed_row_format_supported ( false )) ? " ROW_FORMAT=Compressed " : " ROW_FORMAT=Dynamic " ;
$prefix = $this -> get_prefix ();
$this -> change_database_structure ( " ALTER TABLE { $prefix } $tablename $rowformat " );
}
}
2017-07-25 15:56:16 +02:00
/**
* Does this mysql instance support fulltext indexes ?
*
* @ return bool
*/
public function is_fulltext_search_supported () {
$info = $this -> get_server_info ();
if ( version_compare ( $info [ 'version' ], '5.6.4' , '>=' )) {
return true ;
}
return false ;
}
2019-08-12 16:24:44 +08:00
/**
* Fixes any table names that clash with reserved words .
*
* @ param string $tablename The table name
* @ return string The fixed table name
*/
protected function fix_table_name ( $tablename ) {
$prefixedtablename = parent :: fix_table_name ( $tablename );
// This function quotes the table name if it matches one of the MySQL reserved
// words, e.g. groups.
return $this -> get_manager () -> generator -> getEncQuoted ( $prefixedtablename );
}
2008-10-17 22:43:42 +00:00
}