From 48c3e9959958cad7befc9215f7f2a87638a1fdc6 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Thu, 21 Sep 2017 14:09:44 +0800 Subject: [PATCH 1/7] MDL-59581 database: remove mssql specific code from lib/dml --- lib/dml/mssql_native_moodle_database.php | 1468 ------------------- lib/dml/mssql_native_moodle_recordset.php | 94 -- lib/dml/mssql_native_moodle_temptables.php | 51 - lib/dml/sqlsrv_native_moodle_temptables.php | 24 +- 4 files changed, 22 insertions(+), 1615 deletions(-) delete mode 100644 lib/dml/mssql_native_moodle_database.php delete mode 100644 lib/dml/mssql_native_moodle_recordset.php delete mode 100644 lib/dml/mssql_native_moodle_temptables.php diff --git a/lib/dml/mssql_native_moodle_database.php b/lib/dml/mssql_native_moodle_database.php deleted file mode 100644 index 22994724bba..00000000000 --- a/lib/dml/mssql_native_moodle_database.php +++ /dev/null @@ -1,1468 +0,0 @@ -. - -/** - * Native mssql class representing moodle database interface. - * - * @package core_dml - * @copyright 2009 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -require_once(__DIR__.'/moodle_database.php'); -require_once(__DIR__.'/mssql_native_moodle_recordset.php'); -require_once(__DIR__.'/mssql_native_moodle_temptables.php'); - -/** - * Native mssql class representing moodle database interface. - * - * @package core_dml - * @copyright 2009 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class mssql_native_moodle_database extends moodle_database { - - protected $mssql = null; - protected $last_error_reporting; // To handle mssql driver default verbosity - protected $collation; // current DB collation cache - /** - * Does the used db version support ANSI way of limiting (2012 and higher) - * @var bool - */ - protected $supportsoffsetfetch; - - /** - * 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 (!function_exists('mssql_connect')) { - return get_string('mssqlextensionisnotpresentinphp', '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 'mssql'; - } - - /** - * Returns more specific database driver type - * Note: can be used before connect() - * @return string db type mysqli, pgsql, oci, mssql, sqlsrv - */ - protected function get_dbtype() { - return 'mssql'; - } - - /** - * Returns general database library name - * Note: can be used before connect() - * @return string db type pdo, native - */ - protected function get_dblibrary() { - return 'native'; - } - - /** - * Returns localised database type name - * Note: can be used before connect() - * @return string - */ - public function get_name() { - return get_string('nativemssql', 'install'); - } - - /** - * Returns localised database configuration help. - * Note: can be used before connect() - * @return string - */ - public function get_configuration_help() { - return get_string('nativemssqlhelp', 'install'); - } - - /** - * 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() { - // Verify the database is running with READ_COMMITTED_SNAPSHOT enabled. - // (that's required to get snapshots/row versioning on READ_COMMITED mode). - $correctrcsmode = false; - $sql = "SELECT is_read_committed_snapshot_on - FROM sys.databases - WHERE name = '{$this->dbname}'"; - $this->query_start($sql, null, SQL_QUERY_AUX); - $result = mssql_query($sql, $this->mssql); - $this->query_end($result); - if ($result) { - if ($row = mssql_fetch_assoc($result)) { - $correctrcsmode = (bool)reset($row); - } - } - $this->free_result($result); - - if (!$correctrcsmode) { - return get_string('mssqlrcsmodemissing', 'error'); - } - - // Arrived here, all right. - return null; - } - - /** - * Connect to db - * Must be called before other methods. - * @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 means moodle db prefix, false used for external databases where prefix not used - * @param array $dboptions driver specific options - * @return bool true - * @throws dml_connection_exception if error - */ - public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) { - if ($prefix == '' and !$this->external) { - //Enforce prefixes for everybody but mysql - throw new dml_exception('prefixcannotbeempty', $this->get_dbfamily()); - } - - $driverstatus = $this->driver_installed(); - - if ($driverstatus !== true) { - throw new dml_exception('dbdriverproblem', $driverstatus); - } - - $this->store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions); - - $dbhost = $this->dbhost; - // Zero shouldn't be used as a port number so doing a check with empty() should be fine. - if (!empty($dboptions['dbport'])) { - if (stristr(PHP_OS, 'win') && !stristr(PHP_OS, 'darwin')) { - $dbhost .= ','.$dboptions['dbport']; - } else { - $dbhost .= ':'.$dboptions['dbport']; - } - } - ob_start(); - if (!empty($this->dboptions['dbpersist'])) { // persistent connection - $this->mssql = mssql_pconnect($dbhost, $this->dbuser, $this->dbpass, true); - } else { - $this->mssql = mssql_connect($dbhost, $this->dbuser, $this->dbpass, true); - } - $dberr = ob_get_contents(); - ob_end_clean(); - - if ($this->mssql === false) { - $this->mssql = null; - throw new dml_connection_exception($dberr); - } - - // Disable logging until we are fully setup. - $this->query_log_prevent(); - - // already connected, select database and set some env. variables - $this->query_start("--mssql_select_db", null, SQL_QUERY_AUX); - $result = mssql_select_db($this->dbname, $this->mssql); - $this->query_end($result); - - // No need to set charset. It's UTF8, with transparent conversions - // back and forth performed both by FreeTDS or ODBTP - - // Allow quoted identifiers - $sql = "SET QUOTED_IDENTIFIER ON"; - $this->query_start($sql, null, SQL_QUERY_AUX); - $result = mssql_query($sql, $this->mssql); - $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 = mssql_query($sql, $this->mssql); - $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 = mssql_query($sql, $this->mssql); - $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 = mssql_query($sql, $this->mssql); - $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 MSSQL - // Requires database to run with READ_COMMITTED_SNAPSHOT ON - $sql = "SET TRANSACTION ISOLATION LEVEL READ COMMITTED"; - $this->query_start($sql, NULL, SQL_QUERY_AUX); - $result = mssql_query($sql, $this->mssql); - $this->query_end($result); - - $this->free_result($result); - - $serverinfo = $this->get_server_info(); - // Fetch/offset is supported staring from SQL Server 2012. - $this->supportsoffsetfetch = $serverinfo['version'] > '11'; - - // We can enable logging now. - $this->query_log_allow(); - - // Connection stabilised and configured, going to instantiate the temptables controller - $this->temptables = new mssql_native_moodle_temptables($this); - - 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() { - parent::dispose(); // Call parent dispose to write/close session and other common stuff before closing connection - if ($this->mssql) { - mssql_close($this->mssql); - $this->mssql = null; - } - } - - /** - * Called before each db query. - * @param string $sql - * @param array array of parameters - * @param int $type type of query - * @param mixed $extrainfo driver specific extra information - * @return void - */ - protected function query_start($sql, array $params=null, $type, $extrainfo=null) { - parent::query_start($sql, $params, $type, $extrainfo); - // mssql driver tends to send debug to output, we do not need that ;-) - $this->last_error_reporting = error_reporting(0); - } - - /** - * Called immediately after each db query. - * @param mixed db specific result - * @return void - */ - protected function query_end($result) { - // reset original debug level - error_reporting($this->last_error_reporting); - parent::query_end($result); - } - - /** - * Returns database server info array - * @return array Array containing 'description' and 'version' info - */ - public function get_server_info() { - static $info; - if (!$info) { - $info = array(); - $sql = 'sp_server_info 2'; - $this->query_start($sql, null, SQL_QUERY_AUX); - $result = mssql_query($sql, $this->mssql); - $this->query_end($result); - $row = mssql_fetch_row($result); - $info['description'] = $row[2]; - $this->free_result($result); - - $sql = 'sp_server_info 500'; - $this->query_start($sql, null, SQL_QUERY_AUX); - $result = mssql_query($sql, $this->mssql); - $this->query_end($result); - $row = mssql_fetch_row($result); - $info['version'] = $row[2]; - $this->free_result($result); - } - return $info; - } - - /** - * Converts short table name {tablename} to real table name - * supporting temp tables (#) if detected - * - * @param string sql - * @return string sql - */ - protected function fix_table_names($sql) { - if (preg_match_all('/\{([a-z][a-z0-9_]*)\}/', $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; - } - - /** - * Returns supported query parameter types - * @return int bitmask of accepted SQL_PARAMS_* - */ - protected function allowed_param_types() { - return SQL_PARAMS_QM; // Not really, but emulated, see emulate_bound_params() - } - - /** - * Returns last error reported by database engine. - * @return string error message - */ - public function get_last_error() { - return mssql_get_last_message(); - } - - /** - * Return tables in database WITHOUT current prefix - * @param bool $usecache if true, returns list of cached tables. - * @return array of table names in lowercase and without prefix - */ - public function get_tables($usecache=true) { - if ($usecache and $this->tables !== null) { - return $this->tables; - } - $this->tables = array(); - $sql = "SELECT table_name - FROM INFORMATION_SCHEMA.TABLES - WHERE table_name LIKE '$this->prefix%' - AND table_type = 'BASE TABLE'"; - $this->query_start($sql, null, SQL_QUERY_AUX); - $result = mssql_query($sql, $this->mssql); - $this->query_end($result); - - if ($result) { - while ($row = mssql_fetch_row($result)) { - $tablename = reset($row); - if ($this->prefix !== false && $this->prefix !== '') { - if (strpos($tablename, $this->prefix) !== 0) { - continue; - } - $tablename = substr($tablename, strlen($this->prefix)); - } - $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; - } - - /** - * 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 - */ - 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 - 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"; - - $this->query_start($sql, null, SQL_QUERY_AUX); - $result = mssql_query($sql, $this->mssql); - $this->query_end($result); - - if ($result) { - $lastindex = ''; - $unique = false; - $columns = array(); - while ($row = mssql_fetch_assoc($result)) { - 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; - } - - /** - * Returns datailed 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 - */ - public function get_columns($table, $usecache=true) { - - if ($usecache) { - if ($this->temptables->is_temptable($table)) { - if ($data = $this->get_temp_tables_cache()->get($table)) { - return $data; - } - } else { - if ($data = $this->get_metacache()->get($table)) { - return $data; - } - } - } - - $structure = array(); - - if (!$this->temptables->is_temptable($table)) { // normal table, get metadata from own schema - $sql = "SELECT column_name AS name, - 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 - FROM INFORMATION_SCHEMA.COLUMNS - WHERE table_name = '{" . $table . "}' - ORDER BY ordinal_position"; - } else { // temp table, get metadata from tempdb schema - $sql = "SELECT column_name AS name, - 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 - FROM tempdb.INFORMATION_SCHEMA.COLUMNS - JOIN tempdb.sys.objects ON name = table_name - WHERE object_id = object_id('tempdb..{" . $table . "}') - ORDER BY ordinal_position"; - } - - list($sql, $params, $type) = $this->fix_sql_params($sql, null); - - $this->query_start($sql, null, SQL_QUERY_AUX); - $result = mssql_query($sql, $this->mssql); - $this->query_end($result); - - if (!$result) { - return array(); - } - - while ($rawcolumn = mssql_fetch_assoc($result)) { - - $rawcolumn = (object)$rawcolumn; - - $info = new stdClass(); - $info->name = $rawcolumn->name; - $info->type = $rawcolumn->type; - $info->meta_type = $this->mssqltype2moodletype($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); - - if ($info->meta_type === 'C' and $rawcolumn->char_max_length == -1) { - // This is NVARCHAR(MAX), not a normal NVARCHAR. - $info->max_length = -1; - $info->meta_type = 'X'; - } else { - // 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; - - // Prepare not_null info - $info->not_null = $rawcolumn->is_nullable == 'NO' ? true : false; - - // Process defaults - $info->has_default = !empty($rawcolumn->default_value); - if ($rawcolumn->default_value === NULL) { - $info->default_value = NULL; - } else { - $info->default_value = preg_replace("/^[\(N]+[']?(.*?)[']?[\)]+$/", '\\1', $rawcolumn->default_value); - } - - // Process binary - $info->binary = $info->meta_type == 'B' ? true : false; - - $structure[$info->name] = new database_column_info($info); - } - $this->free_result($result); - - if ($usecache) { - if ($this->temptables->is_temptable($table)) { - $this->get_temp_tables_cache()->set($table, $structure); - } else { - $this->get_metacache()->set($table, $structure); - } - } - - return $structure; - } - - /** - * Normalise values based on varying RDBMS's 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 - */ - protected function normalise_value($column, $value) { - $this->detect_objects($value); - - if (is_bool($value)) { // Always, convert boolean to int - $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. - - } else if ($column->meta_type == 'X') { // MSSQL doesn't cast from int to text, so if text column - if (is_numeric($value)) { // and is numeric value then cast to string - $value = array('numstr' => (string)$value); // and put into array, so emulate_bound_params() will know how - } // 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; - } - - /** - * Selectively call mssql_free_result(), avoiding some warnings without using the horrible @ - * - * @param mssql_resource $resource resource to be freed if possible - */ - private function free_result($resource) { - if (!is_bool($resource)) { // true/false resources cannot be freed - mssql_free_result($resource); - } - } - - /** - * Provides mapping between mssql native data types and moodle_database - database_column_info - ones) - * - * @param string $mssql_type native mssql data type - * @return string 1-char database_column_info data type - */ - private function mssqltype2moodletype($mssql_type) { - $type = null; - switch (strtoupper($mssql_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': - case 'VARBINARY(MAX)': - $type = 'B'; - break; - case 'DATETIME': - $type = 'D'; - break; - } - if (!$type) { - throw new dml_exception('invalidmssqlnativetype', $mssql_type); - } - return $type; - } - - /** - * Do NOT use in code, to be used by database_manager only! - * @param string|array $sql query - * @param array|null $tablenames an array of xmldb table names affected by this request. - * @return bool true - * @throws ddl_change_structure_exception A DDL specific exception is thrown for any errors. - */ - public function change_database_structure($sql, $tablenames = null) { - $this->get_manager(); // Includes DDL exceptions classes ;-) - $sqls = (array)$sql; - - try { - foreach ($sqls as $sql) { - $this->query_start($sql, null, SQL_QUERY_STRUCTURE); - $result = mssql_query($sql, $this->mssql); - $this->query_end($result); - } - } catch (ddl_change_structure_exception $e) { - $this->reset_caches($tablenames); - throw $e; - } - - $this->reset_caches($tablenames); - return true; - } - - /** - * Very ugly hack which emulates bound parameters in queries - * because the mssql driver doesn't support placeholders natively at all - */ - protected function emulate_bound_params($sql, array $params=null) { - if (empty($params)) { - return $sql; - } - // ok, we have verified sql statement with ? and correct number of params - $parts = array_reverse(explode('?', $sql)); - $return = array_pop($parts); - 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'; - - } else if (is_number($param)) { // we can not use is_numeric() because it eats leading zeros from strings like 0045646 - $return .= "'".$param."'"; //fix for MDL-24863 to prevent auto-cast to int. - - } else if (is_float($param)) { - $return .= $param; - - } else { - $param = str_replace("'", "''", $param); - $param = str_replace("\0", "", $param); - $return .= "N'$param'"; - } - - $return .= array_pop($parts); - } - return $return; - } - - /** - * Execute general sql query. Should be used only when no other method suitable. - * Do NOT use this to make changes in db structure, use database_manager methods instead! - * @param string $sql query - * @param array $params query parameters - * @return bool true - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function execute($sql, array $params=null) { - - list($sql, $params, $type) = $this->fix_sql_params($sql, $params); - $rawsql = $this->emulate_bound_params($sql, $params); - - if (strpos($sql, ';') !== false) { - throw new coding_exception('moodle_database::execute() Multiple sql statements found or bound parameters not used properly in query!'); - } - - $this->query_start($sql, $params, SQL_QUERY_UPDATE); - $result = mssql_query($rawsql, $this->mssql); - $this->query_end($result); - $this->free_result($result); - - 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. - * - * The return type is like: - * @see function get_recordset. - * - * @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). - * @return moodle_recordset instance - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) { - - list($limitfrom, $limitnum) = $this->normalise_limit_from_num($limitfrom, $limitnum); - - if ($limitfrom or $limitnum) { - if (!$this->supportsoffsetfetch) { - if ($limitnum >= 1) { // Only apply TOP clause if we have any limitnum (limitfrom offset is handled later). - $fetch = $limitfrom + $limitnum; - if (PHP_INT_MAX - $limitnum < $limitfrom) { // Check PHP_INT_MAX overflow. - $fetch = PHP_INT_MAX; - } - $sql = preg_replace('/^([\s(])*SELECT([\s]+(DISTINCT|ALL))?(?!\s*TOP\s*\()/i', - "\\1SELECT\\2 TOP $fetch", $sql); - } - } else { - $sql = (substr($sql, -1) === ';') ? substr($sql, 0, -1) : $sql; - // We need order by to use FETCH/OFFSET. - // Ordering by first column shouldn't break anything if there was no order in the first place. - if (!strpos(strtoupper($sql), "ORDER BY")) { - $sql .= " ORDER BY 1"; - } - - $sql .= " OFFSET ".$limitfrom." ROWS "; - - if ($limitnum > 0) { - $sql .= " FETCH NEXT ".$limitnum." ROWS ONLY"; - } - } - } - - list($sql, $params, $type) = $this->fix_sql_params($sql, $params); - $rawsql = $this->emulate_bound_params($sql, $params); - - $this->query_start($sql, $params, SQL_QUERY_SELECT); - $result = mssql_query($rawsql, $this->mssql); - $this->query_end($result); - - if ($limitfrom && !$this->supportsoffsetfetch) { // Skip $limitfrom records. - if (!@mssql_data_seek($result, $limitfrom)) { - // Nothing, most probably seek past the end. - mssql_free_result($result); - $result = null; - } - } - - return $this->create_recordset($result); - } - - protected function create_recordset($result) { - return new mssql_native_moodle_recordset($result); - } - - /** - * Get a number of records as an array of objects using a SQL statement. - * - * Return value is like: - * @see function get_records. - * - * @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). - * @return array of objects, or empty array if no records were found - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - 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); - if (isset($results[$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); - } - $results[$id] = $row; - } - $rs->close(); - - return $results; - } - - /** - * 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 - * @return array of values - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - 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; - } - - /** - * 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 - * @return bool|int true or new id - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) { - if (!is_array($params)) { - $params = (array)$params; - } - - $returning = ""; - $isidentity = false; - - if ($customsequence) { - if (!isset($params['id'])) { - throw new coding_exception('moodle_database::insert_record_raw() id field must be specified if custom sequences used.'); - } - $returnid = false; - - $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!! - list($sql, $xparams, $xtype) = $this->fix_sql_params($sql, null); - $this->query_start($sql, null, SQL_QUERY_AUX); - $result = mssql_query($sql, $this->mssql); - $this->query_end($result); - $this->free_result($result); - } - - } else { - unset($params['id']); - if ($returnid) { - $returning = "OUTPUT inserted.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) $returning VALUES ($qms)"; - - list($sql, $params, $type) = $this->fix_sql_params($sql, $params); - $rawsql = $this->emulate_bound_params($sql, $params); - - $this->query_start($sql, $params, SQL_QUERY_INSERT); - $result = mssql_query($rawsql, $this->mssql); - // Expected results are: - // - true: insert ok and there isn't returned information. - // - false: insert failed and there isn't returned information. - // - resource: insert executed, need to look for returned (output) - // values to know if the insert was ok or no. Posible values - // are false = failed, integer = insert ok, id returned. - $end = false; - if (is_bool($result)) { - $end = $result; - } else if (is_resource($result)) { - $end = mssql_result($result, 0, 0); // Fetch 1st column from 1st row. - } - $this->query_end($end); // End the query with the calculated $end. - - if ($returning !== "") { - $params['id'] = $end; - } - $this->free_result($result); - - if ($customsequence) { - // 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!! - list($sql, $xparams, $xtype) = $this->fix_sql_params($sql, null); - $this->query_start($sql, null, SQL_QUERY_AUX); - $result = mssql_query($sql, $this->mssql); - $this->query_end($result); - $this->free_result($result); - } - } - - if (!$returnid) { - return true; - } - - return (int)$params['id']; - } - - /** - * Insert a record into a table and return the "id" field if required. - * - * Some conversions and safety checks are carried out. Lobs are supported. - * If the return ID isn't required, then this just reports success as true/false. - * $data is an object containing needed data - * @param string $table The database table to be inserted into - * @param object $data A data object with values for one or more fields in the record - * @param bool $returnid Should the id of the newly created record entry be returned? If this option is not requested then true/false is returned. - * @return bool|int true or new id - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function insert_record($table, $dataobject, $returnid=true, $bulk=false) { - $dataobject = (array)$dataobject; - - $columns = $this->get_columns($table); - if (empty($columns)) { - throw new dml_exception('ddltablenotexist', $table); - } - $cleaned = array(); - - foreach ($dataobject as $field => $value) { - if ($field === 'id') { - continue; - } - if (!isset($columns[$field])) { - continue; - } - $column = $columns[$field]; - $cleaned[$field] = $this->normalise_value($column, $value); - } - - 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 - * @return bool true - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function import_record($table, $dataobject) { - $dataobject = (array)$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); - } - - $this->insert_record_raw($table, $cleaned, false, false, true); - - return 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 - * @return bool true - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function update_record_raw($table, $params, $bulk=false) { - $params = (array)$params; - - 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 = ?"; - - list($sql, $params, $type) = $this->fix_sql_params($sql, $params); - $rawsql = $this->emulate_bound_params($sql, $params); - - $this->query_start($sql, $params, SQL_QUERY_UPDATE); - $result = mssql_query($rawsql, $this->mssql); - $this->query_end($result); - - $this->free_result($result); - 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 - * @return bool true - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function update_record($table, $dataobject, $bulk=false) { - $dataobject = (array)$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); - } - - 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 - * @return bool true - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function set_field_select($table, $newfield, $newvalue, $select, array $params=null) { - - if ($select) { - $select = "WHERE $select"; - } - if (is_null($params)) { - $params = array(); - } - - // convert params to ? types - list($select, $params, $type) = $this->fix_sql_params($select, $params); - - // Get column metadata - $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"; - - list($sql, $params, $type) = $this->fix_sql_params($sql, $params); - $rawsql = $this->emulate_bound_params($sql, $params); - - $this->query_start($sql, $params, SQL_QUERY_UPDATE); - $result = mssql_query($rawsql, $this->mssql); - $this->query_end($result); - - $this->free_result($result); - - 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 - * @return bool true - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function delete_records_select($table, $select, array $params=null) { - - if ($select) { - $select = "WHERE $select"; - } - - $sql = "DELETE FROM {" . $table . "} $select"; - - list($sql, $params, $type) = $this->fix_sql_params($sql, $params); - $rawsql = $this->emulate_bound_params($sql, $params); - - $this->query_start($sql, $params, SQL_QUERY_UPDATE); - $result = mssql_query($rawsql, $this->mssql); - $this->query_end($result); - - $this->free_result($result); - - return true; - } - - public function sql_cast_char2int($fieldname, $text=false) { - 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) '; - } - } - - public function sql_ceil($fieldname) { - return ' CEILING(' . $fieldname . ')'; - } - - - 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 = mssql_query($sql, $this->mssql); - $this->query_end($result); - - if ($result) { - if ($rawcolumn = mssql_fetch_assoc($result)) { - $this->collation = reset($rawcolumn); - } - $this->free_result($result); - } - - return $this->collation; - } - - public function sql_equal($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notequal = false) { - $equalop = $notequal ? '<>' : '='; - $collation = $this->get_collation(); - - 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); - } - - return "$fieldname COLLATE $collation $equalop $param"; - } - - /** - * 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) - * @param bool $notlike true means "NOT LIKE" - * @param string $escapechar escape char for '%' and '_' - * @return string SQL code fragment - */ - public function sql_like($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notlike = false, $escapechar = '\\') { - if (strpos($param, '%') !== false) { - debugging('Potential SQL injection detected, sql_like() expects bound parameters (? or :named)'); - } - - $collation = $this->get_collation(); - - 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); - } - - $LIKE = $notlike ? 'NOT LIKE' : 'LIKE'; - - return "$fieldname COLLATE $collation $LIKE $param ESCAPE '$escapechar'"; - } - - public function sql_concat() { - $arr = func_get_args(); - foreach ($arr as $key => $ele) { - $arr[$key] = ' CAST(' . $ele . ' AS NVARCHAR(255)) '; - } - $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); - } - return call_user_func_array(array($this, 'sql_concat'), $elements); - } - - public function sql_isempty($tablename, $fieldname, $nullablefield, $textfield) { - if ($textfield) { - return ' (' . $this->sql_compare_text($fieldname) . " = '') "; - } else { - return " ($fieldname = '') "; - } - } - - /** - * 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 ' LEN(' . $fieldname . ')'; - } - - public function sql_order_by_text($fieldname, $numchars=32) { - return " CONVERT(varchar({$numchars}), {$fieldname})"; - } - - /** - * Returns the SQL for returning searching one string for the location of another. - */ - public function sql_position($needle, $haystack) { - return "CHARINDEX(($needle), ($haystack))"; - } - - /** - * 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 - */ - public function sql_substr($expr, $start, $length=false) { - if (count(func_get_args()) < 2) { - throw new coding_exception('moodle_database::sql_substr() requires at least two parameters', 'Originaly this function wa -s only returning name of SQL substring function, it now requires all parameters.'); - } - if ($length === false) { - return "SUBSTRING($expr, " . $this->sql_cast_char2int($start) . ", 2^31-1)"; - } else { - return "SUBSTRING($expr, " . $this->sql_cast_char2int($start) . ", " . $this->sql_cast_char2int($length) . ")"; - } - } - - /** - * Does this driver support tool_replace? - * - * @since Moodle 2.6.1 - * @return bool - */ - public function replace_all_text_supported() { - return true; - } - - public function session_lock_supported() { - return true; - } - - /** - * Obtain session lock - * @param int $rowid id of the row with session record - * @param int $timeout max allowed time to wait for the lock in seconds - * @return bool success - */ - public function get_session_lock($rowid, $timeout) { - if (!$this->session_lock_supported()) { - return; - } - parent::get_session_lock($rowid, $timeout); - - $timeoutmilli = $timeout * 1000; - - $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid; - // There is one bug in PHP/freetds (both reproducible with mssql_query() - // and its mssql_init()/mssql_bind()/mssql_execute() alternative) for - // stored procedures, causing scalar results of the execution - // to be cast to boolean (true/fals). Here there is one - // workaround that forces the return of one recordset resource. - // $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"; - $this->query_start($sql, null, SQL_QUERY_AUX); - $result = mssql_query($sql, $this->mssql); - $this->query_end($result); - - if ($result) { - $row = mssql_fetch_row($result); - if ($row[0] < 0) { - throw new dml_sessionwait_exception(); - } - } - - $this->free_result($result); - } - - public function release_session_lock($rowid) { - if (!$this->session_lock_supported()) { - return; - } - if (!$this->used_for_db_sessions) { - return; - } - - 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 = mssql_query($sql, $this->mssql); - $this->query_end($result); - - $this->free_result($result); - } - - /** - * Driver specific start of real database transaction, - * this can not be used directly in code. - * @return void - */ - protected function begin_transaction() { - // requires database to run with READ_COMMITTED_SNAPSHOT ON - $sql = "BEGIN TRANSACTION"; // Will be using READ COMMITTED isolation - $this->query_start($sql, NULL, SQL_QUERY_AUX); - $result = mssql_query($sql, $this->mssql); - $this->query_end($result); - - $this->free_result($result); - } - - /** - * Driver specific commit of real database transaction, - * this can not be used directly in code. - * @return void - */ - protected function commit_transaction() { - $sql = "COMMIT TRANSACTION"; - $this->query_start($sql, NULL, SQL_QUERY_AUX); - $result = mssql_query($sql, $this->mssql); - $this->query_end($result); - - $this->free_result($result); - } - - /** - * Driver specific abort of real database transaction, - * this can not be used directly in code. - * @return void - */ - protected function rollback_transaction() { - $sql = "ROLLBACK TRANSACTION"; - $this->query_start($sql, NULL, SQL_QUERY_AUX); - $result = mssql_query($sql, $this->mssql); - $this->query_end($result); - - $this->free_result($result); - } -} diff --git a/lib/dml/mssql_native_moodle_recordset.php b/lib/dml/mssql_native_moodle_recordset.php deleted file mode 100644 index 39cf1ebe0ed..00000000000 --- a/lib/dml/mssql_native_moodle_recordset.php +++ /dev/null @@ -1,94 +0,0 @@ -. - -/** - * MSSQL specific recordset. - * - * @package core_dml - * @copyright 2009 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -require_once(__DIR__.'/moodle_recordset.php'); - -class mssql_native_moodle_recordset extends moodle_recordset { - - protected $rsrc; - protected $current; - - public function __construct($rsrc) { - $this->rsrc = $rsrc; - $this->current = $this->fetch_next(); - } - - public function __destruct() { - $this->close(); - } - - private function fetch_next() { - if (!$this->rsrc) { - return false; - } - if (!$row = mssql_fetch_assoc($this->rsrc)) { - mssql_free_result($this->rsrc); - $this->rsrc = null; - return false; - } - - $row = array_change_key_case($row, CASE_LOWER); - // Moodle expects everything from DB as strings. - foreach ($row as $k=>$v) { - if (is_null($v)) { - continue; - } - if (!is_string($v)) { - $row[$k] = (string)$v; - } - } - return $row; - } - - public function current() { - return (object)$this->current; - } - - public function key() { - // return first column value as key - if (!$this->current) { - return false; - } - $key = reset($this->current); - return $key; - } - - public function next() { - $this->current = $this->fetch_next(); - } - - public function valid() { - return !empty($this->current); - } - - public function close() { - if ($this->rsrc) { - mssql_free_result($this->rsrc); - $this->rsrc = null; - } - $this->current = null; - } -} diff --git a/lib/dml/mssql_native_moodle_temptables.php b/lib/dml/mssql_native_moodle_temptables.php deleted file mode 100644 index df04cca1d03..00000000000 --- a/lib/dml/mssql_native_moodle_temptables.php +++ /dev/null @@ -1,51 +0,0 @@ -. - -/** - * MSSQL specific temptables store. Needed because temporary tables - * are named differently than normal tables. Also used to be able to retrieve - * temp table names included in the get_tables() method of the DB. - * - * @package core_dml - * @copyright 2009 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -require_once(__DIR__.'/moodle_temptables.php'); - -class mssql_native_moodle_temptables extends moodle_temptables { - - /** - * Add one temptable to the store. - * - * Overriden because MSSQL requires to add # for local (session) temporary - * tables before the prefix. - * - * Given one moodle temptable name (without prefix), add it to the store, with the - * key being the original moodle name and the value being the real db temptable name - * already prefixed - * - * Override and use this *only* if the database requires modification in the table name. - * - * @param string $tablename name without prefix of the table created as temptable - */ - public function add_temptable($tablename) { - // TODO: throw exception if exists: if ($this->is_temptable... - $this->temptables[$tablename] = '#' . $this->prefix . $tablename; - } -} diff --git a/lib/dml/sqlsrv_native_moodle_temptables.php b/lib/dml/sqlsrv_native_moodle_temptables.php index 2d9cc62b9c7..ae733b827e4 100644 --- a/lib/dml/sqlsrv_native_moodle_temptables.php +++ b/lib/dml/sqlsrv_native_moodle_temptables.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die(); -require_once(__DIR__.'/mssql_native_moodle_temptables.php'); +require_once(__DIR__.'/moodle_temptables.php'); /** * This class is not specific to the SQL Server Native Driver but rather @@ -36,4 +36,24 @@ require_once(__DIR__.'/mssql_native_moodle_temptables.php'); * @copyright 2009 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v2 or later */ -class sqlsrv_native_moodle_temptables extends mssql_native_moodle_temptables {} +class sqlsrv_native_moodle_temptables extends moodle_temptables { + + /** + * Add one temptable to the store. + * + * Overriden because SQLSRV requires to add # for local (session) temporary + * tables before the prefix. + * + * Given one moodle temptable name (without prefix), add it to the store, with the + * key being the original moodle name and the value being the real db temptable name + * already prefixed + * + * Override and use this *only* if the database requires modification in the table name. + * + * @param string $tablename name without prefix of the table created as temptable + */ + public function add_temptable($tablename) { + // TODO: throw exception if exists: if ($this->is_temptable... + $this->temptables[$tablename] = '#' . $this->prefix . $tablename; + } +} From 811808b3d21bd8bf5b206b37d9f17536867232ba Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Thu, 21 Sep 2017 14:48:52 +0800 Subject: [PATCH 2/7] MDL-59581 database: removal of mssql bits from admin/ --- admin/cli/install.php | 1 - admin/tool/log/store/database/classes/helper.php | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/admin/cli/install.php b/admin/cli/install.php index 240a81921ac..1ff43fefafe 100644 --- a/admin/cli/install.php +++ b/admin/cli/install.php @@ -219,7 +219,6 @@ $databases = array('mysqli' => moodle_database::get_driver_instance('mysqli', 'n 'pgsql' => moodle_database::get_driver_instance('pgsql', 'native'), 'oci' => moodle_database::get_driver_instance('oci', 'native'), 'sqlsrv' => moodle_database::get_driver_instance('sqlsrv', 'native'), // MS SQL*Server PHP driver - 'mssql' => moodle_database::get_driver_instance('mssql', 'native'), // FreeTDS driver ); foreach ($databases as $type=>$database) { if ($database->driver_installed() !== true) { diff --git a/admin/tool/log/store/database/classes/helper.php b/admin/tool/log/store/database/classes/helper.php index 1acb99cdaf4..bad2c1b0090 100644 --- a/admin/tool/log/store/database/classes/helper.php +++ b/admin/tool/log/store/database/classes/helper.php @@ -44,8 +44,7 @@ class helper { 'native/mariadb' => \moodle_database::get_driver_instance('mariadb', 'native')->get_name(), 'native/pgsql' => \moodle_database::get_driver_instance('pgsql', 'native')->get_name(), 'native/oci' => \moodle_database::get_driver_instance('oci', 'native')->get_name(), - 'native/sqlsrv' => \moodle_database::get_driver_instance('sqlsrv', 'native')->get_name(), - 'native/mssql' => \moodle_database::get_driver_instance('mssql', 'native')->get_name() + 'native/sqlsrv' => \moodle_database::get_driver_instance('sqlsrv', 'native')->get_name() ); } From 4c00922f2eddfe1ace60e2f28998cb6a3a26cb8d Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Thu, 21 Sep 2017 15:25:48 +0800 Subject: [PATCH 3/7] MDL-59581 database: remove mssql_native_moodle_database test from auth --- auth/db/tests/db_test.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/auth/db/tests/db_test.php b/auth/db/tests/db_test.php index 2900719fdea..fd6a62cb748 100644 --- a/auth/db/tests/db_test.php +++ b/auth/db/tests/db_test.php @@ -97,11 +97,7 @@ class auth_db_testcase extends advanced_testcase { break; case 'mssql': - if (get_class($DB) == 'mssql_native_moodle_database') { - set_config('type', 'mssql_n', 'auth_db'); - } else { - set_config('type', 'mssqlnative', 'auth_db'); - } + set_config('type', 'mssqlnative', 'auth_db'); set_config('sybasequoting', '1', 'auth_db'); break; From 2610190674b22b66b782c5451246a9e8b5522c34 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Thu, 21 Sep 2017 15:18:21 +0800 Subject: [PATCH 4/7] MDL-59581 database: remove mssql reference from top level files --- config-dist.php | 2 +- install.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/config-dist.php b/config-dist.php index 5308052c915..6903e74855d 100644 --- a/config-dist.php +++ b/config-dist.php @@ -38,7 +38,7 @@ $CFG = new stdClass(); // will be stored. This database must already have been created // // and a username/password created to access it. // -$CFG->dbtype = 'pgsql'; // 'pgsql', 'mariadb', 'mysqli', 'mssql', 'sqlsrv' or 'oci' +$CFG->dbtype = 'pgsql'; // 'pgsql', 'mariadb', 'mysqli', 'sqlsrv' or 'oci' $CFG->dblibrary = 'native'; // 'native' only at the moment $CFG->dbhost = 'localhost'; // eg 'localhost' or 'db.isp.com' or IP $CFG->dbname = 'moodle'; // database name, eg moodle diff --git a/install.php b/install.php index 8868c6afa94..e7f8de7c7cf 100644 --- a/install.php +++ b/install.php @@ -483,7 +483,6 @@ if ($config->stage == INSTALL_DATABASETYPE) { 'pgsql' => moodle_database::get_driver_instance('pgsql', 'native'), 'oci' => moodle_database::get_driver_instance('oci', 'native'), 'sqlsrv' => moodle_database::get_driver_instance('sqlsrv', 'native'), // MS SQL*Server PHP driver - 'mssql' => moodle_database::get_driver_instance('mssql', 'native'), // FreeTDS driver ); echo '
'; From 9c533e01dda8c2fc1be81055358d9608a1788cba Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Thu, 21 Sep 2017 15:29:40 +0800 Subject: [PATCH 5/7] MDL-59581 database: remove mssql_native_moodle_database test from enrol --- enrol/database/tests/sync_test.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/enrol/database/tests/sync_test.php b/enrol/database/tests/sync_test.php index d7eacacce50..0165ba15444 100644 --- a/enrol/database/tests/sync_test.php +++ b/enrol/database/tests/sync_test.php @@ -96,11 +96,7 @@ class enrol_database_testcase extends advanced_testcase { break; case 'mssql': - if (get_class($DB) == 'mssql_native_moodle_database') { - set_config('dbtype', 'mssql_n', 'enrol_database'); - } else { - set_config('dbtype', 'mssqlnative', 'enrol_database'); - } + set_config('dbtype', 'mssqlnative', 'enrol_database'); set_config('dbsybasequoting', '1', 'enrol_database'); break; From d67991f15ac09e4f9628dfbabc36f30ff95ee2cb Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Fri, 22 Sep 2017 08:33:30 +0800 Subject: [PATCH 6/7] MDL-59581 database: remove mssql strings from install.php --- lang/en/install.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lang/en/install.php b/lang/en/install.php index bb41d0a5163..d5f5cd392f6 100644 --- a/lang/en/install.php +++ b/lang/en/install.php @@ -167,7 +167,6 @@ $string['memorylimithelp'] = '

The PHP memory limit for your server is current

However, on some servers this will prevent all PHP pages from working (you will see errors when you look at pages) so you\'ll have to remove the .htaccess file.

'; -$string['mssqlextensionisnotpresentinphp'] = 'PHP has not been properly configured with the MSSQL extension so that it can communicate with SQL*Server. Please check your php.ini file or recompile PHP.'; $string['mysqliextensionisnotpresentinphp'] = 'PHP has not been properly configured with the MySQLi extension for it to communicate with MySQL. Please check your php.ini file or recompile PHP.'; $string['nativemariadb'] = 'MariaDB (native/mariadb)'; $string['nativemariadbhelp'] = '

The database is where most of the Moodle settings and data are stored and must be configured here.

@@ -180,9 +179,6 @@ $string['nativemysqlihelp'] = '

The database is where most of the Moodle setti

The database name, username, and password are required fields; table prefix is optional.

The database name may contain only alphanumeric characters, dollar ($) and underscore (_).

If the database currently does not exist, and the user you specify has permission, Moodle will attempt to create a new database with the correct permissions and settings.

'; -$string['nativemssql'] = 'SQL*Server FreeTDS (native/mssql)'; -$string['nativemssqlhelp'] = 'Now you need to configure the database where most Moodle data will be stored. -This database must already have been created and a username and password created to access it. Table prefix is mandatory.'; $string['nativeoci'] = 'Oracle (native/oci)'; $string['nativeocihelp'] = 'Now you need to configure the database where most Moodle data will be stored. This database must already have been created and a username and password created to access it. Table prefix is mandatory.'; @@ -254,3 +250,4 @@ $string['welcomep60'] = 'The following pages will lead you through some easy to $string['welcomep70'] = 'Click the "Next" button below to continue with the set up of Moodle.'; $string['wwwroot'] = 'Web address'; $string['wwwrooterror'] = 'The \'Web Address\' does not appear to be valid - this Moodle installation doesn\'t appear to be there. The value below has been reset.'; +// Deprecated since 3.4. \ No newline at end of file From 2f5fa7109ed18e19a500eacbfd1ff6f4ddc79b48 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Fri, 22 Sep 2017 09:13:09 +0800 Subject: [PATCH 7/7] MDL-59581 database: remove mssql references from lib/ files --- lib/dmllib.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/dmllib.php b/lib/dmllib.php index 58ab46b81c1..1a2f51400c0 100644 --- a/lib/dmllib.php +++ b/lib/dmllib.php @@ -314,10 +314,6 @@ function setup_DB() { $CFG->dbtype = 'pgsql'; break; - case 'mssql_n': - $CFG->dbtype = 'mssql'; - break; - case 'oci8po': $CFG->dbtype = 'oci'; break;