2010-06-23 22:40:49 +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 2 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/>.
/**
2010-07-25 12:57:24 +00:00
* Native sqlsrv class representing moodle database interface .
*
2012-06-05 10:31:03 +02:00
* @ package core_dml
2010-07-25 12:57:24 +00:00
* @ copyright 2009 onwards Eloy Lafuente ( stronk7 ) { @ link http :// stronk7 . com }
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v2 or later
*/
defined ( 'MOODLE_INTERNAL' ) || die ();
2010-06-23 22:40:49 +00:00
2012-06-05 10:31:03 +02:00
require_once ( __DIR__ . '/moodle_database.php' );
require_once ( __DIR__ . '/sqlsrv_native_moodle_recordset.php' );
require_once ( __DIR__ . '/sqlsrv_native_moodle_temptables.php' );
2010-06-23 22:40:49 +00:00
/**
2010-08-22 18:46:23 +00:00
* Native sqlsrv 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 2009 onwards Eloy Lafuente ( stronk7 ) { @ link http :// stronk7 . com }
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v2 or later
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
class sqlsrv_native_moodle_database extends moodle_database {
protected $sqlsrv = null ;
protected $last_error_reporting ; // To handle SQL*Server-Native driver default verbosity
protected $temptables ; // Control existing temptables (sqlsrv_moodle_temptables object)
2010-08-24 21:50:53 +00:00
protected $collation ; // current DB collation cache
2013-01-25 22:46:12 +01:00
/** @var array list of open recordsets */
protected $recordsets = array ();
2010-06-23 22:40:49 +00:00
/**
2010-08-22 18:46:23 +00:00
* Constructor - instantiates the database , specifying if it ' s external ( connect to other systems ) or no ( Moodle DB )
2010-06-23 22:40:49 +00:00
* note this has effect to decide if prefix checks must be performed or no
* @ param bool true means external database used
*/
public function __construct ( $external = false ) {
parent :: __construct ( $external );
}
2010-08-22 18:46:23 +00:00
/**
* Detects if all needed PHP stuff installed .
* Note : can be used before connect ()
* @ return mixed true if ok , string if something
*/
2010-06-23 22:40:49 +00:00
public function driver_installed () {
// use 'function_exists()' rather than 'extension_loaded()' because
2010-07-25 12:57:24 +00:00
// the name used by 'extension_loaded()' is case specific! The extension
// therefore *could be* mixed case and hence not found.
2010-06-23 22:40:49 +00:00
if ( ! function_exists ( 'sqlsrv_num_rows' )) {
2012-05-13 12:21:04 +02:00
if ( stripos ( PHP_OS , 'win' ) === 0 ) {
return get_string ( 'nativesqlsrvnodriver' , 'install' );
} else {
return get_string ( 'nativesqlsrvnonwindows' , 'install' );
}
2010-06-23 22:40:49 +00:00
}
return true ;
}
/**
2010-08-22 18:46:23 +00:00
* Returns database family type - describes SQL dialect
* Note : can be used before connect ()
* @ return string db family name ( mysql , postgres , mssql , sqlsrv , oracle , etc . )
*/
2010-06-23 22:40:49 +00:00
public function get_dbfamily () {
return 'mssql' ;
}
2010-08-22 18:46:23 +00:00
/**
* 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
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
protected function get_dbtype () {
return 'sqlsrv' ;
}
2010-08-22 18:46:23 +00:00
/**
* Returns general database library name
* Note : can be used before connect ()
* @ return string db type pdo , native
*/
2010-06-23 22:40:49 +00:00
protected function get_dblibrary () {
return 'native' ;
}
/**
2010-08-22 18:46:23 +00:00
* Returns localised database type name
* Note : can be used before connect ()
* @ return string
*/
2010-06-23 22:40:49 +00:00
public function get_name () {
return get_string ( 'nativesqlsrv' , 'install' );
}
/**
2010-08-22 18:46:23 +00:00
* Returns localised database configuration help .
* Note : can be used before connect ()
* @ return string
*/
2010-06-23 22:40:49 +00:00
public function get_configuration_help () {
return get_string ( 'nativesqlsrvhelp' , 'install' );
}
/**
2010-08-22 18:46:23 +00:00
* Returns localised database description
* Note : can be used before connect ()
* @ return string
*/
2010-06-23 22:40:49 +00:00
public function get_configuration_hints () {
$str = get_string ( 'databasesettingssub_sqlsrv' , 'install' );
$str .= " <p style='text-align:right'><a href= \" javascript:void(0) \" " ;
2011-07-05 14:34:25 +08:00
$str .= " onclick= \" return window.open('http://docs.moodle.org/en/Using_the_Microsoft_SQL_Server_Driver_for_PHP') \" " ;
2010-06-23 22:40:49 +00:00
$str .= " > " ;
$str .= '<img src="pix/docs.gif' . '" alt="Docs" class="iconhelp" />' ;
$str .= get_string ( 'moodledocslink' , 'install' ) . '</a></p>' ;
return $str ;
}
/**
2010-08-22 18:46:23 +00:00
* Connect to db
2012-01-19 10:15:11 +08:00
* Must be called before most other methods . ( you can call methods that return connection configuration parameters )
* @ 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 .
* @ param mixed $prefix string | bool The moodle db table name ' s prefix . false is used for external databases where prefix not used
2010-08-22 18:46:23 +00:00
* @ param array $dboptions driver specific options
* @ return bool true
* @ throws dml_connection_exception if error
*/
2010-06-23 22:40:49 +00:00
public function connect ( $dbhost , $dbuser , $dbpass , $dbname , $prefix , array $dboptions = null ) {
$driverstatus = $this -> driver_installed ();
if ( $driverstatus !== true ) {
throw new dml_exception ( 'dbdriverproblem' , $driverstatus );
}
/*
2010-08-22 18:46:23 +00:00
* Log all Errors .
*/
2010-06-23 22:40:49 +00:00
sqlsrv_configure ( " WarningsReturnAsErrors " , FALSE );
2012-04-11 22:14:18 +02:00
sqlsrv_configure ( " LogSubsystems " , SQLSRV_LOG_SYSTEM_OFF );
2010-06-23 22:40:49 +00:00
sqlsrv_configure ( " LogSeverity " , SQLSRV_LOG_SEVERITY_ERROR );
$this -> store_settings ( $dbhost , $dbuser , $dbpass , $dbname , $prefix , $dboptions );
$this -> sqlsrv = sqlsrv_connect ( $this -> dbhost , array
(
'UID' => $this -> dbuser ,
'PWD' => $this -> dbpass ,
'Database' => $this -> dbname ,
'CharacterSet' => 'UTF-8' ,
'MultipleActiveResultSets' => true ,
2010-08-22 11:34:56 +00:00
'ConnectionPooling' => ! empty ( $this -> dboptions [ 'dbpersist' ]),
2010-06-23 22:40:49 +00:00
'ReturnDatesAsStrings' => true ,
));
if ( $this -> sqlsrv === false ) {
$this -> sqlsrv = null ;
$dberr = $this -> get_last_error ();
throw new dml_connection_exception ( $dberr );
}
// Allow quoted identifiers
$sql = " SET QUOTED_IDENTIFIER ON " ;
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = sqlsrv_query ( $this -> sqlsrv , $sql );
$this -> query_end ( $result );
$this -> free_result ( $result );
// Force ANSI nulls so the NULL check was done by IS NULL and NOT IS NULL
// instead of equal(=) and distinct(<>) symbols
$sql = " SET ANSI_NULLS ON " ;
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = sqlsrv_query ( $this -> sqlsrv , $sql );
$this -> query_end ( $result );
$this -> free_result ( $result );
// Force ANSI warnings so arithmetic/string overflows will be
// returning error instead of transparently truncating data
$sql = " SET ANSI_WARNINGS ON " ;
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = sqlsrv_query ( $this -> sqlsrv , $sql );
$this -> query_end ( $result );
// Concatenating null with anything MUST return NULL
$sql = " SET CONCAT_NULL_YIELDS_NULL ON " ;
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = sqlsrv_query ( $this -> sqlsrv , $sql );
$this -> query_end ( $result );
$this -> free_result ( $result );
// Set transactions isolation level to READ_COMMITTED
// prevents dirty reads when using transactions +
// is the default isolation level of sqlsrv
$sql = " SET TRANSACTION ISOLATION LEVEL READ COMMITTED " ;
$this -> query_start ( $sql , NULL , SQL_QUERY_AUX );
$result = sqlsrv_query ( $this -> sqlsrv , $sql );
$this -> query_end ( $result );
$this -> free_result ( $result );
// Connection established and configured, going to instantiate the temptables controller
$this -> temptables = new sqlsrv_native_moodle_temptables ( $this );
return true ;
}
/**
2010-08-22 18:46:23 +00:00
* Close database connection and release all resources
* and memory ( especially circular memory references ) .
* Do NOT use connect () again , create a new instance if needed .
*/
2010-06-23 22:40:49 +00:00
public function dispose () {
parent :: dispose (); // Call parent dispose to write/close session and other common stuff before closing connection
if ( $this -> sqlsrv ) {
sqlsrv_close ( $this -> sqlsrv );
$this -> sqlsrv = null ;
}
}
/**
2010-08-22 18:46:23 +00:00
* Called before each db query .
* @ param string $sql
2012-01-19 10:15:11 +08:00
* @ param array $params array of parameters
2010-08-22 18:46:23 +00:00
* @ param int $type type of query
* @ param mixed $extrainfo driver specific extra information
* @ return void
*/
2010-06-23 22:40:49 +00:00
protected function query_start ( $sql , array $params = null , $type , $extrainfo = null ) {
parent :: query_start ( $sql , $params , $type , $extrainfo );
}
/**
2010-08-22 18:46:23 +00:00
* Called immediately after each db query .
* @ param mixed db specific result
* @ return void
*/
2010-06-23 22:40:49 +00:00
protected function query_end ( $result ) {
parent :: query_end ( $result );
}
/**
2010-08-22 18:46:23 +00:00
* Returns database server info array
2012-01-19 10:15:11 +08:00
* @ return array Array containing 'description' , 'version' and 'database' ( current db ) info
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
public function get_server_info () {
static $info ;
if ( ! $info ) {
$server_info = sqlsrv_server_info ( $this -> sqlsrv );
if ( $server_info ) {
2010-09-14 15:35:12 +00:00
$info [ 'description' ] = $server_info [ 'SQLServerName' ];
2010-06-23 22:40:49 +00:00
$info [ 'version' ] = $server_info [ 'SQLServerVersion' ];
$info [ 'database' ] = $server_info [ 'CurrentDatabase' ];
}
}
return $info ;
}
/**
2010-08-22 18:46:23 +00:00
* Override : Converts short table name { tablename } to real table name
* supporting temp tables ( #) if detected
*
* @ param string sql
* @ return string sql
*/
2010-06-23 22:40:49 +00:00
protected function fix_table_names ( $sql ) {
if ( preg_match_all ( '/\{([a-z][a-z0-9_]*)\}/i' , $sql , $matches )) {
foreach ( $matches [ 0 ] as $key => $match ) {
$name = $matches [ 1 ][ $key ];
if ( $this -> temptables -> is_temptable ( $name )) {
$sql = str_replace ( $match , $this -> temptables -> get_correct_name ( $name ), $sql );
} else {
$sql = str_replace ( $match , $this -> prefix . $name , $sql );
}
}
}
return $sql ;
}
/**
2010-08-22 18:46:23 +00:00
* Returns supported query parameter types
2010-08-22 19:06:06 +00:00
* @ return int bitmask
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
protected function allowed_param_types () {
return SQL_PARAMS_QM ; // sqlsrv 1.1 can bind
}
/**
2010-08-22 18:46:23 +00:00
* Returns last error reported by database engine .
2010-08-22 19:06:06 +00:00
* @ return string error message
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
public function get_last_error () {
$retErrors = sqlsrv_errors ( SQLSRV_ERR_ALL );
$errorMessage = 'No errors found' ;
if ( $retErrors != null ) {
$errorMessage = '' ;
foreach ( $retErrors as $arrError ) {
$errorMessage .= " SQLState: " . $arrError [ 'SQLSTATE' ] . " <br> \n " ;
$errorMessage .= " Error Code: " . $arrError [ 'code' ] . " <br> \n " ;
$errorMessage .= " Message: " . $arrError [ 'message' ] . " <br> \n " ;
}
}
return $errorMessage ;
}
/**
2010-08-22 18:46:23 +00:00
* Prepare the query binding and do the actual query .
*
* @ param string $sql The sql statement
2012-06-05 10:31:03 +02:00
* @ param array $params array of params for binding . If NULL , they are ignored .
* @ param int $sql_query_type - Type of operation
* @ param bool $free_result - Default true , transaction query will be freed .
* @ param bool $scrollable - Default false , to use for quickly seeking to target records
* @ return resource | bool result
2010-08-22 18:46:23 +00:00
*/
2011-02-18 01:08:58 +01:00
private function do_query ( $sql , $params , $sql_query_type , $free_result = true , $scrollable = false ) {
2010-06-23 22:40:49 +00:00
list ( $sql , $params , $type ) = $this -> fix_sql_params ( $sql , $params );
2012-06-05 10:31:03 +02:00
/*
* Bound variables * are * supported . Until I can get it to work , emulate the bindings
* The challenge / problem / bug is that although they work , doing a SELECT SCOPE_IDENTITY ()
* doesn ' t return a value ( no result set )
*
* -- somebody from MS
*/
2010-06-23 22:40:49 +00:00
$sql = $this -> emulate_bound_params ( $sql , $params );
$this -> query_start ( $sql , $params , $sql_query_type );
2011-02-18 01:08:58 +01:00
if ( ! $scrollable ) { // Only supporting next row
$result = sqlsrv_query ( $this -> sqlsrv , $sql );
2012-06-05 10:31:03 +02:00
} else { // Supporting absolute/relative rows
2011-02-18 01:08:58 +01:00
$result = sqlsrv_query ( $this -> sqlsrv , $sql , array (), array ( 'Scrollable' => SQLSRV_CURSOR_STATIC ));
}
2010-06-23 22:40:49 +00:00
if ( $result === false ) {
// TODO do something with error or just use if DEV or DEBUG?
$dberr = $this -> get_last_error ();
}
$this -> query_end ( $result );
if ( $free_result ) {
$this -> free_result ( $result );
return true ;
}
return $result ;
}
/**
2012-01-19 10:15:11 +08:00
* Return tables in database WITHOUT current prefix .
* @ param bool $usecache if true , returns list of cached tables .
2010-08-22 18:46:23 +00:00
* @ return array of table names in lowercase and without prefix
*/
2010-06-23 22:40:49 +00:00
public function get_tables ( $usecache = true ) {
if ( $usecache and count ( $this -> tables ) > 0 ) {
return $this -> tables ;
}
$this -> tables = array ();
2010-08-22 19:20:23 +00:00
$prefix = str_replace ( '_' , '\\_' , $this -> prefix );
2010-08-22 19:17:06 +00:00
$sql = " SELECT table_name
2012-04-11 22:14:18 +02:00
FROM INFORMATION_SCHEMA . TABLES
2010-08-23 08:06:02 +00:00
WHERE table_name LIKE '$prefix%' ESCAPE '\\' AND table_type = 'BASE TABLE' " ;
2010-06-23 22:40:49 +00:00
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = sqlsrv_query ( $this -> sqlsrv , $sql );
$this -> query_end ( $result );
if ( $result ) {
while ( $row = sqlsrv_fetch_array ( $result )) {
$tablename = reset ( $row );
2012-03-18 20:50:24 +01:00
if ( $this -> prefix !== '' ) {
if ( strpos ( $tablename , $this -> prefix ) !== 0 ) {
continue ;
}
$tablename = substr ( $tablename , strlen ( $this -> prefix ));
2010-06-23 22:40:49 +00:00
}
$this -> tables [ $tablename ] = $tablename ;
}
$this -> free_result ( $result );
}
// Add the currently available temptables
$this -> tables = array_merge ( $this -> tables , $this -> temptables -> get_temptables ());
return $this -> tables ;
}
/**
2012-01-19 10:15:11 +08:00
* Return table indexes - everything lowercased .
* @ param string $table The table we want to get indexes from .
2010-08-22 18:46:23 +00:00
* @ return array of arrays
*/
2010-06-23 22:40:49 +00:00
public function get_indexes ( $table ) {
$indexes = array ();
$tablename = $this -> prefix . $table ;
// Indexes aren't covered by information_schema metatables, so we need to
// go to sys ones. Skipping primary key indexes on purpose.
$sql = " SELECT i.name AS index_name, i.is_unique, ic.index_column_id, c.name AS column_name
2010-08-22 19:17:06 +00:00
FROM sys . indexes i
JOIN sys . index_columns ic ON i . object_id = ic . object_id AND i . index_id = ic . index_id
JOIN sys . columns c ON ic . object_id = c . object_id AND ic . column_id = c . column_id
JOIN sys . tables t ON i . object_id = t . object_id
WHERE t . name = '$tablename' AND i . is_primary_key = 0
ORDER BY i . name , i . index_id , ic . index_column_id " ;
2010-06-23 22:40:49 +00:00
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = sqlsrv_query ( $this -> sqlsrv , $sql );
$this -> query_end ( $result );
if ( $result ) {
$lastindex = '' ;
$unique = false ;
$columns = array ();
while ( $row = sqlsrv_fetch_array ( $result , SQLSRV_FETCH_ASSOC )) {
if ( $lastindex and $lastindex != $row [ 'index_name' ])
{ // Save lastindex to $indexes and reset info
$indexes [ $lastindex ] = array
(
'unique' => $unique ,
'columns' => $columns
);
$unique = false ;
$columns = array ();
}
$lastindex = $row [ 'index_name' ];
$unique = empty ( $row [ 'is_unique' ]) ? false : true ;
$columns [] = $row [ 'column_name' ];
}
if ( $lastindex ) { // Add the last one if exists
$indexes [ $lastindex ] = array
(
'unique' => $unique ,
'columns' => $columns
);
}
$this -> free_result ( $result );
}
return $indexes ;
}
/**
2010-08-22 18:46:23 +00:00
* Returns detailed information about columns in table . This information is cached internally .
* @ param string $table name
* @ param bool $usecache
* @ return array array of database_column_info objects indexed with column names
*/
2010-06-23 22:40:49 +00:00
public function get_columns ( $table , $usecache = true ) {
if ( $usecache and isset ( $this -> columns [ $table ])) {
return $this -> columns [ $table ];
}
$this -> columns [ $table ] = array ();
if ( ! $this -> temptables -> is_temptable ( $table )) { // normal table, get metadata from own schema
$sql = " SELECT column_name AS name,
2010-08-22 19:17:06 +00:00
data_type AS type ,
numeric_precision AS max_length ,
character_maximum_length AS char_max_length ,
numeric_scale AS scale ,
is_nullable AS is_nullable ,
columnproperty ( object_id ( quotename ( table_schema ) + '.' + quotename ( table_name )), column_name , 'IsIdentity' ) AS auto_increment ,
column_default AS default_value
2012-04-11 22:14:18 +02:00
FROM INFORMATION_SCHEMA . COLUMNS
2010-08-22 19:17:06 +00:00
WHERE table_name = '{".$table."}'
ORDER BY ordinal_position " ;
2010-06-23 22:40:49 +00:00
} else { // temp table, get metadata from tempdb schema
$sql = " SELECT column_name AS name,
2010-08-22 19:17:06 +00:00
data_type AS type ,
numeric_precision AS max_length ,
character_maximum_length AS char_max_length ,
numeric_scale AS scale ,
is_nullable AS is_nullable ,
columnproperty ( object_id ( quotename ( table_schema ) + '.' + quotename ( table_name )), column_name , 'IsIdentity' ) AS auto_increment ,
column_default AS default_value
2012-04-11 22:14:18 +02:00
FROM tempdb . INFORMATION_SCHEMA . COLUMNS " .
2010-06-23 22:40:49 +00:00
// check this statement
// JOIN tempdb..sysobjects ON name = table_name
// WHERE id = object_id('tempdb..{".$table."}')
2010-08-22 19:17:06 +00:00
" WHERE table_name LIKE ' { " . $table . " }__________%'
ORDER BY ordinal_position " ;
2010-06-23 22:40:49 +00:00
}
list ( $sql , $params , $type ) = $this -> fix_sql_params ( $sql , null );
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = sqlsrv_query ( $this -> sqlsrv , $sql );
$this -> query_end ( $result );
if ( ! $result ) {
return array ();
}
while ( $rawcolumn = sqlsrv_fetch_array ( $result , SQLSRV_FETCH_ASSOC )) {
$rawcolumn = ( object ) $rawcolumn ;
2010-09-21 07:57:42 +00:00
$info = new stdClass ();
2010-06-23 22:40:49 +00:00
$info -> name = $rawcolumn -> name ;
$info -> type = $rawcolumn -> type ;
$info -> meta_type = $this -> sqlsrvtype2moodletype ( $info -> type );
// Prepare auto_increment info
$info -> auto_increment = $rawcolumn -> auto_increment ? true : false ;
// Define type for auto_increment columns
$info -> meta_type = ( $info -> auto_increment && $info -> meta_type == 'I' ) ? 'R' : $info -> meta_type ;
// id columns being auto_incremnt are PK by definition
$info -> primary_key = ( $info -> name == 'id' && $info -> meta_type == 'R' && $info -> auto_increment );
// Put correct length for character and LOB types
$info -> max_length = $info -> meta_type == 'C' ? $rawcolumn -> char_max_length : $rawcolumn -> max_length ;
$info -> max_length = ( $info -> meta_type == 'X' || $info -> meta_type == 'B' ) ? - 1 : $info -> max_length ;
// Scale
$info -> scale = $rawcolumn -> scale ? $rawcolumn -> scale : false ;
// Prepare not_null info
$info -> not_null = $rawcolumn -> is_nullable == 'NO' ? true : false ;
// Process defaults
$info -> has_default = ! empty ( $rawcolumn -> default_value );
2010-09-14 15:40:43 +00:00
if ( $rawcolumn -> default_value === NULL ) {
$info -> default_value = NULL ;
} else {
$info -> default_value = preg_replace ( " /^[ \ (N]+[']?(.*?)[']?[ \ )]+ $ / " , '\\1' , $rawcolumn -> default_value );
}
2010-06-23 22:40:49 +00:00
// Process binary
$info -> binary = $info -> meta_type == 'B' ? true : false ;
$this -> columns [ $table ][ $info -> name ] = new database_column_info ( $info );
}
$this -> free_result ( $result );
return $this -> columns [ $table ];
}
/**
2010-08-22 18:46:23 +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
*/
2010-06-23 22:40:49 +00:00
protected function normalise_value ( $column , $value ) {
2012-03-17 18:42:30 +01:00
$this -> detect_objects ( $value );
2012-06-05 10:31:03 +02:00
if ( is_bool ( $value )) { // Always, convert boolean to int
2010-06-23 22:40:49 +00:00
$value = ( int ) $value ;
} // And continue processing because text columns with numeric info need special handling below
if ( $column -> meta_type == 'B' )
{ // BLOBs need to be properly "packed", but can be inserted directly if so.
if ( ! is_null ( $value )) { // If value not null, unpack it to unquoted hexadecimal byte-string format
$value = unpack ( 'H*hex' , $value ); // we leave it as array, so emulate_bound_params() can detect it
} // easily and "bind" the param ok.
2012-06-05 10:31:03 +02:00
} else if ( $column -> meta_type == 'X' ) { // sqlsrv doesn't cast from int to text, so if text column
2010-06-23 22:40:49 +00:00
if ( is_numeric ( $value )) { // and is numeric value then cast to string
2012-06-05 10:31:03 +02:00
$value = array ( 'numstr' => ( string ) $value ); // and put into array, so emulate_bound_params() will know how
2010-06-23 22:40:49 +00:00
} // to "bind" the param ok, avoiding reverse conversion to number
} 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
}
}
return $value ;
}
/**
2010-08-22 18:46:23 +00:00
* Selectively call sqlsrv_free_stmt (), avoiding some warnings without using the horrible @
*
* @ param sqlsrv_resource $resource resource to be freed if possible
2012-06-05 10:31:03 +02:00
* @ return bool
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
private function free_result ( $resource ) {
if ( ! is_bool ( $resource )) { // true/false resources cannot be freed
return sqlsrv_free_stmt ( $resource );
}
}
/**
2010-08-22 18:46:23 +00:00
* Provides mapping between sqlsrv native data types and moodle_database - database_column_info - ones )
*
* @ param string $sqlsrv_type native sqlsrv data type
* @ return string 1 - char database_column_info data type
*/
2010-06-23 22:40:49 +00:00
private function sqlsrvtype2moodletype ( $sqlsrv_type ) {
$type = null ;
switch ( strtoupper ( $sqlsrv_type )) {
case 'BIT' :
$type = 'L' ;
break ;
case 'INT' :
case 'SMALLINT' :
case 'INTEGER' :
case 'BIGINT' :
$type = 'I' ;
break ;
case 'DECIMAL' :
case 'REAL' :
case 'FLOAT' :
$type = 'N' ;
break ;
case 'VARCHAR' :
case 'NVARCHAR' :
$type = 'C' ;
break ;
case 'TEXT' :
case 'NTEXT' :
case 'VARCHAR(MAX)' :
case 'NVARCHAR(MAX)' :
$type = 'X' ;
break ;
case 'IMAGE' :
case 'VARBINARY(MAX)' :
$type = 'B' ;
break ;
case 'DATETIME' :
$type = 'D' ;
break ;
}
if ( ! $type ) {
throw new dml_exception ( 'invalidsqlsrvnativetype' , $sqlsrv_type );
}
return $type ;
}
/**
2010-08-22 18:46:23 +00:00
* Do NOT use in code , to be used by database_manager only !
* @ param string $sql query
* @ return bool true
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
public function change_database_structure ( $sql ) {
$this -> reset_caches ();
$this -> query_start ( $sql , null , SQL_QUERY_STRUCTURE );
$result = sqlsrv_query ( $this -> sqlsrv , $sql );
$this -> query_end ( $result );
return true ;
}
/**
2010-08-22 18:46:23 +00:00
* Prepare the array of params for native binding
*/
2010-06-23 22:40:49 +00:00
protected function build_native_bound_params ( array $params = null ) {
return null ;
}
/**
2010-08-22 18:46:23 +00:00
* Workaround for SQL * Server Native driver similar to MSSQL driver for
* consistent behavior .
*/
2010-06-23 22:40:49 +00:00
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
2011-09-09 11:11:47 +02:00
$parts = explode ( '?' , $sql );
$return = array_shift ( $parts );
2010-06-23 22:40:49 +00:00
foreach ( $params as $param ) {
if ( is_bool ( $param )) {
$return .= ( int ) $param ;
} else if ( is_array ( $param ) && isset ( $param [ 'hex' ])) { // detect hex binary, bind it specially
$return .= '0x' . $param [ 'hex' ];
} else if ( is_array ( $param ) && isset ( $param [ 'numstr' ])) { // detect numerical strings that *must not*
$return .= " N' { $param [ 'numstr' ] } ' " ; // be converted back to number params, but bound as strings
} else if ( is_null ( $param )) {
$return .= 'NULL' ;
2010-08-24 21:50:53 +00:00
} else if ( is_number ( $param )) { // we can not use is_numeric() because it eats leading zeros from strings like 0045646
2010-09-02 10:24:40 +00:00
$return .= " ' $param ' " ; // this is a hack for MDL-23997, we intentionally use string because it is compatible with both nvarchar and int types
2010-06-23 22:40:49 +00:00
} else if ( is_float ( $param )) {
$return .= $param ;
} else {
$param = str_replace ( " ' " , " '' " , $param );
$return .= " N' $param ' " ;
}
2011-09-09 11:11:47 +02:00
$return .= array_shift ( $parts );
2010-06-23 22:40:49 +00:00
}
return $return ;
}
/**
2010-08-22 18:46:23 +00:00
* 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 !
2010-08-22 18:46:23 +00:00
* @ param string $sql query
* @ param array $params query parameters
* @ return bool true
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
public function execute ( $sql , array $params = null ) {
if ( strpos ( $sql , ';' ) !== false ) {
throw new coding_exception ( 'moodle_database::execute() Multiple sql statements found or bound parameters not used properly in query!' );
}
$this -> do_query ( $sql , $params , SQL_QUERY_UPDATE );
return true ;
}
/**
2010-08-22 18:46:23 +00:00
* 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 .
2010-08-22 18:46:23 +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 .
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
public function get_recordset_sql ( $sql , array $params = null , $limitfrom = 0 , $limitnum = 0 ) {
$limitfrom = ( int ) $limitfrom ;
$limitnum = ( int ) $limitnum ;
$limitfrom = max ( 0 , $limitfrom );
$limitnum = max ( 0 , $limitnum );
if ( $limitfrom or $limitnum ) {
2011-02-18 01:08:58 +01:00
if ( $limitnum >= 1 ) { // Only apply TOP clause if we have any limitnum (limitfrom offset is handled later)
$fetch = $limitfrom + $limitnum ;
2011-03-21 18:49:08 +01:00
if ( PHP_INT_MAX - $limitnum < $limitfrom ) { // Check PHP_INT_MAX overflow
$fetch = PHP_INT_MAX ;
}
2011-02-18 01:08:58 +01:00
$sql = preg_replace ( '/^([\s(])*SELECT([\s]+(DISTINCT|ALL))?(?!\s*TOP\s*\()/i' ,
" \\ 1SELECT \\ 2 TOP $fetch " , $sql );
2010-06-23 22:40:49 +00:00
}
}
2011-02-18 01:08:58 +01:00
$result = $this -> do_query ( $sql , $params , SQL_QUERY_SELECT , false , ( bool ) $limitfrom );
2010-06-23 22:40:49 +00:00
2011-02-18 01:08:58 +01:00
if ( $limitfrom ) { // Skip $limitfrom records
sqlsrv_fetch ( $result , SQLSRV_SCROLL_ABSOLUTE , $limitfrom - 1 );
2010-12-16 16:25:03 +08:00
}
2011-02-18 01:08:58 +01:00
return $this -> create_recordset ( $result );
2010-06-23 22:40:49 +00:00
}
/**
2010-08-22 18:46:23 +00:00
* Create a record set and initialize with first row
*
* @ param mixed $result
* @ return sqlsrv_native_moodle_recordset
*/
2010-06-23 22:40:49 +00:00
protected function create_recordset ( $result ) {
2013-01-25 22:46:12 +01:00
$rs = new sqlsrv_native_moodle_recordset ( $result , $this );
$this -> recordsets [] = $rs ;
return $rs ;
}
/**
* Do not use outside of recordset class .
* @ internal
* @ param sqlsrv_native_moodle_recordset $rs
*/
public function recordset_closed ( sqlsrv_native_moodle_recordset $rs ) {
if ( $key = array_search ( $rs , $this -> recordsets , true )) {
unset ( $this -> recordsets [ $key ]);
}
2010-06-23 22:40:49 +00:00
}
/**
2010-08-22 18:46:23 +00:00
* Get a number of records as an array of objects using a SQL statement .
*
2012-02-16 10:29:45 +08:00
* Return value is like :
* @ see function get_records .
2010-08-22 18:46:23 +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 .
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
public function get_records_sql ( $sql , array $params = null , $limitfrom = 0 , $limitnum = 0 ) {
$rs = $this -> get_recordset_sql ( $sql , $params , $limitfrom , $limitnum );
$results = array ();
foreach ( $rs as $row ) {
$id = reset ( $row );
2010-09-17 20:25:02 +00:00
if ( isset ( $results [ $id ])) {
2010-06-23 22:40:49 +00:00
$colname = key ( $row );
debugging ( " Did you remember to make the first column something unique in your call to get_records? Duplicate value ' $id ' found in column ' $colname '. " , DEBUG_DEVELOPER );
}
$results [ $id ] = ( object ) $row ;
}
$rs -> close ();
return $results ;
}
/**
2010-08-22 18:46:23 +00:00
* 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 .
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
public function get_fieldset_sql ( $sql , array $params = null ) {
$rs = $this -> get_recordset_sql ( $sql , $params );
$results = array ();
foreach ( $rs as $row ) {
$results [] = reset ( $row );
}
$rs -> close ();
return $results ;
}
/**
2010-08-22 18:46:23 +00:00
* 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 .
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
public function insert_record_raw ( $table , $params , $returnid = true , $bulk = false , $customsequence = false ) {
if ( ! is_array ( $params )) {
$params = ( array ) $params ;
}
2012-04-10 20:24:05 +02:00
$isidentity = false ;
2010-06-23 22:40:49 +00:00
if ( $customsequence ) {
if ( ! isset ( $params [ 'id' ])) {
throw new coding_exception ( 'moodle_database::insert_record_raw() id field must be specified if custom sequences used.' );
}
2012-04-10 20:24:05 +02:00
2010-06-23 22:40:49 +00:00
$returnid = false ;
2012-04-10 20:24:05 +02:00
$columns = $this -> get_columns ( $table );
if ( isset ( $columns [ 'id' ]) and $columns [ 'id' ] -> auto_increment ) {
$isidentity = true ;
}
// Disable IDENTITY column before inserting record with id, only if the
// column is identity, from meta information.
if ( $isidentity ) {
$sql = 'SET IDENTITY_INSERT {' . $table . '} ON' ; // Yes, it' ON!!
$this -> do_query ( $sql , null , SQL_QUERY_AUX );
}
2010-09-14 15:32:22 +00:00
2010-06-23 22:40:49 +00:00
} else {
unset ( $params [ 'id' ]);
}
if ( empty ( $params )) {
throw new coding_exception ( 'moodle_database::insert_record_raw() no fields found.' );
}
$fields = implode ( ',' , array_keys ( $params ));
$qms = array_fill ( 0 , count ( $params ), '?' );
$qms = implode ( ',' , $qms );
$sql = " INSERT INTO { " . $table . " } ( $fields ) VALUES( $qms ) " ;
$query_id = $this -> do_query ( $sql , $params , SQL_QUERY_INSERT );
2010-09-14 15:32:22 +00:00
if ( $customsequence ) {
2012-04-10 20:24:05 +02:00
// Enable IDENTITY column after inserting record with id, only if the
// column is identity, from meta information.
if ( $isidentity ) {
$sql = 'SET IDENTITY_INSERT {' . $table . '} OFF' ; // Yes, it' OFF!!
$this -> do_query ( $sql , null , SQL_QUERY_AUX );
}
2010-09-14 15:32:22 +00:00
}
2010-06-23 22:40:49 +00:00
if ( $returnid ) {
$id = $this -> sqlsrv_fetch_id ();
return $id ;
} else {
return true ;
}
}
/**
2010-08-22 18:46:23 +00:00
* Get the ID of the current action
*
* @ return mixed ID
*/
2010-06-23 22:40:49 +00:00
private function sqlsrv_fetch_id () {
$query_id = sqlsrv_query ( $this -> sqlsrv , 'SELECT SCOPE_IDENTITY()' );
if ( $query_id === false ) {
$dberr = $this -> get_last_error ();
return false ;
}
$row = $this -> sqlsrv_fetchrow ( $query_id );
return ( int ) $row [ 0 ];
}
/**
2010-08-22 18:46:23 +00:00
* Fetch a single row into an numbered array
*
* @ param mixed $query_id
*/
2010-06-23 22:40:49 +00:00
private function sqlsrv_fetchrow ( $query_id ) {
$row = sqlsrv_fetch_array ( $query_id , SQLSRV_FETCH_NUMERIC );
if ( $row === false ) {
$dberr = $this -> get_last_error ();
return false ;
}
foreach ( $row as $key => $value ) {
$row [ $key ] = ( $value === ' ' || $value === NULL ) ? '' : $value ;
}
return $row ;
}
/**
2010-08-22 18:46:23 +00:00
* 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 .
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
public function insert_record ( $table , $dataobject , $returnid = true , $bulk = false ) {
2010-07-31 20:51:22 +00:00
$dataobject = ( array ) $dataobject ;
2010-06-23 22:40:49 +00:00
$columns = $this -> get_columns ( $table );
$cleaned = array ();
foreach ( $dataobject as $field => $value ) {
2010-07-31 20:51:22 +00:00
if ( $field === 'id' ) {
continue ;
}
2010-06-23 22:40:49 +00:00
if ( ! isset ( $columns [ $field ])) {
continue ;
}
$column = $columns [ $field ];
$cleaned [ $field ] = $this -> normalise_value ( $column , $value );
}
return $this -> insert_record_raw ( $table , $cleaned , $returnid , $bulk );
}
/**
2010-08-22 18:46:23 +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
* @ return bool true
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
public function import_record ( $table , $dataobject ) {
if ( ! is_object ( $dataobject )) {
$dataobject = ( object ) $dataobject ;
}
$columns = $this -> get_columns ( $table );
$cleaned = array ();
foreach ( $dataobject as $field => $value ) {
if ( ! isset ( $columns [ $field ])) {
continue ;
}
$column = $columns [ $field ];
$cleaned [ $field ] = $this -> normalise_value ( $column , $value );
}
2010-09-14 15:32:22 +00:00
$this -> insert_record_raw ( $table , $cleaned , false , false , true );
2010-06-23 22:40:49 +00:00
2010-09-14 15:32:22 +00:00
return true ;
2010-06-23 22:40:49 +00:00
}
/**
2010-08-22 18:46:23 +00:00
* Update record in database , as fast as possible , no safety checks , lobs not supported .
* @ param string $table name
* @ param mixed $params data record as object or array
* @ param bool true means repeated updates expected
* @ return bool true
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
public function update_record_raw ( $table , $params , $bulk = false ) {
2010-07-31 20:51:22 +00:00
$params = ( array ) $params ;
2010-06-23 22:40:49 +00:00
if ( ! isset ( $params [ 'id' ])) {
throw new coding_exception ( 'moodle_database::update_record_raw() id field must be specified.' );
}
$id = $params [ 'id' ];
unset ( $params [ 'id' ]);
if ( empty ( $params )) {
throw new coding_exception ( 'moodle_database::update_record_raw() no fields found.' );
}
$sets = array ();
foreach ( $params as $field => $value ) {
$sets [] = " $field = ? " ;
}
$params [] = $id ; // last ? in WHERE condition
$sets = implode ( ',' , $sets );
$sql = " UPDATE { " . $table . " } SET $sets WHERE id = ? " ;
$this -> do_query ( $sql , $params , SQL_QUERY_UPDATE );
return true ;
}
/**
2010-08-22 18:46:23 +00:00
* 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
* @ return bool true
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
public function update_record ( $table , $dataobject , $bulk = false ) {
2010-07-31 20:51:22 +00:00
$dataobject = ( array ) $dataobject ;
2010-06-23 22:40:49 +00:00
$columns = $this -> get_columns ( $table );
$cleaned = array ();
foreach ( $dataobject as $field => $value ) {
if ( ! isset ( $columns [ $field ])) {
continue ;
}
$column = $columns [ $field ];
$cleaned [ $field ] = $this -> normalise_value ( $column , $value );
}
return $this -> update_record_raw ( $table , $cleaned , $bulk );
}
/**
2010-08-22 18:46:23 +00:00
* Set a single field in every table record which match a particular WHERE clause .
*
* @ param string $table The database table to be checked against .
* @ param string $newfield the field to set .
* @ param string $newvalue the value to set the field to .
* @ param string $select A fragment of SQL to be used in a where clause in the SQL call .
* @ param array $params array of sql parameters
* @ return bool true
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +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 ();
}
2011-03-21 17:19:36 +01:00
// convert params to ? types
list ( $select , $params , $type ) = $this -> fix_sql_params ( $select , $params );
2012-06-05 10:31:03 +02:00
// Get column metadata
2010-06-23 22:40:49 +00:00
$columns = $this -> get_columns ( $table );
$column = $columns [ $newfield ];
$newvalue = $this -> normalise_value ( $column , $newvalue );
if ( is_null ( $newvalue )) {
$newfield = " $newfield = NULL " ;
} else {
$newfield = " $newfield = ? " ;
array_unshift ( $params , $newvalue );
}
$sql = " UPDATE { " . $table . " } SET $newfield $select " ;
$this -> do_query ( $sql , $params , SQL_QUERY_UPDATE );
return true ;
}
/**
2010-08-22 18:46:23 +00:00
* 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
* @ return bool true
2012-01-19 10:15:11 +08:00
* @ throws dml_exception A DML specific exception is thrown for any errors .
2010-08-22 18:46:23 +00:00
*/
2010-06-23 22:40:49 +00:00
public function delete_records_select ( $table , $select , array $params = null ) {
if ( $select ) {
$select = " WHERE $select " ;
}
$sql = " DELETE FROM { " . $table . " } $select " ;
2010-08-22 19:17:06 +00:00
// we use SQL_QUERY_UPDATE because we do not know what is in general SQL, delete constant would not be accurate
$this -> do_query ( $sql , $params , SQL_QUERY_UPDATE );
2010-06-23 22:40:49 +00:00
return true ;
}
public function sql_cast_char2int ( $fieldname , $text = false ) {
2010-11-18 01:27:49 +00:00
if ( ! $text ) {
return ' CAST(' . $fieldname . ' AS INT) ' ;
} else {
return ' CAST(' . $this -> sql_compare_text ( $fieldname ) . ' AS INT) ' ;
}
}
public function sql_cast_char2real ( $fieldname , $text = false ) {
if ( ! $text ) {
return ' CAST(' . $fieldname . ' AS REAL) ' ;
} else {
return ' CAST(' . $this -> sql_compare_text ( $fieldname ) . ' AS REAL) ' ;
}
2010-06-23 22:40:49 +00:00
}
public function sql_ceil ( $fieldname ) {
return ' CEILING(' . $fieldname . ')' ;
}
2010-08-24 21:50:53 +00:00
protected function get_collation () {
if ( isset ( $this -> collation )) {
return $this -> collation ;
}
if ( ! empty ( $this -> dboptions [ 'dbcollation' ])) {
// perf speedup
$this -> collation = $this -> dboptions [ 'dbcollation' ];
return $this -> collation ;
}
// make some default
$this -> collation = 'Latin1_General_CI_AI' ;
$sql = " SELECT CAST(DATABASEPROPERTYEX(' $this->dbname ', 'Collation') AS varchar(255)) AS SQLCollation " ;
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = sqlsrv_query ( $this -> sqlsrv , $sql );
$this -> query_end ( $result );
if ( $result ) {
if ( $rawcolumn = sqlsrv_fetch_array ( $result , SQLSRV_FETCH_ASSOC )) {
$this -> collation = reset ( $rawcolumn );
}
$this -> free_result ( $result );
}
return $this -> collation ;
}
/**
* 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
}
$collation = $this -> get_collation ();
2010-09-04 14:39:01 +00:00
$LIKE = $notlike ? 'NOT LIKE' : 'LIKE' ;
2010-08-24 21:50:53 +00:00
if ( $casesensitive ) {
$collation = str_replace ( '_CI' , '_CS' , $collation );
} else {
$collation = str_replace ( '_CS' , '_CI' , $collation );
}
if ( $accentsensitive ) {
$collation = str_replace ( '_AI' , '_AS' , $collation );
} else {
$collation = str_replace ( '_AS' , '_AI' , $collation );
}
2010-09-04 14:39:01 +00:00
return " $fieldname COLLATE $collation $LIKE $param ESCAPE ' $escapechar ' " ;
2010-08-24 21:50:53 +00:00
}
2010-06-23 22:40:49 +00:00
public function sql_concat () {
$arr = func_get_args ();
foreach ( $arr as $key => $ele ) {
2013-04-08 13:17:56 +02:00
$arr [ $key ] = ' CAST(' . $ele . ' AS NVARCHAR(255)) ' ;
2010-06-23 22:40:49 +00:00
}
$s = implode ( ' + ' , $arr );
if ( $s === '' ) {
return " '' " ;
}
return " $s " ;
}
public function sql_concat_join ( $separator = " ' ' " , $elements = array ()) {
for ( $n = count ( $elements ) - 1 ; $n > 0 ; $n -- ) {
array_splice ( $elements , $n , 0 , $separator );
}
$s = implode ( ' + ' , $elements );
if ( $s === '' ) {
return " '' " ;
}
return " $s " ;
}
public function sql_isempty ( $tablename , $fieldname , $nullablefield , $textfield ) {
if ( $textfield ) {
return ' (' . $this -> sql_compare_text ( $fieldname ) . " = '') " ;
} else {
return " ( $fieldname = '') " ;
}
}
/**
2010-08-22 18:46:23 +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 .
*/
2010-06-23 22:40:49 +00:00
public function sql_length ( $fieldname ) {
return ' LEN(' . $fieldname . ')' ;
}
public function sql_order_by_text ( $fieldname , $numchars = 32 ) {
return ' CONVERT(varchar, ' . $fieldname . ', ' . $numchars . ')' ;
}
/**
2010-08-22 18:46:23 +00:00
* Returns the SQL for returning searching one string for the location of another .
*/
2010-06-23 22:40:49 +00:00
public function sql_position ( $needle , $haystack ) {
return " CHARINDEX(( $needle ), ( $haystack )) " ;
}
/**
2010-08-22 18:46:23 +00:00
* Returns the proper substr () SQL text used to extract substrings from DB
* NOTE : this was originally returning only function name
*
* @ param string $expr some string field , no aggregates
* @ param mixed $start integer or expression evaluating to int
* @ param mixed $length optional integer or expression evaluating to int
* @ return string sql fragment
*/
2010-06-23 22:40:49 +00:00
public function sql_substr ( $expr , $start , $length = false ) {
if ( count ( func_get_args ()) < 2 ) {
2010-08-22 18:46:23 +00:00
throw new coding_exception ( 'moodle_database::sql_substr() requires at least two parameters' ,
'Originally this function was only returning name of SQL substring function, it now requires all parameters.' );
2010-06-23 22:40:49 +00:00
}
if ( $length === false ) {
return " SUBSTRING( $expr , $start , (LEN( $expr ) - $start + 1)) " ;
} else {
return " SUBSTRING( $expr , $start , $length ) " ;
}
}
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 ) {
2010-06-23 22:40:49 +00:00
if ( ! $this -> session_lock_supported ()) {
return ;
}
2011-11-06 17:10:23 +01:00
parent :: get_session_lock ( $rowid , $timeout );
$timeoutmilli = $timeout * 1000 ;
2010-06-23 22:40:49 +00:00
$fullname = $this -> dbname . '-' . $this -> prefix . '-session-' . $rowid ;
2011-11-16 01:27:56 +01:00
// While this may work using proper {call sp_...} calls + binding +
// executing + consuming recordsets, the solution used for the mssql
// driver is working perfectly, so 100% mimic-ing that code.
// $sql = "sp_getapplock '$fullname', 'Exclusive', 'Session', $timeoutmilli";
$sql = " BEGIN
DECLARE @ result INT
EXECUTE @ result = sp_getapplock @ Resource = '$fullname' ,
@ LockMode = 'Exclusive' ,
@ LockOwner = 'Session' ,
@ LockTimeout = '$timeoutmilli'
SELECT @ result
END " ;
2010-06-23 22:40:49 +00:00
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = sqlsrv_query ( $this -> sqlsrv , $sql );
$this -> query_end ( $result );
2011-11-06 17:10:23 +01:00
if ( $result ) {
$row = sqlsrv_fetch_array ( $result );
if ( $row [ 0 ] < 0 ) {
throw new dml_sessionwait_exception ();
}
}
2010-06-23 22:40:49 +00:00
$this -> free_result ( $result );
}
public function release_session_lock ( $rowid ) {
if ( ! $this -> session_lock_supported ()) {
return ;
}
2012-10-25 17:12:53 +08:00
if ( ! $this -> used_for_db_sessions ) {
return ;
}
2010-06-23 22:40:49 +00:00
parent :: release_session_lock ( $rowid );
$fullname = $this -> dbname . '-' . $this -> prefix . '-session-' . $rowid ;
$sql = " sp_releaseapplock ' $fullname ', 'Session' " ;
$this -> query_start ( $sql , null , SQL_QUERY_AUX );
$result = sqlsrv_query ( $this -> sqlsrv , $sql );
$this -> query_end ( $result );
$this -> free_result ( $result );
}
/**
2010-08-22 18:46:23 +00:00
* Driver specific start of real database transaction ,
* this can not be used directly in code .
* @ return void
*/
2010-06-23 22:40:49 +00:00
protected function begin_transaction () {
2013-01-25 22:46:12 +01:00
// Recordsets do not work well with transactions in SQL Server,
// let's prefetch the recordsets to memory to work around these problems.
foreach ( $this -> recordsets as $rs ) {
$rs -> transaction_starts ();
}
2010-06-23 22:40:49 +00:00
$this -> query_start ( 'native sqlsrv_begin_transaction' , NULL , SQL_QUERY_AUX );
$result = sqlsrv_begin_transaction ( $this -> sqlsrv );
$this -> query_end ( $result );
}
/**
2010-08-22 18:46:23 +00:00
* Driver specific commit of real database transaction ,
* this can not be used directly in code .
* @ return void
*/
2010-06-23 22:40:49 +00:00
protected function commit_transaction () {
$this -> query_start ( 'native sqlsrv_commit' , NULL , SQL_QUERY_AUX );
$result = sqlsrv_commit ( $this -> sqlsrv );
$this -> query_end ( $result );
}
/**
2010-08-22 18:46:23 +00:00
* Driver specific abort of real database transaction ,
* this can not be used directly in code .
* @ return void
*/
2010-06-23 22:40:49 +00:00
protected function rollback_transaction () {
$this -> query_start ( 'native sqlsrv_rollback' , NULL , SQL_QUERY_AUX );
$result = sqlsrv_rollback ( $this -> sqlsrv );
$this -> query_end ( $result );
}
2010-08-22 18:46:23 +00:00
}