Add option for msdb reference in sql project (#10810)

* add msdb option

* add msdb dacpacs

* add tests

* fix system dacpac path for windows
This commit is contained in:
Kim Santiago
2020-06-09 10:12:39 -07:00
committed by GitHub
parent 02598621c3
commit 1aab8aba34
12 changed files with 112 additions and 25 deletions

View File

@@ -16,6 +16,9 @@ export const sqlDatabaseProjectExtensionId = 'microsoft.sql-database-projects';
export const mssqlExtensionId = 'microsoft.mssql';
export const dacpac = 'dacpac';
export const master = 'master';
export const masterDacpac = 'master.dacpac';
export const msdb = 'msdb';
export const msdbDacpac = 'msdb.dacpac';
export const MicrosoftDatatoolsSchemaSqlSql = 'Microsoft.Data.Tools.Schema.Sql.Sql';
export const databaseSchemaProvider = 'DatabaseSchemaProvider';
@@ -32,11 +35,13 @@ export const noString = localize('noString', "No");
export const extractTargetInput = localize('extractTargetInput', "Target for extraction:");
export const selectString = localize('selectString', "Select");
export const addDatabaseReferenceInput = localize('addDatabaseReferenceInput', "Add database reference for:");
export const systemDatabaseReferenceInput = localize('systemDatabaseReferenceInput', "System Database:");
export const databaseReferenceLocation = localize('databaseReferenceLocation', "Database location");
export const databaseReferenceSameDatabase = localize('databaseReferenceSameDatabase', "Same database");
export const databaseReferenceDifferentDabaseSameServer = localize('databaseReferenceDifferentDabaseSameServer', "Different database, same server");
export const databaseReferenceDatabaseName = localize('databaseReferenceDatabaseName', "Database name");
export const dacpacFiles = localize('dacpacFiles', "dacpac Files");
export const systemDatabase = localize('systemDatabase', "System Database");
export function newObjectNamePrompt(objectType: string) { return localize('newObjectNamePrompt', 'New {0} name:', objectType); }
// Deploy dialog strings
@@ -74,6 +79,7 @@ export const schemaCompareNotInstalled = localize('schemaCompareNotInstalled', "
export const buildDacpacNotFound = localize('buildDacpacNotFound', "Dacpac created from build not found");
export const updateProjectForRoundTrip = localize('updateProjectForRoundTrip', "To build this project, Azure Data Studio needs to update targets and references. If the project is created in SSDT, it will continue to work in both tools. Do you want Azure Data Studio to update the project?");
export const databaseReferenceTypeRequired = localize('databaseReferenceTypeRequired', "Database reference type is required for adding a reference to a database");
export const systemDatabaseReferenceRequired = localize('systemDatabaseReferenceRequired', "System database selection is required for adding a reference to a system database");
export const dacpacFileLocationRequired = localize('dacpacFileLocationRequired', "Dacpac file location is required for adding a reference to a database");
export const databaseLocationRequired = localize('databaseLocation', "Database location is required for adding a reference to a database");
export const databaseNameRequired = localize('databaseNameRequired', "Database name is required for adding a reference to a different database");

View File

@@ -16,7 +16,7 @@ import { IConnectionProfile, TaskExecutionMode } from 'azdata';
import { promises as fs } from 'fs';
import { ApiWrapper } from '../common/apiWrapper';
import { DeployDatabaseDialog } from '../dialogs/deployDatabaseDialog';
import { Project, DatabaseReferenceLocation } from '../models/project';
import { Project, DatabaseReferenceLocation, SystemDatabase, TargetPlatform } from '../models/project';
import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider';
import { FolderNode } from '../models/tree/fileFolderTreeItem';
import { IDeploymentProfile, IGenerateScriptProfile } from '../models/IDeploymentProfile';
@@ -306,8 +306,9 @@ export class ProjectsController {
const databaseReferenceType = await this.getDatabaseReferenceType();
// if master is selected, we know which dacpac needs to be added
if (databaseReferenceType === constants.master) {
await project.addMasterDatabaseReference();
if (databaseReferenceType === constants.systemDatabase) {
const systemDatabase = await this.getSystemDatabaseName(project);
await project.addSystemDatabaseReference(systemDatabase);
} else {
// get other information needed to add a reference to the dacpac
const dacpacFileLocation = await this.getDacpacFileLocation();
@@ -315,9 +316,9 @@ export class ProjectsController {
if (databaseLocation === DatabaseReferenceLocation.differentDatabaseSameServer) {
const databaseName = await this.getDatabaseName(dacpacFileLocation);
await project.addDatabaseReference(dacpacFileLocation, <DatabaseReferenceLocation>databaseLocation, databaseName);
await project.addDatabaseReference(dacpacFileLocation, <DatabaseReferenceLocation>databaseLocation, false, databaseName);
} else {
await project.addDatabaseReference(dacpacFileLocation, <DatabaseReferenceLocation>databaseLocation);
await project.addDatabaseReference(dacpacFileLocation, <DatabaseReferenceLocation>databaseLocation, false);
}
}
} catch (err) {
@@ -328,7 +329,7 @@ export class ProjectsController {
private async getDatabaseReferenceType(): Promise<string> {
let databaseReferenceOptions: QuickPickItem[] = [
{
label: constants.master
label: constants.systemDatabase
},
{
label: constants.dacpac
@@ -347,6 +348,33 @@ export class ProjectsController {
return input.label;
}
public async getSystemDatabaseName(project: Project): Promise<SystemDatabase> {
let databaseReferenceOptions: QuickPickItem[] = [
{
label: constants.master
}
];
// Azure dbs can only reference master
if (project.getProjectTargetPlatform() !== TargetPlatform.SqlAzureV12) {
databaseReferenceOptions.push(
{
label: constants.msdb
});
}
let input = await this.apiWrapper.showQuickPick(databaseReferenceOptions, {
canPickMany: false,
placeHolder: constants.systemDatabaseReferenceInput
});
if (!input) {
throw new Error(constants.systemDatabaseReferenceRequired);
}
return input.label === constants.master ? SystemDatabase.master : SystemDatabase.msdb;
}
private async getDacpacFileLocation(): Promise<Uri> {
let fileUris = await this.apiWrapper.showOpenDialog(
{

View File

@@ -144,14 +144,28 @@ export class Project {
}
/**
* Adds reference to the appropriate master dacpac to the project
* Adds reference to the appropriate system database dacpac to the project
*/
public async addMasterDatabaseReference(): Promise<void> {
const uri = this.getMasterDacpac();
this.addDatabaseReference(uri, DatabaseReferenceLocation.differentDatabaseSameServer, constants.master);
public async addSystemDatabaseReference(name: SystemDatabase): Promise<void> {
let uri: Uri;
let dbName: string;
if (name === SystemDatabase.master) {
uri = this.getSystemDacpacUri(constants.masterDacpac);
dbName = constants.master;
} else {
uri = this.getSystemDacpacUri(constants.msdbDacpac);
dbName = constants.msdb;
}
this.addDatabaseReference(uri, DatabaseReferenceLocation.differentDatabaseSameServer, true, dbName);
}
public getMasterDacpac(): Uri {
public getSystemDacpacUri(dacpac: string): Uri {
let version = this.getProjectTargetPlatform();
return Uri.parse(path.join('$(NETCoreTargetsPath)', 'SystemDacpacs', version, dacpac));
}
public getProjectTargetPlatform(): string {
// check for invalid DSP
if (this.projFileXmlDoc.getElementsByTagName(constants.DSP).length !== 1 || this.projFileXmlDoc.getElementsByTagName(constants.DSP)[0].childNodes.length !== 1) {
throw new Error(constants.invalidDataSchemaProvider);
@@ -166,12 +180,11 @@ export class Project {
version = version.substring(0, version.length - constants.databaseSchemaProvider.length);
// make sure version is valid
console.error(Object.values(TargetPlatform));
if (!Object.values(TargetPlatform).includes(version)) {
throw new Error(constants.invalidDataSchemaProvider);
}
return Uri.parse(path.join('$(NETCoreTargetsPath)', 'SystemDacpacs', version, 'master.dacpac'));
return version;
}
/**
@@ -179,8 +192,8 @@ export class Project {
* @param uri Uri of the dacpac
* @param databaseName name of the database
*/
public async addDatabaseReference(uri: Uri, databaseLocation: DatabaseReferenceLocation, databaseName?: string): Promise<void> {
let databaseReferenceEntry = new DatabaseReferenceProjectEntry(uri, databaseLocation, databaseName);
public async addDatabaseReference(uri: Uri, databaseLocation: DatabaseReferenceLocation, isSystemDatabase: boolean, databaseName?: string): Promise<void> {
let databaseReferenceEntry = new DatabaseReferenceProjectEntry(uri, databaseLocation, isSystemDatabase, databaseName);
await this.addToProjFile(databaseReferenceEntry);
}
@@ -231,7 +244,7 @@ export class Project {
private addDatabaseReferenceToProjFile(entry: DatabaseReferenceProjectEntry): void {
const referenceNode = this.projFileXmlDoc.createElement(constants.ArtifactReference);
referenceNode.setAttribute(constants.Condition, constants.NetCoreCondition);
referenceNode.setAttribute(constants.Include, entry.fsUri.fsPath);
referenceNode.setAttribute(constants.Include, entry.isSystemDatabase ? entry.fsUri.fsPath.substring(1) : entry.fsUri.fsPath); // need to remove the leading slash for system database path for build to work on Windows
let suppressMissingDependenciesErrorNode = this.projFileXmlDoc.createElement(constants.SuppressMissingDependenciesErrors);
let falseTextNode = this.projFileXmlDoc.createTextNode('False');
@@ -347,7 +360,7 @@ export class ProjectEntry {
* Represents a database reference entry in a project file
*/
class DatabaseReferenceProjectEntry extends ProjectEntry {
constructor(uri: Uri, public databaseLocation: DatabaseReferenceLocation, public name?: string) {
constructor(uri: Uri, public databaseLocation: DatabaseReferenceLocation, public isSystemDatabase: boolean, public name?: string) {
super(uri, '', EntryType.DatabaseReference);
}
}
@@ -373,3 +386,8 @@ export enum TargetPlatform {
Sql150 = '150',
SqlAzureV12 = 'AzureV12'
}
export enum SystemDatabase {
master,
msdb
}

View File

@@ -90,16 +90,33 @@ describe('Project: sqlproj content operations', function (): void {
const project = new Project(projFilePath);
await project.readProjFile();
let uri = project.getMasterDacpac();
should.equal(uri.fsPath, Uri.parse(path.join('$(NETCoreTargetsPath)', 'SystemDacpacs', '130', 'master.dacpac')).fsPath);
let uri = project.getSystemDacpacUri(constants.masterDacpac);
should.equal(uri.fsPath, Uri.parse(path.join('$(NETCoreTargetsPath)', 'SystemDacpacs', '130', constants.masterDacpac)).fsPath);
project.changeDSP(TargetPlatform.Sql150.toString());
uri = project.getMasterDacpac();
should.equal(uri.fsPath, Uri.parse(path.join('$(NETCoreTargetsPath)', 'SystemDacpacs', '150', 'master.dacpac')).fsPath);
uri = project.getSystemDacpacUri(constants.masterDacpac);
should.equal(uri.fsPath, Uri.parse(path.join('$(NETCoreTargetsPath)', 'SystemDacpacs', '150', constants.masterDacpac)).fsPath);
project.changeDSP(TargetPlatform.SqlAzureV12.toString());
uri = project.getMasterDacpac();
should.equal(uri.fsPath, Uri.parse(path.join('$(NETCoreTargetsPath)', 'SystemDacpacs', 'AzureV12', 'master.dacpac')).fsPath);
uri = project.getSystemDacpacUri(constants.masterDacpac);
should.equal(uri.fsPath, Uri.parse(path.join('$(NETCoreTargetsPath)', 'SystemDacpacs', 'AzureV12',constants.masterDacpac)).fsPath);
});
it('Should choose correct msdb dacpac', async function(): Promise<void> {
projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
const project = new Project(projFilePath);
await project.readProjFile();
let uri = project.getSystemDacpacUri(constants.msdbDacpac);
should.equal(uri.fsPath, Uri.parse(path.join('$(NETCoreTargetsPath)', 'SystemDacpacs', '130', constants.msdbDacpac)).fsPath);
project.changeDSP(TargetPlatform.Sql150.toString());
uri = project.getSystemDacpacUri(constants.msdbDacpac);
should.equal(uri.fsPath, Uri.parse(path.join('$(NETCoreTargetsPath)', 'SystemDacpacs', '150', constants.msdbDacpac)).fsPath);
project.changeDSP(TargetPlatform.SqlAzureV12.toString());
uri = project.getSystemDacpacUri(constants.msdbDacpac);
should.equal(uri.fsPath, Uri.parse(path.join('$(NETCoreTargetsPath)', 'SystemDacpacs', 'AzureV12', constants.msdbDacpac)).fsPath);
});
it('Should throw error when choosing correct master dacpac if invalid DSP', async function(): Promise<void> {
@@ -108,7 +125,7 @@ describe('Project: sqlproj content operations', function (): void {
await project.readProjFile();
project.changeDSP('invalidPlatform');
await testUtils.shouldThrowSpecificError(async () => await project.getMasterDacpac(), constants.invalidDataSchemaProvider);
await testUtils.shouldThrowSpecificError(async () => await project.getSystemDacpacUri(constants.masterDacpac), constants.invalidDataSchemaProvider);
});
});

View File

@@ -18,7 +18,7 @@ import { SqlDatabaseProjectTreeViewProvider } from '../controllers/databaseProje
import { ProjectsController } from '../controllers/projectController';
import { promises as fs } from 'fs';
import { createContext, TestContext } from './testContext';
import { Project } from '../models/project';
import { Project, SystemDatabase } from '../models/project';
import { DeployDatabaseDialog } from '../dialogs/deployDatabaseDialog';
import { ApiWrapper } from '../common/apiWrapper';
import { IDeploymentProfile, IGenerateScriptProfile } from '../models/IDeploymentProfile';
@@ -255,6 +255,24 @@ describe('ProjectsController: add database reference operations', function (): v
const projController = new ProjectsController(testContext.apiWrapper.object, new SqlDatabaseProjectTreeViewProvider());
await testUtils.shouldThrowSpecificError(async () => await projController.addDatabaseReference(new Project('FakePath')), constants.databaseNameRequired);
});
it('Should return the correct system database', async function (): Promise<void> {
const projController = new ProjectsController(testContext.apiWrapper.object, new SqlDatabaseProjectTreeViewProvider());
const projFilePath = await testUtils.createTestSqlProjFile(baselines.openProjectFileBaseline);
const project: Project = new Project(projFilePath);
await project.readProjFile();
testContext.apiWrapper.setup(x => x.showQuickPick(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ label: constants.master }));
let systemDb = await projController.getSystemDatabaseName(project);
should.equal(systemDb, SystemDatabase.master);
testContext.apiWrapper.setup(x => x.showQuickPick(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ label: constants.msdb }));
systemDb = await projController.getSystemDatabaseName(project);
should.equal(systemDb, SystemDatabase.msdb);
testContext.apiWrapper.setup(x => x.showQuickPick(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined));
await testUtils.shouldThrowSpecificError(async () => await projController.getSystemDatabaseName(project), constants.systemDatabaseReferenceRequired);
});
});
describe('ProjectsController: round trip feature with SSDT', function (): void {