Skip to content

Commit

Permalink
Add sql proj schema compare for dacpac (microsoft#10388)
Browse files Browse the repository at this point in the history
* add support for schema compare to specify source dacpac

* add build and dacpac produced from build

* check if dacpac exists

* add tests

* move exists check code to utils

* fix test run failing
  • Loading branch information
kisantia authored May 15, 2020
1 parent ef4ab4a commit b83279c
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default class MainController implements vscode.Disposable {
}

private initializeSchemaCompareDialog(): void {
azdata.tasks.registerTask('schemaCompare.start', (profile: azdata.IConnectionProfile) => new SchemaCompareMainWindow(null, this.extensionContext).start(profile));
vscode.commands.registerCommand('schemaCompare.start', (context: any) => new SchemaCompareMainWindow(null, this.extensionContext).start(context));
}

public dispose(): void {
Expand Down
15 changes: 15 additions & 0 deletions extensions/schema-compare/src/schemaCompareMainWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,14 @@ export class SchemaCompareMainWindow {
this.editor = azdata.workspace.createModelViewEditor(loc.SchemaCompareLabel, { retainContextWhenHidden: true, supportsSave: true, resourceName: schemaCompareResourceName });
}

// schema compare can get started with three contexts for the source:
// 1. undefined
// 2. connection profile
// 3. dacpac
public async start(context: any) {
// if schema compare was launched from a db, set that as the source
let profile = context ? <azdata.IConnectionProfile>context.connectionProfile : undefined;
let sourceDacpac = context as string;
if (profile) {
let ownerUri = await azdata.connection.getUriForConnection((profile.id));
this.sourceEndpointInfo = {
Expand All @@ -91,6 +96,16 @@ export class SchemaCompareMainWindow {
packageFilePath: '',
connectionDetails: undefined
};
} else if (sourceDacpac) {
this.sourceEndpointInfo = {
endpointType: mssql.SchemaCompareEndpointType.Dacpac,
serverDisplayName: '',
serverName: '',
databaseName: '',
ownerUri: '',
packageFilePath: sourceDacpac,
connectionDetails: undefined
};
}

this.editor.registerContent(async view => {
Expand Down
42 changes: 42 additions & 0 deletions extensions/schema-compare/src/test/schemaCompare.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,46 @@ describe('SchemaCompareResult.start', function (): void {
should(result.getComparisonResult() !== undefined);
should(result.getComparisonResult().operationId === 'Test Operation Id');
});

it('Should start with the source as undefined', async function (): Promise<void> {
let sc = new SchemaCompareTestService();

let result = new SchemaCompareMainWindow(sc, mockExtensionContext.object);
await result.start(undefined);
let promise = new Promise(resolve => setTimeout(resolve, 5000)); // to ensure comparison result view is initialized
await promise;

should.equal(result.sourceEndpointInfo, undefined);
should.equal(result.targetEndpointInfo, undefined);
});

it('Should start with the source as database', async function (): Promise<void> {
let sc = new SchemaCompareTestService();

let result = new SchemaCompareMainWindow(sc, mockExtensionContext.object);
await result.start({connectionProfile: mockConnectionProfile});
let promise = new Promise(resolve => setTimeout(resolve, 5000)); // to ensure comparison result view is initialized
await promise;

should.notEqual(result.sourceEndpointInfo, undefined);
should.equal(result.sourceEndpointInfo.endpointType, mssql.SchemaCompareEndpointType.Database);
should.equal(result.sourceEndpointInfo.serverName, mockConnectionProfile.serverName);
should.equal(result.sourceEndpointInfo.databaseName, mockConnectionProfile.databaseName);
should.equal(result.targetEndpointInfo, undefined);
});

it('Should start with the source as dacpac.', async function (): Promise<void> {
let sc = new SchemaCompareTestService();

let result = new SchemaCompareMainWindow(sc, mockExtensionContext.object);
const dacpacPath = 'C:\\users\\test\\test.dacpac';
await result.start(dacpacPath);
let promise = new Promise(resolve => setTimeout(resolve, 5000)); // to ensure comparison result view is initialized
await promise;

should.notEqual(result.sourceEndpointInfo, undefined);
should.equal(result.sourceEndpointInfo.endpointType, mssql.SchemaCompareEndpointType.Dacpac);
should.equal(result.sourceEndpointInfo.packageFilePath, dacpacPath);
should.equal(result.targetEndpointInfo, undefined);
});
});
3 changes: 3 additions & 0 deletions extensions/sql-database-projects/src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const localize = nls.loadMessageBundle();
export const dataSourcesFileName = 'datasources.json';
export const sqlprojExtension = '.sqlproj';
export const initialCatalogSetting = 'Initial Catalog';
export const schemaCompareExtensionId = 'microsoft.schema-compare';

// UI Strings

Expand Down Expand Up @@ -51,6 +52,8 @@ export const unknownDataSourceType = localize('unknownDataSourceType', "Unknown
export const invalidSqlConnectionString = localize('invalidSqlConnectionString', "Invalid SQL connection string");
export const projectNameRequired = localize('projectNameRequired', "Name is required to create a new database project.");
export const projectLocationRequired = localize('projectLocationRequired', "Location is required to create a new database project.");
export const schemaCompareNotInstalled = localize('schemaCompareNotInstalled', "Schema compare extension installation is required to run schema compare");
export const buildDacpacNotFound = localize('buildDacpacNotFound', "Dacpac created from build not found");
export function projectAlreadyOpened(path: string) { return localize('projectAlreadyOpened', "Project '{0}' is already opened.", path); }
export function projectAlreadyExists(name: string, path: string) { return localize('projectAlreadyExists', "A project named {0} already exists in {1}.", name, path); }
export function mssqlNotFound(mssqlConfigDir: string) { return localize('mssqlNotFound', "Could not get mssql extension's install location at {0}", mssqlConfigDir); }
Expand Down
14 changes: 14 additions & 0 deletions extensions/sql-database-projects/src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import * as vscode from 'vscode';
import * as os from 'os';
import { promises as fs } from 'fs';

/**
* Consolidates on the error message string
*/
Expand Down Expand Up @@ -74,3 +76,15 @@ export function getSafeNonWindowsPath(filePath: string): string {
filePath = filePath.split('\\').join('/').split('"').join('');
return '"' + filePath + '"';
}

/**
* Checks if the folder or file exists @param path path of the folder/file
*/
export async function exists(path: string): Promise<boolean> {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export default class MainController implements Disposable {

this.apiWrapper.registerCommand('sqlDatabaseProjects.build', async (node: BaseProjectTreeItem) => { await this.projectsController.buildProject(node); });
this.apiWrapper.registerCommand('sqlDatabaseProjects.deploy', async (node: BaseProjectTreeItem) => { await this.projectsController.deploy(node); });
this.apiWrapper.registerCommand('sqlDatabaseProjects.schemaCompare', async (node: BaseProjectTreeItem) => { await this.projectsController.schemaCompare(node); });
this.apiWrapper.registerCommand('sqlDatabaseProjects.import', async (node: BaseProjectTreeItem) => { await this.projectsController.import(node); });

this.apiWrapper.registerCommand('sqlDatabaseProjects.newScript', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPromptFromNode(node, templates.script); });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,27 @@ export class ProjectsController {
deployDatabaseDialog.openDialog();
}

public async schemaCompare(treeNode: BaseProjectTreeItem): Promise<void> {
// check if schema compare extension is installed
if (this.apiWrapper.getExtension(constants.schemaCompareExtensionId)) {
// build project
await this.buildProject(treeNode);

// start schema compare with the dacpac produced from build
const project = this.getProjectContextFromTreeNode(treeNode);
const dacpacPath = path.join(project.projectFolderPath, 'bin', 'Debug', `${project.projectFileName}.dacpac`);

// check that dacpac exists
if (await utils.exists(dacpacPath)) {
this.apiWrapper.executeCommand('schemaCompare.start', dacpacPath);
} else {
this.apiWrapper.showErrorMessage(constants.buildDacpacNotFound);
}
} else {
this.apiWrapper.showErrorMessage(constants.schemaCompareNotInstalled);
}
}

public async import(treeNode: BaseProjectTreeItem) {
const project = this.getProjectContextFromTreeNode(treeNode);
await this.apiWrapper.showErrorMessage(`Import not yet implemented: ${project.projectFilePath}`); // TODO
Expand Down

0 comments on commit b83279c

Please sign in to comment.