Schema Compare extension (#4974)

* extension now working

* fix diff editor title disappearing and remove border from source and target name boxes

* redoing a bunch of stuff that disappeared after rebasing

* add images and add to extensions.ts

* moving a few changes to the right place after rebase

* formatting

* update toolbar svgs

* addressing comments

* add return types

* Adding PR comments

* Adding light and dark theme icons

* Fixing the diff editor title for dark theme
This commit is contained in:
kisantia
2019-04-17 19:14:22 -07:00
committed by udeeshagautam
parent 910e4815fa
commit d3483afaed
37 changed files with 1359 additions and 6 deletions

View File

@@ -253,7 +253,8 @@ const sqlBuiltInExtensions = [
'profiler',
'admin-pack',
'big-data-cluster',
'dacpac'
'dacpac',
'schema-compare'
];
const builtInExtensions = require('../builtInExtensions.json');
/**

View File

@@ -300,7 +300,8 @@ const sqlBuiltInExtensions = [
'profiler',
'admin-pack',
'big-data-cluster',
'dacpac'
'dacpac',
'schema-compare'
];
// {{SQL CARBON EDIT}} - End

View File

@@ -433,4 +433,27 @@ export namespace AddServerGroupRequest {
export namespace RemoveServerGroupRequest {
export const type = new RequestType<RemoveServerGroupParams, boolean, void, void>('cms/removeCmsServerGroup');
}
// ------------------------------- <CMS> ----------------------------------------
// ------------------------------- <CMS> ----------------------------------------
// ------------------------------- <Schema Compare> -----------------------------
export interface SchemaCompareParams {
sourceEndpointInfo: azdata.SchemaCompareEndpointInfo;
targetEndpointInfo: azdata.SchemaCompareEndpointInfo;
taskExecutionMode: TaskExecutionMode;
}
export interface SchemaCompareGenerateScriptParams {
operationId: string;
targetDatabaseName: string;
scriptFilePath: string;
taskExecutionMode: TaskExecutionMode;
}
export namespace SchemaCompareRequest {
export const type = new RequestType<SchemaCompareParams, azdata.SchemaCompareResult, void, void>('schemaCompare/compare');
}
export namespace SchemaCompareGenerateScriptRequest {
export const type = new RequestType<SchemaCompareGenerateScriptParams, azdata.ResultStatus, void, void>('schemaCompare/generateScript');
}
// ------------------------------- <Schema Compare> -----------------------------

View File

@@ -145,6 +145,64 @@ export class DacFxServicesFeature extends SqlOpsFeature<undefined> {
}
}
export class SchemaCompareServicesFeature extends SqlOpsFeature<undefined> {
private static readonly messageTypes: RPCMessageType[] = [
contracts.SchemaCompareRequest.type,
contracts.SchemaCompareGenerateScriptRequest.type
];
constructor(client: SqlOpsDataClient) {
super(client, SchemaCompareServicesFeature.messageTypes);
}
public fillClientCapabilities(capabilities: ClientCapabilities): void {
}
public initialize(capabilities: ServerCapabilities): void {
this.register(this.messages, {
id: UUID.generateUuid(),
registerOptions: undefined
});
}
protected registerProvider(options: undefined): Disposable {
const client = this._client;
let self = this;
let schemaCompare = (sourceEndpointInfo: azdata.SchemaCompareEndpointInfo, targetEndpointInfo: azdata.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode): Thenable<azdata.SchemaCompareResult> => {
let params: contracts.SchemaCompareParams = {sourceEndpointInfo: sourceEndpointInfo, targetEndpointInfo: targetEndpointInfo, taskExecutionMode: taskExecutionMode};
return client.sendRequest(contracts.SchemaCompareRequest.type, params).then(
r => {
return r;
},
e => {
client.logFailedRequest(contracts.SchemaCompareRequest.type, e);
return Promise.resolve(undefined);
}
);
};
let schemaCompareGenerateScript = (operationId: string, targetDatabaseName: string, scriptFilePath: string, taskExecutionMode: azdata.TaskExecutionMode): Thenable<azdata.DacFxResult> => {
let params: contracts.SchemaCompareGenerateScriptParams = {operationId: operationId, targetDatabaseName: targetDatabaseName, scriptFilePath: scriptFilePath, taskExecutionMode: taskExecutionMode};
return client.sendRequest(contracts.SchemaCompareGenerateScriptRequest.type, params).then(
r => {
return r;
},
e => {
client.logFailedRequest(contracts.SchemaCompareGenerateScriptRequest.type, e);
return Promise.resolve(undefined);
}
);
};
return azdata.dataprotocol.registerSchemaCompareServicesProvider({
providerId: client.providerId,
schemaCompare,
schemaCompareGenerateScript
});
}
}
export class AgentServicesFeature extends SqlOpsFeature<undefined> {
private static readonly messagesTypes: RPCMessageType[] = [
contracts.AgentJobsRequest.type,

View File

@@ -21,7 +21,7 @@ import { CredentialStore } from './credentialstore/credentialstore';
import { AzureResourceProvider } from './resourceProvider/resourceProvider';
import * as Utils from './utils';
import { Telemetry, LanguageClientErrorHandler } from './telemetry';
import { TelemetryFeature, AgentServicesFeature, DacFxServicesFeature } from './features';
import { TelemetryFeature, AgentServicesFeature, DacFxServicesFeature, SchemaCompareServicesFeature } from './features';
import { AppContext } from './appContext';
import { ApiWrapper } from './apiWrapper';
import { UploadFilesCommand, MkDirCommand, SaveFileCommand, PreviewFileCommand, CopyPathCommand, DeleteFilesCommand } from './objectExplorerNodeProvider/hdfsCommands';
@@ -77,6 +77,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<MssqlE
TelemetryFeature,
AgentServicesFeature,
DacFxServicesFeature,
SchemaCompareServicesFeature
],
outputChannel: new CustomOutputChannel()
};

View File

@@ -0,0 +1,29 @@
# Microsoft SQL Server Schema Compare for Azure Data Studio
Microsoft SQL Server Schema Compare for Azure Data Studio includes:
## Schema Compare *(preview)*
The Schema Compare extension provides an easy to use experience to compare .dacpac files and databases and apply the changes from source to target.
This experience is currently in its initial preview. Please report issues and feature requests [here.](https://github.com/microsoft/azuredatastudio/issues)
### How do I start a Schema Comparison?
* The main entry point for schema compare is to right click a database in the Object Explorer, and click **Schema Compare**.
* The user can also launch the schema compare dialog from the command palette (Ctrl+Shift+P) by searching for **Schema Compare**
### Why would I use the Schema Compare?
Schema Compare was created to add the ability to compare the schemas from .dacpac files and databases and apply the changes.
## Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
## Privacy Statement
The [Microsoft Enterprise and Developer Privacy Statement](https://privacy.microsoft.com/en-us/privacystatement) describes the privacy statement of this software.
## License
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the [Source EULA](https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt).

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#231f20;}.cls-3{fill:#0095d7;}</style></defs><title>importflatfile_inverse</title><path class="cls-1" d="M13.34,1.57c-2.81,0-7.52.53-7.65,2.49v3.2L7,8.54V5.66a17.11,17.11,0,0,0,6.37,1,17.1,17.1,0,0,0,6.38-1V17.58c-.17.46-2.55,1.35-6.38,1.35a19.63,19.63,0,0,1-3.43-.27V20a23.78,23.78,0,0,0,3.43.25c2.86,0,7.66-.57,7.66-2.64V4.06C20.87,2.1,16.16,1.57,13.34,1.57Zm6.38,2.55c-.2.45-2.56,1.28-6.38,1.28S7.24,4.6,7,4.14c.27-.47,2.6-1.29,6.37-1.29s6.16.85,6.38,1.25h0Z"/><polygon class="cls-2" points="18.55 3.06 18.53 3.07 18.53 3.04 18.55 3.06"/><path class="cls-1" d="M7,10,5.69,8.68,5,8H0V19.85H8.91v-8ZM5.2,9.24l.49.49L7,11l.67.67H5.2Zm3,9.86H.74V8.71H4.46v3.71H8.17Z"/><path class="cls-3" d="M16.5,15a.27.27,0,0,1-.08.2L14.2,17.4a.26.26,0,0,1-.19.08.28.28,0,0,1-.2-.08.26.26,0,0,1-.08-.2.82.82,0,0,1,0-.14l.06-.25.08-.32.08-.32.07-.27,0-.17H4.5v-1.5h9.59l0-.17L14,13.79l-.08-.32-.08-.31-.06-.25a.91.91,0,0,1,0-.14.26.26,0,0,1,.08-.2.28.28,0,0,1,.2-.08.26.26,0,0,1,.19.08l2.22,2.22A.26.26,0,0,1,16.5,15Z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21"><defs><style>.cls-1{fill:#212121;}.cls-2{fill:#231f20;}.cls-3{fill:#00539c;}</style></defs><title>importflatfile</title><path class="cls-1" d="M13.34,1.3c-2.81,0-7.52.53-7.65,2.49V7L7,8.27V5.39a17.11,17.11,0,0,0,6.37,1,17.1,17.1,0,0,0,6.38-1V17.31c-.17.46-2.55,1.35-6.38,1.35a19.63,19.63,0,0,1-3.43-.27V19.7a23.78,23.78,0,0,0,3.43.25C16.2,20,21,19.38,21,17.31V3.79C20.87,1.83,16.16,1.3,13.34,1.3Zm6.38,2.55c-.2.45-2.56,1.28-6.38,1.28S7.24,4.33,7,3.87c.27-.47,2.6-1.29,6.37-1.29s6.16.85,6.38,1.25h0Z"/><polygon class="cls-2" points="18.55 2.79 18.53 2.81 18.53 2.78 18.55 2.79"/><path class="cls-1" d="M7,9.69,5.69,8.41,5,7.7H0V19.58H8.91v-8ZM5.2,9l.49.49L7,10.74l.67.67H5.2Zm3,9.86H.74V8.44H4.46v3.71H8.17Z"/><path class="cls-3" d="M16.5,14.72a.27.27,0,0,1-.08.2L14.2,17.14a.26.26,0,0,1-.19.08.28.28,0,0,1-.2-.08.26.26,0,0,1-.08-.2.82.82,0,0,1,0-.14l.06-.25.08-.32.08-.32.07-.27,0-.17H4.5V14h9.59l0-.17L14,13.53l-.08-.32-.08-.31-.06-.25a.91.91,0,0,1,0-.14.26.26,0,0,1,.08-.2.28.28,0,0,1,.2-.08.26.26,0,0,1,.19.08l2.22,2.22A.26.26,0,0,1,16.5,14.72Z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -0,0 +1,52 @@
{
"name": "schema-compare",
"displayName": "SQL Server Schema Compare",
"description": "SQL Server Schema Compare for Azure Data Studio supports comparing the schemas of databases and dacpacs.",
"version": "0.1.0",
"publisher": "Microsoft",
"preview": true,
"engines": {
"vscode": "^1.25.0",
"sqlops": "*"
},
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/extensions/import/Microsoft_SQL_Server_Import_Extension_and_Tools_Import_Flat_File_Preview.docx",
"icon": "images/sqlserver.png",
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
"activationEvents": [
"*"
],
"main": "./out/main",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/azuredatastudio.git"
},
"extensionDependencies": [
"Microsoft.mssql"
],
"contributes": {
"commands": [
{
"command": "schemaCompare.start",
"title": "Schema Compare",
"icon": {
"light": "./images/light_icon.svg",
"dark": "./images/dark_icon.svg"
}
}
],
"menus": {
"objectExplorer/item/context": [
{
"command": "schemaCompare.start",
"when": "connectionProvider == MSSQL && nodeType && nodeType == Database",
"group": "export"
}
]
}
},
"dependencies": {
"vscode-extension-telemetry": "0.0.18",
"vscode-nls": "^3.2.1"
},
"devDependencies": {}
}

View File

@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { SchemaCompareDialog } from '../dialogs/schemaCompareDialog';
/**
* The main controller class that initializes the extension
*/
export default class MainController implements vscode.Disposable {
protected _context: vscode.ExtensionContext;
public constructor(context: vscode.ExtensionContext) {
this._context = context;
}
public get extensionContext(): vscode.ExtensionContext {
return this._context;
}
public deactivate(): void {
}
public activate(): Promise<boolean> {
this.initializeSchemaCompareDialog();
return Promise.resolve(true);
}
private initializeSchemaCompareDialog(): void {
azdata.tasks.registerTask('schemaCompare.start', (profile: azdata.IConnectionProfile) => new SchemaCompareDialog().openDialog(profile));
}
public dispose(): void {
this.deactivate();
}
}

View File

@@ -0,0 +1,462 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vscode-nls';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as os from 'os';
import { SchemaCompareResult } from '../schemaCompareResult';
const localize = nls.loadMessageBundle();
const CompareButtonText: string = localize('schemaCompareDialog.Compare', 'Compare');
const CancelButtonText: string = localize('schemaCompareDialog.Cancel', 'Cancel');
const SourceTextBoxLabel: string = localize('schemaCompareDialog.SourceLabel', 'Source File');
const TargetTextBoxLabel: string = localize('schemaCompareDialog.TargetLabel', 'Target File');
const DacpacRadioButtonLabel: string = localize('schemaCompare.dacpacRadioButtonLabel', 'Data-tier Application File (.dacpac)');
const DatabaseRadioButtonLabel: string = localize('schemaCompare.databaseButtonLabel', 'Database');
const SourceRadioButtonsLabel: string = localize('schemaCompare.sourceButtonsLabel', 'Source Type');
const TargetRadioButtonsLabel: string = localize('schemaCompare.targetButtonsLabel', 'Target Type');
const NoActiveConnectionsLabel: string = localize('schemaCompare.NoActiveConnectionsText', 'No active connections');
const SchemaCompareLabel: string = localize('schemaCompare.dialogTitle', 'Schema Compare');
export class SchemaCompareDialog {
public dialog: azdata.window.Dialog;
private schemaCompareTab: azdata.window.DialogTab;
private sourceDacpacComponent: azdata.FormComponent;
private sourceTextBox: azdata.InputBoxComponent;
private sourceFileButton: azdata.ButtonComponent;
private sourceServerComponent: azdata.FormComponent;
private sourceServerDropdown: azdata.DropDownComponent;
private sourceDatabaseComponent: azdata.FormComponent;
private sourceDatabaseDropdown: azdata.DropDownComponent;
private sourceNoActiveConnectionsText: azdata.FormComponent;
private targetDacpacComponent: azdata.FormComponent;
private targetTextBox: azdata.InputBoxComponent;
private targetFileButton: azdata.ButtonComponent;
private targetServerComponent: azdata.FormComponent;
private targetServerDropdown: azdata.DropDownComponent;
private targetDatabaseComponent: azdata.FormComponent;
private targetDatabaseDropdown: azdata.DropDownComponent;
private targetNoActiveConnectionsText: azdata.FormComponent;
private formBuilder: azdata.FormBuilder;
private sourceIsDacpac: boolean;
private targetIsDacpac: boolean;
private database: string;
public dialogName: string;
protected initializeDialog(): void {
this.schemaCompareTab = azdata.window.createTab(SchemaCompareLabel);
this.initializeSchemaCompareTab();
this.dialog.content = [this.schemaCompareTab];
}
public openDialog(p: any, dialogName?: string): void {
let profile = p ? <azdata.IConnectionProfile>p.connectionProfile : undefined;
if (profile) {
this.database = profile.databaseName;
}
let event = dialogName ? dialogName : null;
this.dialog = azdata.window.createModelViewDialog(SchemaCompareLabel, event);
this.initializeDialog();
this.dialog.okButton.label = CompareButtonText;
this.dialog.okButton.onClick(async () => await this.execute());
this.dialog.cancelButton.label = CancelButtonText;
this.dialog.cancelButton.onClick(async () => await this.cancel());
azdata.window.openDialog(this.dialog);
}
protected async execute(): Promise<void> {
let sourceName: string;
let targetName: string;
let sourceEndpointInfo: azdata.SchemaCompareEndpointInfo;
if (this.sourceIsDacpac) {
sourceName = this.sourceTextBox.value;
sourceEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.dacpac,
databaseName: '',
ownerUri: '',
packageFilePath: this.sourceTextBox.value
};
} else {
sourceName = (this.sourceServerDropdown.value as ConnectionDropdownValue).name + '.' + (<azdata.CategoryValue>this.sourceDatabaseDropdown.value).name;
let ownerUri = await azdata.connection.getUriForConnection((this.sourceServerDropdown.value as ConnectionDropdownValue).connection.connectionId);
sourceEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.database,
databaseName: (<azdata.CategoryValue>this.sourceDatabaseDropdown.value).name,
ownerUri: ownerUri,
packageFilePath: ''
};
}
let targetEndpointInfo: azdata.SchemaCompareEndpointInfo;
if (this.targetIsDacpac) {
targetName = this.targetTextBox.value;
targetEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.dacpac,
databaseName: '',
ownerUri: '',
packageFilePath: this.targetTextBox.value
};
} else {
targetName = (this.targetServerDropdown.value as ConnectionDropdownValue).name + '.' + (<azdata.CategoryValue>this.targetDatabaseDropdown.value).name;
let ownerUri = await azdata.connection.getUriForConnection((this.targetServerDropdown.value as ConnectionDropdownValue).connection.connectionId);
targetEndpointInfo = {
endpointType: azdata.SchemaCompareEndpointType.database,
databaseName: (<azdata.CategoryValue>this.targetDatabaseDropdown.value).name,
ownerUri: ownerUri,
packageFilePath: ''
};
}
let schemaCompareResult = new SchemaCompareResult(sourceName, targetName, sourceEndpointInfo, targetEndpointInfo);
schemaCompareResult.start();
}
protected async cancel(): Promise<void> {
}
private initializeSchemaCompareTab(): void {
this.schemaCompareTab.registerContent(async view => {
this.sourceTextBox = view.modelBuilder.inputBox().withProperties({
width: 275
}).component();
this.targetTextBox = view.modelBuilder.inputBox().withProperties({
width: 275
}).component();
this.sourceServerComponent = await this.createSourceServerDropdown(view);
await this.populateServerDropdown(false);
this.sourceDatabaseComponent = await this.createSourceDatabaseDropdown(view);
if ((this.sourceServerDropdown.value as ConnectionDropdownValue)) {
await this.populateDatabaseDropdown((this.sourceServerDropdown.value as ConnectionDropdownValue).connection.connectionId, false);
}
this.targetServerComponent = await this.createTargetServerDropdown(view);
await this.populateServerDropdown(true);
this.targetDatabaseComponent = await this.createTargetDatabaseDropdown(view);
if ((this.targetServerDropdown.value as ConnectionDropdownValue)) {
await this.populateDatabaseDropdown((this.targetServerDropdown.value as ConnectionDropdownValue).connection.connectionId, true);
}
this.sourceDacpacComponent = await this.createFileBrowser(view, false);
this.targetDacpacComponent = await this.createFileBrowser(view, true);
let sourceRadioButtons = await this.createSourceRadiobuttons(view);
let targetRadioButtons = await this.createTargetRadiobuttons(view);
this.sourceNoActiveConnectionsText = await this.createNoActiveConnectionsText(view);
this.targetNoActiveConnectionsText = await this.createNoActiveConnectionsText(view);
// if schema compare was launched from a db context menu, set that db as the source
if (this.database) {
this.formBuilder = view.modelBuilder.formContainer()
.withFormItems([
sourceRadioButtons,
this.sourceServerComponent,
this.sourceDatabaseComponent,
targetRadioButtons,
this.targetDacpacComponent
], {
horizontal: true
});
} else {
this.formBuilder = view.modelBuilder.formContainer()
.withFormItems([
sourceRadioButtons,
this.sourceDacpacComponent,
targetRadioButtons,
this.targetDacpacComponent
], {
horizontal: true
});
}
let formModel = this.formBuilder.component();
await view.initializeModel(formModel);
});
}
private async createFileBrowser(view: azdata.ModelView, isTarget: boolean): Promise<azdata.FormComponent> {
let currentTextbox = isTarget ? this.targetTextBox : this.sourceTextBox;
if (isTarget) {
this.targetFileButton = view.modelBuilder.button().withProperties({
label: '•••',
}).component();
} else {
this.sourceFileButton = view.modelBuilder.button().withProperties({
label: '•••',
}).component();
}
let currentButton = isTarget ? this.targetFileButton : this.sourceFileButton;
currentButton.onDidClick(async (click) => {
let fileUris = await vscode.window.showOpenDialog(
{
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
defaultUri: vscode.Uri.file(os.homedir()),
openLabel: localize('schemaCompare.openFile', 'Open'),
filters: {
'dacpac Files': ['dacpac'],
}
}
);
if (!fileUris || fileUris.length === 0) {
return;
}
let fileUri = fileUris[0];
currentTextbox.value = fileUri.fsPath;
});
return {
component: currentTextbox,
title: isTarget ? TargetTextBoxLabel : SourceTextBoxLabel,
actions: [currentButton]
};
}
private async createSourceRadiobuttons(view: azdata.ModelView): Promise<azdata.FormComponent> {
let dacpacRadioButton = view.modelBuilder.radioButton()
.withProperties({
name: 'source',
label: DacpacRadioButtonLabel
}).component();
let databaseRadioButton = view.modelBuilder.radioButton()
.withProperties({
name: 'source',
label: DatabaseRadioButtonLabel
}).component();
// show dacpac file browser
dacpacRadioButton.onDidClick(() => {
this.sourceIsDacpac = true;
this.formBuilder.removeFormItem(this.sourceNoActiveConnectionsText);
this.formBuilder.removeFormItem(this.sourceServerComponent);
this.formBuilder.removeFormItem(this.sourceDatabaseComponent);
this.formBuilder.insertFormItem(this.sourceDacpacComponent, 1, { horizontal: true });
});
// show server and db dropdowns or 'No active connections' text
databaseRadioButton.onDidClick(() => {
this.sourceIsDacpac = false;
if ((this.sourceServerDropdown.value as ConnectionDropdownValue)) {
this.formBuilder.insertFormItem(this.sourceServerComponent, 1, { horizontal: true, componentWidth: 300 });
this.formBuilder.insertFormItem(this.sourceDatabaseComponent, 2, { horizontal: true, componentWidth: 300 });
} else {
this.formBuilder.insertFormItem(this.sourceNoActiveConnectionsText, 1, { horizontal: true });
}
this.formBuilder.removeFormItem(this.sourceDacpacComponent);
});
if (this.database) {
databaseRadioButton.checked = true;
this.sourceIsDacpac = false;
} else {
dacpacRadioButton.checked = true;
this.sourceIsDacpac = true;
}
let flexRadioButtonsModel = view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'column' })
.withItems([dacpacRadioButton, databaseRadioButton]
).component();
return {
component: flexRadioButtonsModel,
title: SourceRadioButtonsLabel
};
}
private async createTargetRadiobuttons(view: azdata.ModelView): Promise<azdata.FormComponent> {
let dacpacRadioButton = view.modelBuilder.radioButton()
.withProperties({
name: 'target',
label: DacpacRadioButtonLabel
}).component();
let databaseRadioButton = view.modelBuilder.radioButton()
.withProperties({
name: 'target',
label: DatabaseRadioButtonLabel
}).component();
// show dacpac file browser
dacpacRadioButton.onDidClick(() => {
this.targetIsDacpac = true;
this.formBuilder.removeFormItem(this.targetNoActiveConnectionsText);
this.formBuilder.removeFormItem(this.targetServerComponent);
this.formBuilder.removeFormItem(this.targetDatabaseComponent);
this.formBuilder.addFormItem(this.targetDacpacComponent, { horizontal: true });
});
// show server and db dropdowns or 'No active connections' text
databaseRadioButton.onDidClick(() => {
this.targetIsDacpac = false;
this.formBuilder.removeFormItem(this.targetDacpacComponent);
if ((this.targetServerDropdown.value as ConnectionDropdownValue)) {
this.formBuilder.addFormItem(this.targetServerComponent, { horizontal: true, componentWidth: 300 });
this.formBuilder.addFormItem(this.targetDatabaseComponent, { horizontal: true, componentWidth: 300 });
} else {
this.formBuilder.addFormItem(this.targetNoActiveConnectionsText, { horizontal: true });
}
});
dacpacRadioButton.checked = true;
this.targetIsDacpac = true;
let flexRadioButtonsModel = view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'column' })
.withItems([dacpacRadioButton, databaseRadioButton]
).component();
return {
component: flexRadioButtonsModel,
title: TargetRadioButtonsLabel
};
}
protected async createSourceServerDropdown(view: azdata.ModelView): Promise<azdata.FormComponent> {
this.sourceServerDropdown = view.modelBuilder.dropDown().component();
this.sourceServerDropdown.onValueChanged(async () => {
await this.populateDatabaseDropdown((this.sourceServerDropdown.value as ConnectionDropdownValue).connection.connectionId, false);
});
return {
component: this.sourceServerDropdown,
title: localize('schemaCompare.sourceServerDropdownTitle', 'Source Server')
};
}
protected async createTargetServerDropdown(view: azdata.ModelView): Promise<azdata.FormComponent> {
this.targetServerDropdown = view.modelBuilder.dropDown().component();
this.targetServerDropdown.onValueChanged(async () => {
await this.populateDatabaseDropdown((this.targetServerDropdown.value as ConnectionDropdownValue).connection.connectionId, true);
});
return {
component: this.targetServerDropdown,
title: localize('schemaCompare.targetServerDropdownTitle', 'Target Server')
};
}
protected async populateServerDropdown(isTarget: boolean): Promise<void> {
let currentDropdown = isTarget ? this.targetServerDropdown : this.sourceServerDropdown;
let values = await this.getServerValues();
currentDropdown.updateProperties({
values: values
});
}
protected async getServerValues(): Promise<{ connection: azdata.connection.Connection, displayName: string, name: string }[]> {
let cons = await azdata.connection.getActiveConnections();
// This user has no active connections
if (!cons || cons.length === 0) {
return undefined;
}
let values = cons.map(c => {
let db = c.options.databaseDisplayName;
let usr = c.options.user;
let srv = c.options.server;
if (!db) {
db = '<default>';
}
if (!usr) {
usr = 'default';
}
let finalName = `${srv}, ${db} (${usr})`;
return {
connection: c,
displayName: finalName,
name: srv
};
});
return values;
}
protected async createSourceDatabaseDropdown(view: azdata.ModelView): Promise<azdata.FormComponent> {
this.sourceDatabaseDropdown = view.modelBuilder.dropDown().component();
return {
component: this.sourceDatabaseDropdown,
title: localize('schemaCompare.sourceDatabaseDropdownTitle', 'Source Database')
};
}
protected async createTargetDatabaseDropdown(view: azdata.ModelView): Promise<azdata.FormComponent> {
this.targetDatabaseDropdown = view.modelBuilder.dropDown().component();
return {
component: this.targetDatabaseDropdown,
title: localize('schemaCompare.targetDatabaseDropdownTitle', 'Target Database')
};
}
protected async populateDatabaseDropdown(connectionId: string, isTarget: boolean): Promise<void> {
let currentDropdown = isTarget ? this.targetDatabaseDropdown : this.sourceDatabaseDropdown;
currentDropdown.updateProperties({ values: [] });
let values = await this.getDatabaseValues(connectionId);
currentDropdown.updateProperties({
values: values
});
}
protected async getDatabaseValues(connectionId: string): Promise<{ displayName, name }[]> {
let idx = -1;
let count = -1;
let values = (await azdata.connection.listDatabases(connectionId)).map(db => {
count++;
// if schema compare was launched from a db context menu, set that db at the top of the dropdown
if (this.database && db === this.database) {
idx = count;
}
return {
displayName: db,
name: db
};
});
if (idx >= 0) {
let tmp = values[0];
values[0] = values[idx];
values[idx] = tmp;
}
return values;
}
protected async createNoActiveConnectionsText(view: azdata.ModelView): Promise<azdata.FormComponent> {
let noActiveConnectionsText = view.modelBuilder.text().withProperties({ value: NoActiveConnectionsLabel }).component();
return {
component: noActiveConnectionsText,
title: ''
};
}
}
interface ConnectionDropdownValue extends azdata.CategoryValue {
connection: azdata.connection.Connection;
}

View File

@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import MainController from './controllers/mainController';
let controllers: MainController[] = [];
export async function activate(context: vscode.ExtensionContext): Promise<void> {
// Start the main controller
let mainController = new MainController(context);
controllers.push(mainController);
context.subscriptions.push(mainController);
await mainController.activate();
}
export function deactivate(): void {
for (let controller of controllers) {
controller.deactivate();
}
}

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M13 8C13.4115 8 13.7995 8.08073 14.1641 8.24219C14.5286 8.39844 14.8464 8.61198 15.1172 8.88281C15.388 9.15365 15.6016 9.47135 15.7578 9.83594C15.9193 10.2005 16 10.5885 16 11C16 11.4115 15.9193 11.7995 15.7578 12.1641C15.6016 12.5286 15.388 12.8464 15.1172 13.1172C14.8464 13.388 14.5286 13.6042 14.1641 13.7656C13.7995 13.9219 13.4115 14 13 14C12.6875 14 12.3828 13.9531 12.0859 13.8594C11.7891 13.7656 11.5156 13.6276 11.2656 13.4453C11.1823 13.5286 11.0417 13.6745 10.8438 13.8828C10.651 14.0859 10.4271 14.3203 10.1719 14.5859C9.91667 14.8464 9.64583 15.1198 9.35938 15.4062C9.07812 15.6875 8.8125 15.9479 8.5625 16.1875C8.3125 16.4219 8.09115 16.6172 7.89844 16.7734C7.70573 16.9245 7.57292 17 7.5 17C7.36458 17 7.2474 16.9505 7.14844 16.8516C7.04948 16.7526 7 16.6354 7 16.5C7 16.4271 7.07552 16.2943 7.22656 16.1016C7.38281 15.9089 7.57812 15.6875 7.8125 15.4375C8.05208 15.1875 8.3125 14.9219 8.59375 14.6406C8.88021 14.3542 9.15365 14.0833 9.41406 13.8281C9.67969 13.5729 9.91406 13.349 10.1172 13.1562C10.3255 12.9583 10.4714 12.8177 10.5547 12.7344C10.3724 12.4844 10.2344 12.2109 10.1406 11.9141C10.0469 11.6172 10 11.3125 10 11C10 10.5885 10.0781 10.2005 10.2344 9.83594C10.3958 9.47135 10.612 9.15365 10.8828 8.88281C11.1536 8.61198 11.4714 8.39844 11.8359 8.24219C12.2005 8.08073 12.5885 8 13 8ZM13 13C13.2708 13 13.5286 12.9479 13.7734 12.8438C14.0182 12.7344 14.2292 12.5911 14.4062 12.4141C14.5885 12.2318 14.7318 12.0208 14.8359 11.7812C14.9453 11.5365 15 11.276 15 11C15 10.7292 14.9453 10.4714 14.8359 10.2266C14.7318 9.98177 14.5885 9.77083 14.4062 9.59375C14.2292 9.41146 14.0182 9.26823 13.7734 9.16406C13.5286 9.05469 13.2708 9 13 9C12.724 9 12.4635 9.05469 12.2188 9.16406C11.9792 9.26823 11.7682 9.41146 11.5859 9.59375C11.4089 9.77083 11.2656 9.98177 11.1562 10.2266C11.0521 10.4714 11 10.7292 11 11C11 11.276 11.0521 11.5365 11.1562 11.7812C11.2656 12.0208 11.4089 12.2318 11.5859 12.4141C11.7682 12.5911 11.9792 12.7344 12.2188 12.8438C12.4635 12.9479 12.724 13 13 13ZM2 12V10H0V1H12V3H14V7.14062C13.8333 7.09896 13.6667 7.0651 13.5 7.03906C13.3333 7.01302 13.1667 7 13 7V4H3V11H9C9 11.1667 9.01302 11.3333 9.03906 11.5C9.0651 11.6667 9.09896 11.8333 9.14062 12H2ZM2 9V3H11V2H1V9H2Z" fill="#4894FE"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M13 8C13.4115 8 13.7995 8.08073 14.1641 8.24219C14.5286 8.39844 14.8464 8.61198 15.1172 8.88281C15.388 9.15365 15.6016 9.47135 15.7578 9.83594C15.9193 10.2005 16 10.5885 16 11C16 11.4115 15.9193 11.7995 15.7578 12.1641C15.6016 12.5286 15.388 12.8464 15.1172 13.1172C14.8464 13.388 14.5286 13.6042 14.1641 13.7656C13.7995 13.9219 13.4115 14 13 14C12.6875 14 12.3828 13.9531 12.0859 13.8594C11.7891 13.7656 11.5156 13.6276 11.2656 13.4453C11.1823 13.5286 11.0417 13.6745 10.8438 13.8828C10.651 14.0859 10.4271 14.3203 10.1719 14.5859C9.91667 14.8464 9.64583 15.1198 9.35938 15.4062C9.07812 15.6875 8.8125 15.9479 8.5625 16.1875C8.3125 16.4219 8.09115 16.6172 7.89844 16.7734C7.70573 16.9245 7.57292 17 7.5 17C7.36458 17 7.2474 16.9505 7.14844 16.8516C7.04948 16.7526 7 16.6354 7 16.5C7 16.4271 7.07552 16.2943 7.22656 16.1016C7.38281 15.9089 7.57812 15.6875 7.8125 15.4375C8.05208 15.1875 8.3125 14.9219 8.59375 14.6406C8.88021 14.3542 9.15365 14.0833 9.41406 13.8281C9.67969 13.5729 9.91406 13.349 10.1172 13.1562C10.3255 12.9583 10.4714 12.8177 10.5547 12.7344C10.3724 12.4844 10.2344 12.2109 10.1406 11.9141C10.0469 11.6172 10 11.3125 10 11C10 10.5885 10.0781 10.2005 10.2344 9.83594C10.3958 9.47135 10.612 9.15365 10.8828 8.88281C11.1536 8.61198 11.4714 8.39844 11.8359 8.24219C12.2005 8.08073 12.5885 8 13 8ZM13 13C13.2708 13 13.5286 12.9479 13.7734 12.8438C14.0182 12.7344 14.2292 12.5911 14.4062 12.4141C14.5885 12.2318 14.7318 12.0208 14.8359 11.7812C14.9453 11.5365 15 11.276 15 11C15 10.7292 14.9453 10.4714 14.8359 10.2266C14.7318 9.98177 14.5885 9.77083 14.4062 9.59375C14.2292 9.41146 14.0182 9.26823 13.7734 9.16406C13.5286 9.05469 13.2708 9 13 9C12.724 9 12.4635 9.05469 12.2188 9.16406C11.9792 9.26823 11.7682 9.41146 11.5859 9.59375C11.4089 9.77083 11.2656 9.98177 11.1562 10.2266C11.0521 10.4714 11 10.7292 11 11C11 11.276 11.0521 11.5365 11.1562 11.7812C11.2656 12.0208 11.4089 12.2318 11.5859 12.4141C11.7682 12.5911 11.9792 12.7344 12.2188 12.8438C12.4635 12.9479 12.724 13 13 13ZM2 12V10H0V1H12V3H14V7.14062C13.8333 7.09896 13.6667 7.0651 13.5 7.03906C13.3333 7.01302 13.1667 7 13 7V4H3V11H9C9 11.1667 9.01302 11.3333 9.03906 11.5C9.0651 11.6667 9.09896 11.8333 9.14062 12H2ZM2 9V3H11V2H1V9H2Z" fill="#015CDA"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 4.28906V16H1V0H9.71094L14 4.28906ZM10 4H12.2891L10 1.71094V4ZM13 15V5H9V1H2V15H13ZM5.85156 7.35156L4.20312 9L5.85156 10.6484L5.14844 11.3516L2.79688 9L5.14844 6.64844L5.85156 7.35156ZM9.85156 6.64844L12.2031 9L9.85156 11.3516L9.14844 10.6484L10.7969 9L9.14844 7.35156L9.85156 6.64844Z" fill="#4894FE"/>
</svg>

After

Width:  |  Height:  |  Size: 419 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 4.28906V16H1V0H9.71094L14 4.28906ZM10 4H12.2891L10 1.71094V4ZM13 15V5H9V1H2V15H13ZM5.85156 7.35156L4.20312 9L5.85156 10.6484L5.14844 11.3516L2.79688 9L5.14844 6.64844L5.85156 7.35156ZM9.85156 6.64844L12.2031 9L9.85156 11.3516L9.14844 10.6484L10.7969 9L9.14844 7.35156L9.85156 6.64844Z" fill="#015CDA"/>
</svg>

After

Width:  |  Height:  |  Size: 419 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 11V12H1.95312L3.22656 13.2734L2.52344 13.9766L0.046875 11.5L2.52344 9.02344L3.22656 9.72656L1.95312 11H16ZM12.7734 6.27344L14.0469 5H0V4H14.0469L12.7734 2.72656L13.4766 2.02344L15.9531 4.5L13.4766 6.97656L12.7734 6.27344Z" fill="#4894FE"/>
</svg>

After

Width:  |  Height:  |  Size: 356 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 11V12H1.95312L3.22656 13.2734L2.52344 13.9766L0.046875 11.5L2.52344 9.02344L3.22656 9.72656L1.95312 11H16ZM12.7734 6.27344L14.0469 5H0V4H14.0469L12.7734 2.72656L13.4766 2.02344L15.9531 4.5L13.4766 6.97656L12.7734 6.27344Z" fill="#015CDA"/>
</svg>

After

Width:  |  Height:  |  Size: 356 B

View File

@@ -0,0 +1,366 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vscode-nls';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as os from 'os';
import * as path from 'path';
const localize = nls.loadMessageBundle();
export class SchemaCompareResult {
private differencesTable: azdata.TableComponent;
private loader: azdata.LoadingComponent;
private editor: azdata.workspace.ModelViewEditor;
private diffEditor: azdata.DiffEditorComponent;
private splitView: azdata.SplitViewContainer;
private flexModel: azdata.FlexContainer;
private noDifferencesLabel: azdata.TextComponent;
private sourceTargetFlexLayout: azdata.FlexContainer;
private switchButton: azdata.ButtonComponent;
private compareButton: azdata.ButtonComponent;
private generateScriptButton: azdata.ButtonComponent;
private SchemaCompareActionMap: Map<Number, string>;
private comparisonResult: azdata.SchemaCompareResult;
private sourceNameComponent: azdata.TableComponent;
private targetNameComponent: azdata.TableComponent;
constructor(private sourceName: string, private targetName: string, private sourceEndpointInfo: azdata.SchemaCompareEndpointInfo, private targetEndpointInfo: azdata.SchemaCompareEndpointInfo) {
this.SchemaCompareActionMap = new Map<Number, string>();
this.SchemaCompareActionMap[azdata.SchemaUpdateAction.Delete] = localize('schemaCompare.deleteAction', 'Delete');
this.SchemaCompareActionMap[azdata.SchemaUpdateAction.Change] = localize('schemaCompare.changeAction', 'Change');
this.SchemaCompareActionMap[azdata.SchemaUpdateAction.Add] = localize('schemaCompare.addAction', 'Add');
this.editor = azdata.workspace.createModelViewEditor(localize('schemaCompare.Title', 'Schema Compare'), { retainContextWhenHidden: true, supportsSave: true });
this.editor.registerContent(async view => {
this.differencesTable = view.modelBuilder.table().withProperties({
data: [],
height: 300,
}).component();
this.diffEditor = view.modelBuilder.diffeditor().withProperties({
contentLeft: os.EOL,
contentRight: os.EOL,
height: 500,
title: localize('schemaCompare.ObjectDefinitionsTitle', 'Object Definitions')
}).component();
this.splitView = view.modelBuilder.splitViewContainer().component();
let sourceTargetLabels = view.modelBuilder.flexContainer()
.withProperties({
alignItems: 'stretch',
horizontal: true
}).component();
this.sourceTargetFlexLayout = view.modelBuilder.flexContainer()
.withProperties({
alignItems: 'stretch',
horizontal: true
}).component();
this.createSwitchButton(view);
this.createCompareButton(view);
this.createGenerateScriptButton(view);
this.resetButtons();
let toolBar = view.modelBuilder.toolbarContainer();
toolBar.addToolbarItems([{
component: this.compareButton
}, {
component: this.generateScriptButton,
toolbarSeparatorAfter: true
},
{
component: this.switchButton
}]);
let sourceLabel = view.modelBuilder.text().withProperties({
value: localize('schemaCompare.sourceLabel', 'Source')
}).component();
let targetLabel = view.modelBuilder.text().withProperties({
value: localize('schemaCompare.targetLabel', 'Target')
}).component();
let arrowLabel = view.modelBuilder.text().withProperties({
value: localize('schemaCompare.switchLabel', '➔')
}).component();
this.sourceNameComponent = view.modelBuilder.table().withProperties({
columns: [
{
value: sourceName,
headerCssClass: 'no-borders',
toolTip: sourceName
},
]
}).component();
this.targetNameComponent = view.modelBuilder.table().withProperties({
columns: [
{
value: targetName,
headerCssClass: 'no-borders',
toolTip: targetName
},
]
}).component();
sourceTargetLabels.addItem(sourceLabel, { CSSStyles: { 'width': '55%', 'margin-left': '15px', 'font-size': 'larger', 'font-weight': 'bold' } });
sourceTargetLabels.addItem(targetLabel, { CSSStyles: { 'width': '45%', 'font-size': 'larger', 'font-weight': 'bold' } });
this.sourceTargetFlexLayout.addItem(this.sourceNameComponent, { CSSStyles: { 'width': '45%', 'height': '25px', 'margin-top': '10px', 'margin-left': '15px' } });
this.sourceTargetFlexLayout.addItem(arrowLabel, { CSSStyles: { 'width': '10%', 'font-size': 'larger', 'text-align-last': 'center' } });
this.sourceTargetFlexLayout.addItem(this.targetNameComponent, { CSSStyles: { 'width': '45%', 'height': '25px', 'margin-top': '10px', 'margin-left': '15px' } });
this.loader = view.modelBuilder.loadingComponent().component();
this.noDifferencesLabel = view.modelBuilder.text().withProperties({
value: localize('schemaCompare.noDifferences', 'No schema differences were found')
}).component();
this.flexModel = view.modelBuilder.flexContainer().component();
this.flexModel.addItem(toolBar.component(), { flex: 'none' });
this.flexModel.addItem(sourceTargetLabels, { flex: 'none' });
this.flexModel.addItem(this.sourceTargetFlexLayout, { flex: 'none' });
this.flexModel.addItem(this.loader, { CSSStyles: { 'margin-top': '30px' } });
this.flexModel.setLayout({
flexFlow: 'column',
height: '100%'
});
await view.initializeModel(this.flexModel);
});
}
public start(): void {
this.editor.openEditor();
this.execute();
}
private async execute(): Promise<void> {
let service = await SchemaCompareResult.getService('MSSQL');
this.comparisonResult = await service.schemaCompare(this.sourceEndpointInfo, this.targetEndpointInfo, azdata.TaskExecutionMode.execute);
if (!this.comparisonResult || !this.comparisonResult.success) {
vscode.window.showErrorMessage(localize('schemaCompare.compareErrorMessage', "Schema Compare failed: {0}", this.comparisonResult.errorMessage ? this.comparisonResult.errorMessage : 'Unknown'));
return;
}
let data = this.getAllDifferences(this.comparisonResult.differences);
this.differencesTable.updateProperties({
data: data,
columns: [
{
value: localize('schemaCompare.typeColumn', 'Type'),
cssClass: 'align-with-header',
width: 50
},
{
value: localize('schemaCompare.sourceNameColumn', 'Target Name'),
cssClass: 'align-with-header',
width: 90
},
{
value: localize('schemaCompare.actionColumn', 'Action'),
cssClass: 'align-with-header',
width: 30
},
{
value: localize('schemaCompare.targetNameColumn', 'Source Name'),
cssClass: 'align-with-header',
width: 150
}]
});
this.splitView.addItem(this.differencesTable);
this.splitView.addItem(this.diffEditor);
this.splitView.setLayout({
orientation: 'vertical',
splitViewHeight: 800
});
this.flexModel.removeItem(this.loader);
this.switchButton.enabled = true;
this.compareButton.enabled = true;
if (this.comparisonResult.differences.length > 0) {
this.flexModel.addItem(this.splitView);
// only enable generate script button if the target is a db
if (this.targetEndpointInfo.endpointType === azdata.SchemaCompareEndpointType.database) {
this.generateScriptButton.enabled = true;
} else {
this.generateScriptButton.title = localize('schemaCompare.generateScriptButtonDisabledTitle', 'Generate script is enabled when the target is a database');
}
} else {
this.flexModel.addItem(this.noDifferencesLabel, { CSSStyles: { 'margin': 'auto' } });
}
let sourceText = '';
let targetText = '';
this.differencesTable.onRowSelected(() => {
let difference = this.comparisonResult.differences[this.differencesTable.selectedRows[0]];
if (difference !== undefined) {
sourceText = difference.sourceScript === null ? '\n' : this.getAggregatedScript(difference, true);
targetText = difference.targetScript === null ? '\n' : this.getAggregatedScript(difference, false);
this.diffEditor.updateProperties({
contentLeft: sourceText,
contentRight: targetText,
title: localize('schemaCompare.ObjectDefinitionsTitle', 'Object Definitions')
});
}
});
}
private getAllDifferences(differences: azdata.DiffEntry[]): string[][] {
let data = [];
if (differences) {
differences.forEach(difference => {
if (difference.differenceType === azdata.SchemaDifferenceType.Object) {
if (difference.sourceValue !== null || difference.targetValue !== null) {
data.push([difference.name, difference.sourceValue, this.SchemaCompareActionMap[difference.updateAction], difference.targetValue]);
}
}
});
}
return data;
}
private getAggregatedScript(diffEntry: azdata.DiffEntry, getSourceScript: boolean): string {
let script = '';
if (diffEntry !== null) {
script += getSourceScript ? diffEntry.sourceScript : diffEntry.targetScript;
diffEntry.children.forEach(child => {
let childScript = this.getAggregatedScript(child, getSourceScript);
if (childScript !== 'null') {
script += childScript;
}
});
}
return script;
}
private reExecute(): void {
this.flexModel.removeItem(this.splitView);
this.flexModel.removeItem(this.noDifferencesLabel);
this.flexModel.addItem(this.loader, { CSSStyles: { 'margin-top': '30px' } });
this.diffEditor.updateProperties({
contentLeft: os.EOL,
contentRight: os.EOL
});
this.differencesTable.selectedRows = null;
this.resetButtons();
this.execute();
}
private createCompareButton(view: azdata.ModelView): void {
this.compareButton = view.modelBuilder.button().withProperties({
label: localize('schemaCompare.compareButton', 'Compare'),
iconPath: {
light: path.join(__dirname, 'media', 'compare.svg'),
dark: path.join(__dirname, 'media', 'compare-inverse.svg')
},
title: localize('schemaCompare.compareButtonTitle', 'Compare')
}).component();
this.compareButton.onDidClick(async (click) => {
this.reExecute();
});
}
private createGenerateScriptButton(view: azdata.ModelView): void {
this.generateScriptButton = view.modelBuilder.button().withProperties({
label: localize('schemaCompare.generateScriptButton', 'Generate script'),
iconPath: {
light : path.join(__dirname, 'media', 'generate-script.svg'),
dark: path.join(__dirname, 'media', 'generate-script-inverse.svg')
},
}).component();
this.generateScriptButton.onDidClick(async (click) => {
// get file path
let now = new Date();
let datetime = now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate() + '-' + now.getHours() + '-' + now.getMinutes() + '-' + now.getSeconds();
let defaultFilePath = path.join(os.homedir(), this.targetName + '_Update_' + datetime + '.sql');
let fileUri = await vscode.window.showSaveDialog(
{
defaultUri: vscode.Uri.file(defaultFilePath),
saveLabel: localize('schemaCompare.saveFile', 'Save'),
filters: {
'SQL Files': ['sql'],
}
}
);
if (!fileUri) {
return;
}
let service = await SchemaCompareResult.getService('MSSQL');
let result = await service.schemaCompareGenerateScript(this.comparisonResult.operationId, this.targetEndpointInfo.databaseName, fileUri.fsPath, azdata.TaskExecutionMode.execute);
if (!result || !result.success) {
vscode.window.showErrorMessage(
localize('schemaCompare.generateScriptErrorMessage', "Generate script failed: '{0}'", (result && result.errorMessage) ? result.errorMessage : 'Unknown'));
}
});
}
private resetButtons(): void {
this.compareButton.enabled = false;
this.switchButton.enabled = false;
this.generateScriptButton.enabled = false;
this.generateScriptButton.title = localize('schemaCompare.generateScriptEnabledButton', 'Generate script to deploy changes to target');
}
private createSwitchButton(view: azdata.ModelView): void {
let swapIcon = path.join(__dirname, 'media', 'switch-directions.svg');
this.switchButton = view.modelBuilder.button().withProperties({
label: localize('schemaCompare.switchDirectionButton', 'Switch direction'),
iconPath: {
light : path.join(__dirname, 'media', 'switch-directions.svg'),
dark: path.join(__dirname, 'media', 'switch-directions-inverse.svg')
},
title: localize('schemaCompare.switchButtonTitle', 'Switch source and target')
}).component();
this.switchButton.onDidClick(async (click) => {
// switch source and target
[this.sourceEndpointInfo, this.targetEndpointInfo] = [this.targetEndpointInfo, this.sourceEndpointInfo];
[this.sourceName, this.targetName] = [this.targetName, this.sourceName];
this.sourceNameComponent.updateProperties({
columns: [
{
value: this.sourceName,
headerCssClass: 'no-borders',
toolTip: this.sourceName
},
]
});
this.targetNameComponent.updateProperties({
columns: [
{
value: this.targetName,
headerCssClass: 'no-borders',
toolTip: this.targetName
},
]
});
this.reExecute();
});
}
private static async getService(providerName: string): Promise<azdata.SchemaCompareServicesProvider> {
let service = azdata.dataprotocol.getProvider<azdata.SchemaCompareServicesProvider>(providerName, azdata.DataProviderType.SchemaCompareServicesProvider);
return service;
}
}

View File

@@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../src/sql/azdata.d.ts'/>
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
/// <reference types='@types/node'/>

View File

@@ -0,0 +1,20 @@
{
"compileOnSave": true,
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "./out",
"lib": [
"es6",
"es2015.promise"
],
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"declaration": false
},
"exclude": [
"node_modules"
]
}

View File

@@ -0,0 +1,46 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
applicationinsights@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
integrity sha1-U0Rrgw/o1dYZ7uKieLMdPSUDCSc=
dependencies:
diagnostic-channel "0.2.0"
diagnostic-channel-publishers "0.2.1"
zone.js "0.7.6"
diagnostic-channel-publishers@0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM=
diagnostic-channel@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=
dependencies:
semver "^5.3.0"
semver@^5.3.0:
version "5.7.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
vscode-extension-telemetry@0.0.18:
version "0.0.18"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327"
integrity sha512-Vw3Sr+dZwl+c6PlsUwrTtCOJkgrmvS3OUVDQGcmpXWAgq9xGq6as0K4pUx+aGqTjzLAESmWSrs6HlJm6J6Khcg==
dependencies:
applicationinsights "1.0.1"
vscode-nls@^3.2.1:
version "3.2.5"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4"
integrity sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw==
zone.js@0.7.6:
version "0.7.6"
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
integrity sha1-+7w50+AmHQmG8boGMG6zrrDSIAk=

View File

@@ -44,6 +44,8 @@ declare module 'azdata' {
export function registerDacFxServicesProvider(provider: DacFxServicesProvider): vscode.Disposable;
export function registerSchemaCompareServicesProvider(provider: SchemaCompareServicesProvider): vscode.Disposable;
/**
* An [event](#Event) which fires when the specific flavor of a language used in DMP
* connections has changed. And example is for a SQL connection, the flavor changes
@@ -1699,6 +1701,51 @@ declare module 'azdata' {
generateDeployPlan(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: TaskExecutionMode): Thenable<GenerateDeployPlanResult>;
}
// Schema Compare interfaces -----------------------------------------------------------------------
export interface SchemaCompareResult extends ResultStatus {
operationId: string;
areEqual: boolean;
differences: DiffEntry[];
}
export interface DiffEntry {
updateAction: SchemaUpdateAction;
differenceType: SchemaDifferenceType;
name: string;
sourceValue: string;
targetValue: string;
parent: DiffEntry;
children: DiffEntry[];
sourceScript: string;
targetScript: string;
}
export enum SchemaUpdateAction {
Delete = 0,
Change = 1,
Add = 2
}
export enum SchemaDifferenceType {
Object = 0,
Property = 1
}
export enum SchemaCompareEndpointType {
database = 0,
dacpac = 1
}
export interface SchemaCompareEndpointInfo {
endpointType: SchemaCompareEndpointType;
packageFilePath: string;
databaseName: string;
ownerUri: string;
}
export interface SchemaCompareServicesProvider extends DataProvider {
schemaCompare(sourceEndpointInfo: SchemaCompareEndpointInfo, targetEndpointInfo: SchemaCompareEndpointInfo, taskExecutionMode: TaskExecutionMode): Thenable<SchemaCompareResult>;
schemaCompareGenerateScript(operationId: string, targetDatabaseName: string, scriptFilePath: string, taskExecutionMode: TaskExecutionMode): Thenable<ResultStatus>;
}
// Security service interfaces ------------------------------------------------------------------------
export interface CredentialInfo {
id: number;
@@ -2916,6 +2963,7 @@ declare module 'azdata' {
value: string;
width?: number;
cssClass?: string;
headerCssClass?: string;
toolTip?: string;
}
@@ -3764,6 +3812,7 @@ declare module 'azdata' {
AgentServicesProvider = 'AgentServicesProvider',
CapabilitiesProvider = 'CapabilitiesProvider',
DacFxServicesProvider = 'DacFxServicesProvider',
SchemaCompareServicesProvider = 'SchemaCompareServicesProvider',
ObjectExplorerNodeProvider = 'ObjectExplorerNodeProvider',
}

View File

@@ -30,7 +30,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation';
@Component({
template: `
<div *ngIf="_title">
<div style="width: 100%; height:100%; padding-left:3px !important; background: #F4F4F4; border: 1px solid #BFBDBD;">
<div class="modelview-diff-editor-title" style="width: 100%; height:100%; padding-left:3px !important; border: 1px solid #BFBDBD;">
{{_title}}
</div>
</div>`,
@@ -71,6 +71,7 @@ export default class DiffEditorComponent extends ComponentBase implements ICompo
private _createEditor(): void {
this._instantiationService = this._instantiationService.createChild(new ServiceCollection([IProgressService, new SimpleProgressService()]));
this._editor = this._instantiationService.createInstance(TextDiffEditor);
this._editor.reverseColoring();
this._editor.create(this._el.nativeElement);
this._editor.setVisible(true);
let uri1 = this.createUri('source');

View File

@@ -13,4 +13,12 @@ modelview-diff-editor-component {
height: 100%;
width : 100%;
display: block;
}
.vs-dark modelview-diff-editor-title {
background: #444444;
}
modelview-diff-editor-title {
background: #f4f4f4;
}

View File

@@ -58,6 +58,7 @@ export default class TableComponent extends ComponentBase implements IComponent,
field: col.value,
width: col.width,
cssClass: col.cssClass,
headerCssClass: col.headerCssClass,
toolTip: col.toolTip
};
} else {

View File

@@ -11,4 +11,9 @@
.align-with-header
{
padding-left:3px !important;
}
.no-borders
{
border: none !important
}

View File

@@ -0,0 +1,59 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { localize } from 'vs/nls';
export const SERVICE_ID = 'SchemaCompareService';
export const ISchemaCompareService = createDecorator<ISchemaCompareService>(SERVICE_ID);
export interface ISchemaCompareService {
_serviceBrand: any;
registerProvider(providerId: string, provider: azdata.SchemaCompareServicesProvider): void;
schemaCompare(sourceEndpointInfo: azdata.SchemaCompareEndpointInfo, targetEndpointInfo: azdata.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode): void;
schemaCompareGenerateScript(operationId: string, targetDatabaseName: string, scriptFilePath: string, taskExecutionMode: azdata.TaskExecutionMode): void;
}
export class SchemaCompareService implements ISchemaCompareService {
_serviceBrand: any;
private _providers: { [handle: string]: azdata.SchemaCompareServicesProvider; } = Object.create(null);
constructor(@IConnectionManagementService private _connectionService: IConnectionManagementService) { }
registerProvider(providerId: string, provider: azdata.SchemaCompareServicesProvider): void {
this._providers[providerId] = provider;
}
schemaCompare(sourceEndpointInfo: azdata.SchemaCompareEndpointInfo, targetEndpointInfo: azdata.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode): Thenable<azdata.SchemaCompareResult> {
return this._runAction(sourceEndpointInfo.ownerUri, (runner) => {
return runner.schemaCompare(sourceEndpointInfo, targetEndpointInfo, taskExecutionMode);
});
}
schemaCompareGenerateScript(operationId: string, targetDatabaseName: string, scriptFilePath: string, taskExecutionMode: azdata.TaskExecutionMode): Thenable<azdata.ResultStatus> {
return this._runAction('', (runner) => {
return runner.schemaCompareGenerateScript(operationId, targetDatabaseName, scriptFilePath, taskExecutionMode);
});
}
private _runAction<T>(uri: string, action: (handler: azdata.SchemaCompareServicesProvider) => Thenable<T>): Thenable<T> {
let providerId: string = this._connectionService.getProviderIdFromUri(uri);
if (!providerId) {
return Promise.reject(new Error(localize('providerIdNotValidError', "Connection is required in order to interact with SchemaCompareService")));
}
let handler = this._providers[providerId];
if (handler) {
return action(handler);
} else {
return Promise.reject(new Error(localize('noHandlerRegistered', "No Handler Registered")));
}
}
}

2
src/sql/sqlops.d.ts vendored
View File

@@ -39,6 +39,7 @@ declare module 'sqlops' {
export function registerDacFxServicesProvider(provider: DacFxServicesProvider): vscode.Disposable;
/**
* An [event](#Event) which fires when the specific flavor of a language used in DMP
* connections has changed. And example is for a SQL connection, the flavor changes
@@ -1324,6 +1325,7 @@ declare module 'sqlops' {
generateDeployPlan(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: TaskExecutionMode): Thenable<GenerateDeployPlanResult>;
}
// Security service interfaces ------------------------------------------------------------------------
export interface CredentialInfo {
id: number;

View File

@@ -1602,6 +1602,7 @@ declare module 'sqlops' {
AgentServicesProvider = 'AgentServicesProvider',
CapabilitiesProvider = 'CapabilitiesProvider',
DacFxServicesProvider = 'DacFxServicesProvider',
SchemaCompareServicesProvider = 'SchemaCompareServicesProvider',
ObjectExplorerNodeProvider = 'ObjectExplorerNodeProvider'
}

View File

@@ -292,6 +292,7 @@ export enum DataProviderType {
AgentServicesProvider = 'AgentServicesProvider',
CapabilitiesProvider = 'CapabilitiesProvider',
DacFxServicesProvider = 'DacFxServicesProvider',
SchemaCompareServicesProvider = 'SchemaCompareServicesProvider',
ObjectExplorerNodeProvider = 'ObjectExplorerNodeProvider'
}
@@ -547,3 +548,19 @@ export class ConnectionProfile {
return undefined;
}
}
export enum SchemaUpdateAction {
Delete = 0,
Change = 1,
Add = 2
}
export enum SchemaDifferenceType {
Object = 0,
Property = 1
}
export enum SchemaCompareEndpointType {
database = 0,
dacpac = 1
}

View File

@@ -167,6 +167,12 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
return rt;
}
$registerSchemaCompareServiceProvider(provider: azdata.SchemaCompareServicesProvider): vscode.Disposable {
let rt = this.registerProvider(provider, DataProviderType.SchemaCompareServicesProvider);
this._proxy.$registerSchemaCompareServicesProvider(provider.providerId, provider.handle);
return rt;
}
// Capabilities Discovery handlers
$getServerCapabilities(handle: number, client: azdata.DataProtocolClientCapabilities): Thenable<azdata.DataProtocolServerCapabilities> {
return this._resolveProvider<azdata.CapabilitiesProvider>(handle).getServerCapabilities(client);

View File

@@ -26,6 +26,7 @@ import { ISerializationService } from 'sql/platform/serialization/common/seriali
import { IFileBrowserService } from 'sql/platform/fileBrowser/common/interfaces';
import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
import { IDacFxService } from 'sql/platform/dacfx/common/dacFxService';
import { ISchemaCompareService } from 'sql/platform/schemaCompare/common/schemaCompareService';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
/**
@@ -57,6 +58,7 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
@ISerializationService private _serializationService: ISerializationService,
@IFileBrowserService private _fileBrowserService: IFileBrowserService,
@IDacFxService private _dacFxService: IDacFxService,
@ISchemaCompareService private _schemaCompareService: ISchemaCompareService,
) {
if (extHostContext) {
this._proxy = extHostContext.getProxy(SqlExtHostContext.ExtHostDataProtocol);
@@ -453,6 +455,20 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
return undefined;
}
public $registerSchemaCompareServicesProvider(providerId: string, handle: number): Promise<any> {
const self = this;
this._schemaCompareService.registerProvider(providerId, <azdata.SchemaCompareServicesProvider>{
schemaCompare(sourceEndpointInfo: azdata.SchemaCompareEndpointInfo, targetEndpointInfo: azdata.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode): Thenable<azdata.SchemaCompareResult> {
return self._proxy.$schemaCompare(handle, sourceEndpointInfo, targetEndpointInfo, taskExecutionMode);
},
schemaCompareGenerateScript(operationId: string, targetDatabaseName: string, scriptFilePath: string, taskExecutionMode: azdata.TaskExecutionMode): Thenable<azdata.ResultStatus> {
return self._proxy.$schemaCompareGenerateScript(handle, operationId, targetDatabaseName, scriptFilePath, taskExecutionMode);
}
});
return undefined;
}
// Connection Management handlers
public $onConnectionComplete(handle: number, connectionInfoSummary: azdata.ConnectionInfoSummary): void {
this._connectionManagementService.onConnectionComplete(handle, connectionInfoSummary);

View File

@@ -354,6 +354,10 @@ export function createApiFactory(
return extHostDataProvider.$registerDacFxServiceProvider(provider);
};
let registerSchemaCompareServicesProvider = (provider: azdata.SchemaCompareServicesProvider): vscode.Disposable => {
return extHostDataProvider.$registerSchemaCompareServiceProvider(provider);
};
// namespace: dataprotocol
const dataprotocol: typeof azdata.dataprotocol = {
registerBackupProvider,
@@ -371,6 +375,7 @@ export function createApiFactory(
registerAgentServicesProvider,
registerCapabilitiesServiceProvider,
registerDacFxServicesProvider,
registerSchemaCompareServicesProvider,
onDidChangeLanguageFlavor(listener: (e: azdata.DidChangeLanguageFlavorParams) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) {
return extHostDataProvider.onDidChangeLanguageFlavor(listener, thisArgs, disposables);
},
@@ -528,7 +533,10 @@ export function createApiFactory(
nb: nb,
AzureResource: sqlExtHostTypes.AzureResource,
TreeItem: sqlExtHostTypes.TreeItem,
extensions: extensions
extensions: extensions,
SchemaUpdateAction: sqlExtHostTypes.SchemaUpdateAction,
SchemaDifferenceType: sqlExtHostTypes.SchemaDifferenceType,
SchemaCompareEndpointType: sqlExtHostTypes.SchemaCompareEndpointType
};
},
@@ -754,6 +762,7 @@ export function createApiFactory(
return extHostDataProvider.$registerDacFxServiceProvider(provider);
};
// namespace: dataprotocol
const dataprotocol: typeof sqlops.dataprotocol = {
registerBackupProvider,

View File

@@ -455,6 +455,15 @@ export abstract class ExtHostDataProtocolShape {
*/
$generateDeployPlan(handle: number, packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode): Thenable<azdata.GenerateDeployPlanResult> { throw ni(); }
/**
* Schema compare
*/
$schemaCompare(handle: number, sourceEndpointInfo: azdata.SchemaCompareEndpointInfo, targetEndpointInfo: azdata.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode): Thenable<azdata.SchemaCompareResult> { throw ni(); }
/**
* Schema compare generate script
*/
$schemaCompareGenerateScript(handle: number, operationId: string, targetDatabaseName: string, scriptFilePath: string, taskExecutionMode: azdata.TaskExecutionMode): Thenable<azdata.ResultStatus> { throw ni(); }
}
/**
@@ -524,6 +533,7 @@ export interface MainThreadDataProtocolShape extends IDisposable {
$registerAdminServicesProvider(providerId: string, handle: number): Promise<any>;
$registerAgentServicesProvider(providerId: string, handle: number): Promise<any>;
$registerDacFxServicesProvider(providerId: string, handle: number): Promise<any>;
$registerSchemaCompareServicesProvider(providerId: string, handle: number): Promise<any>;
$unregisterProvider(handle: number): Promise<any>;
$onConnectionComplete(handle: number, connectionInfoSummary: azdata.ConnectionInfoSummary): void;
$onIntelliSenseCacheComplete(handle: number, connectionUri: string): void;

View File

@@ -195,6 +195,7 @@ import { IAdminService, AdminService } from 'sql/workbench/services/admin/common
import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces';
import { JobManagementService } from 'sql/platform/jobManagement/common/jobManagementService';
import { IDacFxService, DacFxService } from 'sql/platform/dacfx/common/dacFxService';
import { ISchemaCompareService, SchemaCompareService } from 'sql/platform/schemaCompare/common/schemaCompareService';
import { IBackupService } from 'sql/platform/backup/common/backupService';
import { BackupService } from 'sql/platform/backup/common/backupServiceImp';
import { IBackupUiService } from 'sql/workbench/services/backup/common/backupUiService';
@@ -269,6 +270,7 @@ registerSingleton(INotebookService, NotebookService);
registerSingleton(IAccountPickerService, AccountPickerService);
registerSingleton(IProfilerService, ProfilerService);
registerSingleton(IDacFxService, DacFxService);
registerSingleton(ISchemaCompareService, SchemaCompareService);
// {{SQL CARBON EDIT}} - End
//#region --- workbench parts