diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index a580d0af711..ee19a965ec0 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -2306,5 +2306,12 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2013071500.02); } + if ($oldversion < 2013072600.01) { + upgrade_mssql_nvarcharmax(); + upgrade_mssql_varbinarymax(); + + upgrade_main_savepoint(true, 2013072600.01); + } + return true; } diff --git a/lib/db/upgradelib.php b/lib/db/upgradelib.php index 6edc5d8c827..3d89eb95405 100644 --- a/lib/db/upgradelib.php +++ b/lib/db/upgradelib.php @@ -164,3 +164,109 @@ function upgrade_mysql_fix_unsigned_and_lob_columns() { $pbar->update($i, $tablecount, "Converted unsigned/lob columns in MySQL database - $i/$tablecount."); } } + +/** + * Migrate NTEXT to NVARCHAR(MAX). + */ +function upgrade_mssql_nvarcharmax() { + global $DB; + + if ($DB->get_dbfamily() !== 'mssql') { + return; + } + + $pbar = new progress_bar('mssqlconvertntext', 500, true); + + $prefix = $DB->get_prefix(); + $tables = $DB->get_tables(false); + + $tablecount = count($tables); + $i = 0; + foreach ($tables as $table) { + $i++; + + $columns = array(); + + $sql = "SELECT column_name + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = '{{$table}}' AND UPPER(data_type) = 'NTEXT'"; + $rs = $DB->get_recordset_sql($sql); + foreach ($rs as $column) { + $columns[] = $column->column_name; + } + $rs->close(); + + if ($columns) { + // Set appropriate timeout - 1 minute per thousand of records should be enough, min 60 minutes just in case. + $count = $DB->count_records($table, array()); + $timeout = ($count/1000)*60; + $timeout = ($timeout < 60*60) ? 60*60 : (int)$timeout; + upgrade_set_timeout($timeout); + + $updates = array(); + foreach ($columns as $column) { + // Change the definition. + $sql = "ALTER TABLE {$prefix}$table ALTER COLUMN $column NVARCHAR(MAX)"; + $DB->change_database_structure($sql); + $updates[] = "$column = $column"; + } + + // Now force the migration of text data to new optimised storage. + $sql = "UPDATE {{$table}} SET ".implode(', ', $updates); + $DB->execute($sql); + } + + $pbar->update($i, $tablecount, "Converted NTEXT to NVARCHAR(MAX) columns in MS SQL Server database - $i/$tablecount."); + } +} + +/** + * Migrate IMAGE to VARBINARY(MAX). + */ +function upgrade_mssql_varbinarymax() { + global $DB; + + if ($DB->get_dbfamily() !== 'mssql') { + return; + } + + $pbar = new progress_bar('mssqlconvertimage', 500, true); + + $prefix = $DB->get_prefix(); + $tables = $DB->get_tables(false); + + $tablecount = count($tables); + $i = 0; + foreach ($tables as $table) { + $i++; + + $columns = array(); + + $sql = "SELECT column_name + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = '{{$table}}' AND UPPER(data_type) = 'IMAGE'"; + $rs = $DB->get_recordset_sql($sql); + foreach ($rs as $column) { + $columns[] = $column->column_name; + } + $rs->close(); + + if ($columns) { + // Set appropriate timeout - 1 minute per thousand of records should be enough, min 60 minutes just in case. + $count = $DB->count_records($table, array()); + $timeout = ($count/1000)*60; + $timeout = ($timeout < 60*60) ? 60*60 : (int)$timeout; + upgrade_set_timeout($timeout); + + foreach ($columns as $column) { + // Change the definition. + $sql = "ALTER TABLE {$prefix}$table ALTER COLUMN $column VARBINARY(MAX)"; + $DB->change_database_structure($sql); + } + + // Binary columns should not be used, do not waste time optimising the storage. + } + + $pbar->update($i, $tablecount, "Converted IMAGE to VARBINARY(MAX) columns in MS SQL Server database - $i/$tablecount."); + } +} diff --git a/lib/ddl/mssql_sql_generator.php b/lib/ddl/mssql_sql_generator.php index bbf769ded3d..ccd2d811aec 100644 --- a/lib/ddl/mssql_sql_generator.php +++ b/lib/ddl/mssql_sql_generator.php @@ -221,10 +221,10 @@ class mssql_sql_generator extends sql_generator { $dbtype .= '(' . $xmldb_length . ')'; break; case XMLDB_TYPE_TEXT: - $dbtype = 'NTEXT'; + $dbtype = 'NVARCHAR(MAX)'; break; case XMLDB_TYPE_BINARY: - $dbtype = 'IMAGE'; + $dbtype = 'VARBINARY(MAX)'; break; case XMLDB_TYPE_DATETIME: $dbtype = 'DATETIME'; diff --git a/lib/dml/mssql_native_moodle_database.php b/lib/dml/mssql_native_moodle_database.php index 862b055f8bf..2191b893f7d 100644 --- a/lib/dml/mssql_native_moodle_database.php +++ b/lib/dml/mssql_native_moodle_database.php @@ -464,9 +464,15 @@ class mssql_native_moodle_database extends moodle_database { // 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; + 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 ? $rawcolumn->scale : false; @@ -573,6 +579,7 @@ class mssql_native_moodle_database extends moodle_database { $type = 'X'; break; case 'IMAGE': + case 'VARBINARY': case 'VARBINARY(MAX)': $type = 'B'; break; diff --git a/lib/dml/sqlsrv_native_moodle_database.php b/lib/dml/sqlsrv_native_moodle_database.php index 13c46e46f71..b5062b120b9 100644 --- a/lib/dml/sqlsrv_native_moodle_database.php +++ b/lib/dml/sqlsrv_native_moodle_database.php @@ -528,9 +528,15 @@ class sqlsrv_native_moodle_database extends moodle_database { // 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; + 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 ? $rawcolumn->scale : false; @@ -645,6 +651,7 @@ class sqlsrv_native_moodle_database extends moodle_database { break; case 'IMAGE': + case 'VARBINARY': case 'VARBINARY(MAX)': $type = 'B'; break; diff --git a/lib/upgrade.txt b/lib/upgrade.txt index d0a41926ec6..d2591e2ce55 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -11,6 +11,8 @@ information provided here is intended especially for developers. * Use core_text::* instead of textlib:: and also core_collator::* instead of collatorlib::*. * Use new function moodleform::mock_submit() to simulate form submission in unit tests (backported). * New $CFG->localcachedir setting useful for cluster nodes. Admins have to update X-Sendfile aliases if used. +* MS SQL Server drivers are now using NVARCHAR(MAX) instead of NTEXT and VARBINARY(MAX) instead of IMAGE, + this change should be fully transparent and it should help significantly with add-on compatibility. DEPRECATIONS: Various previously deprecated functions have now been altered to throw DEBUG_DEVELOPER debugging notices diff --git a/version.php b/version.php index f289eb5d47a..3fe6bc28413 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2013072600.00; // YYYYMMDD = weekly release date of this DEV branch +$version = 2013072600.01; // YYYYMMDD = weekly release date of this DEV branch // RR = release increments - 00 in DEV branches // .XX = incremental changes