mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-14 01:25:37 -05:00
Add sql proj schema compare for dacpac (#10388)
* 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
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 = {
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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); });
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user