mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
[SQLDB-Migration] Collation Validation (#21572)
Adding collation validation in target database selection page. collecting source database info including state, size, collation etc in the step 1 (select databases to assess and migrate) comparing source database collation and target database collation in target selection page. image
This commit is contained in:
@@ -53,7 +53,7 @@ const query_databases_with_size = `
|
|||||||
FROM sys.master_files with (nolock)
|
FROM sys.master_files with (nolock)
|
||||||
GROUP BY database_id
|
GROUP BY database_id
|
||||||
)
|
)
|
||||||
SELECT name, state_desc AS state, db_size.size
|
SELECT name, state_desc AS state, db_size.size, collation_name
|
||||||
FROM sys.databases with (nolock) LEFT JOIN db_size ON sys.databases.database_id = db_size.database_id
|
FROM sys.databases with (nolock) LEFT JOIN db_size ON sys.databases.database_id = db_size.database_id
|
||||||
WHERE sys.databases.state = 0
|
WHERE sys.databases.state = 0
|
||||||
`;
|
`;
|
||||||
@@ -88,6 +88,13 @@ export interface TableInfo {
|
|||||||
selectedForMigration: boolean;
|
selectedForMigration: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SourceDatabaseInfo {
|
||||||
|
databaseName: string;
|
||||||
|
databaseCollation: string;
|
||||||
|
databaseState: number;
|
||||||
|
databaseSizeInMB: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TargetDatabaseInfo {
|
export interface TargetDatabaseInfo {
|
||||||
serverName: string;
|
serverName: string;
|
||||||
serverCollation: string;
|
serverCollation: string;
|
||||||
@@ -331,6 +338,7 @@ export async function getDatabasesList(connectionProfile: azdata.connection.Conn
|
|||||||
name: getSqlString(row[0]),
|
name: getSqlString(row[0]),
|
||||||
state: getSqlString(row[1]),
|
state: getSqlString(row[1]),
|
||||||
sizeInMB: getSqlString(row[2]),
|
sizeInMB: getSqlString(row[2]),
|
||||||
|
collation: getSqlString(row[3])
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}) ?? [];
|
}) ?? [];
|
||||||
|
|||||||
@@ -410,6 +410,23 @@ export function SQL_TARGET_CONNECTION_SOURCE_NOT_MAPPED(sourceDatabaseName: stri
|
|||||||
sourceDatabaseName);
|
sourceDatabaseName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//`Database mapping error. Source database ({0}) collation ({1}) does not match target database ({2}) collation ({3}). Please select a target database with the same collation to the source database.`
|
||||||
|
export function SQL_TARGET_SOURCE_COLLATION_NOT_SAME(
|
||||||
|
sourceDatabaseName: string,
|
||||||
|
targetDatabaseName: string,
|
||||||
|
sourceDatabaseCollation: string | undefined,
|
||||||
|
targetDatabaseCollation: string | undefined): string {
|
||||||
|
return localize(
|
||||||
|
'sql.migration.wizard.target.source.collation.error',
|
||||||
|
"A mapping error was found between '{0}' and '{1}' databases. The source database collation '{2}' does not match the target database collation '{3}'. Please select or re-create a target database with the same collation as the source database.",
|
||||||
|
sourceDatabaseName,
|
||||||
|
targetDatabaseName,
|
||||||
|
sourceDatabaseCollation,
|
||||||
|
targetDatabaseCollation);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SQL_MIGRATION_TROUBLESHOOTING_LINK = localize('sql.migration.wizard.troubleshooting', 'Learn more: https://aka.ms/dms-migrations-troubleshooting.');
|
||||||
|
|
||||||
// Managed Instance
|
// Managed Instance
|
||||||
export const AZURE_SQL_DATABASE_MANAGED_INSTANCE = localize('sql.migration.azure.sql.database.managed.instance', "Azure SQL Managed Instance");
|
export const AZURE_SQL_DATABASE_MANAGED_INSTANCE = localize('sql.migration.azure.sql.database.managed.instance', "Azure SQL Managed Instance");
|
||||||
export const NO_MANAGED_INSTANCE_FOUND = localize('sql.migration.no.managedInstance.found', "No managed instances found.");
|
export const NO_MANAGED_INSTANCE_FOUND = localize('sql.migration.no.managedInstance.found', "No managed instances found.");
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export class RetryMigrationDialog {
|
|||||||
|
|
||||||
// SKURecommendation
|
// SKURecommendation
|
||||||
databaseList: [sourceDatabaseName],
|
databaseList: [sourceDatabaseName],
|
||||||
|
databaseInfoList: [],
|
||||||
serverAssessment: null,
|
serverAssessment: null,
|
||||||
skuRecommendation: null,
|
skuRecommendation: null,
|
||||||
migrationTargetType: getMigrationTargetTypeEnum(migration)!,
|
migrationTargetType: getMigrationTargetTypeEnum(migration)!,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { v4 as uuidv4 } from 'uuid';
|
|||||||
import { sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews, logError } from '../telemtery';
|
import { sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews, logError } from '../telemtery';
|
||||||
import { hashString, deepClone } from '../api/utils';
|
import { hashString, deepClone } from '../api/utils';
|
||||||
import { SKURecommendationPage } from '../wizard/skuRecommendationPage';
|
import { SKURecommendationPage } from '../wizard/skuRecommendationPage';
|
||||||
import { excludeDatabases, getConnectionProfile, LoginTableInfo, TargetDatabaseInfo } from '../api/sqlUtils';
|
import { excludeDatabases, getConnectionProfile, LoginTableInfo, SourceDatabaseInfo, TargetDatabaseInfo } from '../api/sqlUtils';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
export enum ValidateIrState {
|
export enum ValidateIrState {
|
||||||
@@ -143,6 +143,7 @@ export interface SavedInfo {
|
|||||||
closedPage: number;
|
closedPage: number;
|
||||||
databaseAssessment: string[];
|
databaseAssessment: string[];
|
||||||
databaseList: string[];
|
databaseList: string[];
|
||||||
|
databaseInfoList: SourceDatabaseInfo[];
|
||||||
migrationTargetType: MigrationTargetType | null;
|
migrationTargetType: MigrationTargetType | null;
|
||||||
azureAccount: azdata.Account | null;
|
azureAccount: azdata.Account | null;
|
||||||
azureTenant: azurecore.Tenant | null;
|
azureTenant: azurecore.Tenant | null;
|
||||||
@@ -218,6 +219,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
public mementoString: string;
|
public mementoString: string;
|
||||||
|
|
||||||
public _databasesForMigration: string[] = [];
|
public _databasesForMigration: string[] = [];
|
||||||
|
public _databaseInfosForMigration: SourceDatabaseInfo[] = [];
|
||||||
public _didUpdateDatabasesForMigration: boolean = false;
|
public _didUpdateDatabasesForMigration: boolean = false;
|
||||||
public _didDatabaseMappingChange: boolean = false;
|
public _didDatabaseMappingChange: boolean = false;
|
||||||
public _vmDbs: string[] = [];
|
public _vmDbs: string[] = [];
|
||||||
@@ -1268,6 +1270,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
closedPage: currentPage,
|
closedPage: currentPage,
|
||||||
databaseAssessment: [],
|
databaseAssessment: [],
|
||||||
databaseList: [],
|
databaseList: [],
|
||||||
|
databaseInfoList: [],
|
||||||
migrationTargetType: null,
|
migrationTargetType: null,
|
||||||
azureAccount: null,
|
azureAccount: null,
|
||||||
azureTenant: null,
|
azureTenant: null,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import * as constants from '../constants/strings';
|
|||||||
import { debounce } from '../api/utils';
|
import { debounce } from '../api/utils';
|
||||||
import * as styles from '../constants/styles';
|
import * as styles from '../constants/styles';
|
||||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||||
import { getDatabasesList, excludeDatabases } from '../api/sqlUtils';
|
import { getDatabasesList, excludeDatabases, SourceDatabaseInfo } from '../api/sqlUtils';
|
||||||
|
|
||||||
export class DatabaseSelectorPage extends MigrationWizardPage {
|
export class DatabaseSelectorPage extends MigrationWizardPage {
|
||||||
private _view!: azdata.ModelView;
|
private _view!: azdata.ModelView;
|
||||||
@@ -236,10 +236,12 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
|
|||||||
|
|
||||||
databaseList.sort((a, b) => a.options.name.localeCompare(b.options.name));
|
databaseList.sort((a, b) => a.options.name.localeCompare(b.options.name));
|
||||||
this._dbNames = [];
|
this._dbNames = [];
|
||||||
|
stateMachine._databaseInfosForMigration = [];
|
||||||
|
|
||||||
this._databaseTableValues = databaseList.map(database => {
|
this._databaseTableValues = databaseList.map(database => {
|
||||||
const databaseName = database.options.name;
|
const databaseName = database.options.name;
|
||||||
this._dbNames.push(databaseName);
|
this._dbNames.push(databaseName);
|
||||||
|
stateMachine._databaseInfosForMigration.push(this.getSourceDatabaseInfo(database));
|
||||||
return [
|
return [
|
||||||
selectedDatabases?.indexOf(databaseName) > -1,
|
selectedDatabases?.indexOf(databaseName) > -1,
|
||||||
<azdata.IconColumnCellValue>{
|
<azdata.IconColumnCellValue>{
|
||||||
@@ -271,4 +273,13 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
|
|||||||
});
|
});
|
||||||
this.migrationStateModel._databasesForAssessment = selectedDatabases;
|
this.migrationStateModel._databasesForAssessment = selectedDatabases;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getSourceDatabaseInfo(database: azdata.DatabaseInfo): SourceDatabaseInfo {
|
||||||
|
return {
|
||||||
|
databaseName: database.options.name,
|
||||||
|
databaseCollation: database.options.collation,
|
||||||
|
databaseSizeInMB: database.options.sizeInMB,
|
||||||
|
databaseState: database.options.state
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1039,17 +1039,23 @@ export class TargetSelectionPage extends MigrationWizardPage {
|
|||||||
|
|
||||||
private _getSourceTargetMappingErrors(): string[] {
|
private _getSourceTargetMappingErrors(): string[] {
|
||||||
// Validate source/target database mappings:
|
// Validate source/target database mappings:
|
||||||
const errors: string[] = [];
|
var errors: string[] = [];
|
||||||
|
const collationErrors: string[] = [];
|
||||||
const targetDatabaseKeys = new Map<string, string>();
|
const targetDatabaseKeys = new Map<string, string>();
|
||||||
const migrationDatabaseCount = this._azureResourceTable.dataValues?.length ?? 0;
|
const migrationDatabaseCount = this._azureResourceTable.dataValues?.length ?? 0;
|
||||||
this.migrationStateModel._targetDatabaseNames = [];
|
this.migrationStateModel._targetDatabaseNames = [];
|
||||||
|
const databaseInfosForMigration = new Map(this.migrationStateModel._databaseInfosForMigration.map(o => [o.databaseName, o]));
|
||||||
|
|
||||||
if (migrationDatabaseCount === 0) {
|
if (migrationDatabaseCount === 0) {
|
||||||
errors.push(constants.SQL_TARGET_MAPPING_ERROR_MISSING_TARGET);
|
errors.push(constants.SQL_TARGET_MAPPING_ERROR_MISSING_TARGET);
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < this.migrationStateModel._databasesForMigration.length; i++) {
|
for (let i = 0; i < this.migrationStateModel._databasesForMigration.length; i++) {
|
||||||
const sourceDatabaseName = this.migrationStateModel._databasesForMigration[i];
|
const sourceDatabaseName = this.migrationStateModel._databasesForMigration[i];
|
||||||
|
const sourceDatabaseInfo = databaseInfosForMigration.get(sourceDatabaseName);
|
||||||
const targetDatabaseInfo = this.migrationStateModel._sourceTargetMapping.get(sourceDatabaseName);
|
const targetDatabaseInfo = this.migrationStateModel._sourceTargetMapping.get(sourceDatabaseName);
|
||||||
const targetDatabaseName = targetDatabaseInfo?.databaseName;
|
const targetDatabaseName = targetDatabaseInfo?.databaseName;
|
||||||
|
const sourceDatabaseCollation = sourceDatabaseInfo?.databaseCollation;
|
||||||
|
const targetDatabaseCollation = targetDatabaseInfo?.databaseCollation;
|
||||||
if (targetDatabaseName && targetDatabaseName.length > 0) {
|
if (targetDatabaseName && targetDatabaseName.length > 0) {
|
||||||
if (!targetDatabaseKeys.has(targetDatabaseName)) {
|
if (!targetDatabaseKeys.has(targetDatabaseName)) {
|
||||||
targetDatabaseKeys.set(targetDatabaseName, sourceDatabaseName);
|
targetDatabaseKeys.set(targetDatabaseName, sourceDatabaseName);
|
||||||
@@ -1063,12 +1069,28 @@ export class TargetSelectionPage extends MigrationWizardPage {
|
|||||||
sourceDatabaseName,
|
sourceDatabaseName,
|
||||||
mappedSourceDatabaseName));
|
mappedSourceDatabaseName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collation validation
|
||||||
|
if (!this._isCollationSame(sourceDatabaseCollation, targetDatabaseCollation)) {
|
||||||
|
collationErrors.push(
|
||||||
|
constants.SQL_TARGET_SOURCE_COLLATION_NOT_SAME(
|
||||||
|
sourceDatabaseName,
|
||||||
|
targetDatabaseName,
|
||||||
|
sourceDatabaseCollation,
|
||||||
|
targetDatabaseCollation));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// source/target has mapping
|
// source/target has mapping
|
||||||
errors.push(constants.SQL_TARGET_CONNECTION_SOURCE_NOT_MAPPED(sourceDatabaseName));
|
errors.push(constants.SQL_TARGET_CONNECTION_SOURCE_NOT_MAPPED(sourceDatabaseName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (collationErrors.length > 0) {
|
||||||
|
collationErrors.push(constants.SQL_MIGRATION_TROUBLESHOOTING_LINK);
|
||||||
|
errors = errors.concat(collationErrors);
|
||||||
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1083,4 +1105,12 @@ export class TargetSelectionPage extends MigrationWizardPage {
|
|||||||
await this._targetUserNameInputBox.validate();
|
await this._targetUserNameInputBox.validate();
|
||||||
await this._azureResourceTable.validate();
|
await this._azureResourceTable.validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _isCollationSame(sourceDatabaseCollation: string | undefined, targetDatabaseCollation: string | undefined): boolean {
|
||||||
|
return sourceDatabaseCollation !== undefined &&
|
||||||
|
sourceDatabaseCollation.length > 0 &&
|
||||||
|
targetDatabaseCollation !== undefined &&
|
||||||
|
targetDatabaseCollation.length > 0 &&
|
||||||
|
sourceDatabaseCollation.toLocaleLowerCase() === targetDatabaseCollation.toLocaleLowerCase();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user