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 {
2008-10-18 22:35:42 +00:00
protected $mysqli = 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 );
}
2012-07-11 16:39:20 +02:00
if ( isset ( $dboptions [ 'dbcollation' ]) and strpos ( $dboptions [ 'dbcollation' ], 'utf8_' ) === 0 ) {
$collation = $dboptions [ 'dbcollation' ];
} else {
$collation = 'utf8_unicode_ci' ;
}
$result = $conn -> query ( " CREATE DATABASE $dbname DEFAULT CHARACTER SET utf8 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 ()) {
$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 ;
}
// get the default database engine
$sql = " SELECT @@storage_engine " ;
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
if ( $rec = $result -> fetch_assoc ()) {
$engine = $rec [ '@@storage_engine' ];
}
$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 ()) {
$collation = $rec [ 'collation_name' ];
}
$result -> close ();
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 ()) {
if ( strpos ( $rec [ '@@collation_database' ], 'utf8_' ) === 0 ) {
$collation = $rec [ '@@collation_database' ];
}
}
$result -> close ();
}
if ( ! $collation ) {
// We want only utf8 compatible collations.
$collation = null ;
$sql = " SHOW COLLATION WHERE Collation LIKE 'utf8 \ _%' AND Charset = 'utf8' " ;
$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 ;
}
/**
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 " ;
}
2008-11-22 01:16:52 +00:00
ob_start ();
2011-01-14 10:20:04 +01:00
$this -> mysqli = new mysqli ( $dbhost , $dbuser , $dbpass , $dbname , $dbport , $dbsocket );
2008-11-22 01:16:52 +00:00
$dberr = ob_get_contents ();
ob_end_clean ();
$errorno = @ $this -> mysqli -> connect_errno ;
if ( $errorno !== 0 ) {
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
2008-11-04 22:25:23 +00:00
$this -> query_start ( " --set_charset() " , null , SQL_QUERY_AUX );
2008-10-17 22:43:42 +00:00
$this -> mysqli -> set_charset ( 'utf8' );
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 );
}
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 ();
2008-11-04 22:25:23 +00:00
$sql = " SHOW INDEXES FROM { $this -> prefix } $table " ;
$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
* @ return array array of database_column_info objects indexed with column names
*/
public function get_columns ( $table , $usecache = true ) {
2012-08-14 11:38:13 +12:00
if ( $usecache ) {
2013-10-23 11:25:58 +02:00
$properties = array ( 'dbfamily' => $this -> get_dbfamily (), 'settings' => $this -> get_settings_hash ());
$cache = cache :: make ( 'core' , 'databasemeta' , $properties );
if ( $data = $cache -> get ( $table )) {
2012-08-14 11:38:13 +12:00
return $data ;
}
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
WHERE table_name = '" . $this->prefix.$table . "'
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 ()) {
$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 ();
$sql = " SHOW COLUMNS FROM { $this -> prefix } $table " ;
$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 );
$rawcolumn -> column_key = $rawcolumn -> key ; unset ( $rawcolumn -> default );
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 ) {
2013-10-23 11:25:58 +02:00
$cache -> 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
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 );
$info -> default_value = $rawcolumn -> column_default ;
$info -> has_default = ! is_null ( $rawcolumn -> column_default );
$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
2012-12-22 18:42:55 +01:00
$sql = " SHOW COLLATION WHERE Collation =' $collation ' AND Charset = 'utf8' " ;
$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 !
* @ param string $sql query
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 change_database_structure ( $sql ) {
2009-01-12 18:10:50 +00:00
$this -> reset_caches ();
2008-11-04 22:25:23 +00:00
$this -> query_start ( $sql , null , SQL_QUERY_STRUCTURE );
$result = $this -> mysqli -> query ( $sql );
$this -> query_end ( $result );
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 );
$sql = " INSERT INTO { $this -> prefix } $table ( $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 );
}
/**
* 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 );
$sql = " UPDATE { $this -> prefix } $table 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
}
$sql = " UPDATE { $this -> prefix } $table SET $newfield $select " ;
$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 " ;
}
$sql = " DELETE FROM { $this -> prefix } $table $select " ;
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 ) {
return ' CAST(' . $fieldname . ' AS DECIMAL) ' ;
2008-10-17 22:43:42 +00:00
}
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
}
$escapechar = $this -> mysqli -> real_escape_string ( $escapechar ); // prevents problems with C-style escapes of enclosing '\'
2010-09-04 14:39:01 +00:00
$LIKE = $notlike ? 'NOT LIKE' : 'LIKE' ;
2010-08-24 21:50:53 +00:00
if ( $casesensitive ) {
2010-09-04 14:39:01 +00:00
return " $fieldname $LIKE $param COLLATE utf8_bin ESCAPE ' $escapechar ' " ;
2010-08-24 21:50:53 +00:00
} else {
if ( $accentsensitive ) {
2010-09-04 14:39:01 +00:00
return " LOWER( $fieldname ) $LIKE LOWER( $param ) COLLATE utf8_bin ESCAPE ' $escapechar ' " ;
2010-08-24 21:50:53 +00:00
} else {
2010-09-04 14:39:01 +00:00
return " $fieldname $LIKE $param 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
* @ return string or empty if not supported
*/
public function sql_regex ( $positivematch = true ) {
return $positivematch ? 'REGEXP' : 'NOT REGEXP' ;
}
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) ' ;
}
2013-11-28 12:59:16 +08:00
/**
* Does this driver support tool_replace ?
*
* @ since 2.6 . 1
* @ 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 ;
}
2008-10-17 22:43:42 +00:00
}