Add reference to another sql project (#12186)

* add projects to add database reference dialog

* able to add project references

* check for circular dependency

* only allow adding reference to project in the same workspace

* fix location dropdown when project reference is enabled

* add tests

* more tests

* cleanup

* fix flakey test

* addressing comments
This commit is contained in:
Kim Santiago
2020-09-10 17:44:39 -07:00
committed by GitHub
parent 7df132b307
commit 133ff73a43
11 changed files with 380 additions and 70 deletions

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DatabaseReferenceLocation, SystemDatabase } from './project';
import { SystemDatabase } from './project';
import { Uri } from 'vscode';
export interface IDatabaseReferenceSettings {
@@ -16,9 +16,17 @@ export interface ISystemDatabaseReferenceSettings extends IDatabaseReferenceSett
}
export interface IDacpacReferenceSettings extends IDatabaseReferenceSettings {
databaseLocation: DatabaseReferenceLocation;
dacpacFileLocation: Uri;
databaseVariable?: string;
serverName?: string;
serverVariable?: string;
}
export interface IProjectReferenceSettings extends IDatabaseReferenceSettings {
projectRelativePath: Uri | undefined;
projectName: string;
projectGuid: string;
databaseVariable?: string;
serverName?: string;
serverVariable?: string;
}

View File

@@ -14,7 +14,7 @@ import * as templates from '../templates/templates';
import { Uri, window } from 'vscode';
import { promises as fs } from 'fs';
import { DataSource } from './dataSources/dataSources';
import { ISystemDatabaseReferenceSettings, IDacpacReferenceSettings } from './IDatabaseReferenceSettings';
import { ISystemDatabaseReferenceSettings, IDacpacReferenceSettings, IProjectReferenceSettings } from './IDatabaseReferenceSettings';
/**
* Class representing a Project, and providing functions for operating on it
@@ -22,6 +22,7 @@ import { ISystemDatabaseReferenceSettings, IDacpacReferenceSettings } from './ID
export class Project {
public projectFilePath: string;
public projectFileName: string;
public projectGuid: string | undefined;
public files: FileProjectEntry[] = [];
public dataSources: DataSource[] = [];
public importedTargets: string[] = [];
@@ -61,6 +62,9 @@ export class Project {
const projFileText = await fs.readFile(this.projectFilePath);
this.projFileXmlDoc = new xmldom.DOMParser().parseFromString(projFileText.toString());
// get projectGUID
this.projectGuid = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ProjectGuid)[0].childNodes[0].nodeValue;
// find all folders and files to include
for (let ig = 0; ig < this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup).length; ig++) {
const itemGroup = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup)[ig];
@@ -128,13 +132,12 @@ export class Project {
const name = nameNodes.length === 1 ? nameNodes[0].childNodes[0].nodeValue : undefined;
const suppressMissingDependenciesErrorNode = references[r].getElementsByTagName(constants.SuppressMissingDependenciesErrors);
const suppressMissingDependences = suppressMissingDependenciesErrorNode[0].childNodes[0].nodeValue === true ?? false;
const suppressMissingDependencies = suppressMissingDependenciesErrorNode[0].childNodes[0].nodeValue === true ?? false;
this.databaseReferences.push(new DacpacReferenceProjectEntry({
dacpacFileLocation: Uri.file(utils.getPlatformSafeFileEntryPath(filepath)),
databaseLocation: name ? DatabaseReferenceLocation.differentDatabaseSameServer : DatabaseReferenceLocation.sameDatabase,
databaseName: name,
suppressMissingDependenciesErrors: suppressMissingDependences
suppressMissingDependenciesErrors: suppressMissingDependencies
}));
}
}
@@ -151,9 +154,14 @@ export class Project {
const name = nameNodes[0].childNodes[0].nodeValue;
const suppressMissingDependenciesErrorNode = projectReferences[r].getElementsByTagName(constants.SuppressMissingDependenciesErrors);
const suppressMissingDependences = suppressMissingDependenciesErrorNode[0].childNodes[0].nodeValue === true ?? false;
const suppressMissingDependencies = suppressMissingDependenciesErrorNode[0].childNodes[0].nodeValue === true ?? false;
this.databaseReferences.push(new SqlProjectReferenceProjectEntry(Uri.file(utils.getPlatformSafeFileEntryPath(filepath)), name, suppressMissingDependences));
this.databaseReferences.push(new SqlProjectReferenceProjectEntry({
projectRelativePath: Uri.file(utils.getPlatformSafeFileEntryPath(filepath)),
projectName: name,
projectGuid: '', // don't care when just reading project as a reference
suppressMissingDependenciesErrors: suppressMissingDependencies
}));
}
}
@@ -374,6 +382,16 @@ export class Project {
await this.addToProjFile(databaseReferenceEntry);
}
/**
* Adds reference to a another project in the workspace
* @param uri Uri of the dacpac
* @param databaseName name of the database
*/
public async addProjectReference(settings: IProjectReferenceSettings): Promise<void> {
const projectReferenceEntry = new SqlProjectReferenceProjectEntry(settings);
await this.addToProjFile(projectReferenceEntry);
}
/**
* Adds a SQLCMD variable to the project
* @param name name of the variable
@@ -529,10 +547,15 @@ export class Project {
throw new Error(constants.databaseReferenceAlreadyExists);
}
const isSystemDatabaseProjectEntry = (<SystemDatabaseReferenceProjectEntry>entry).ssdtUri;
if (isSystemDatabaseProjectEntry) {
if (entry instanceof SystemDatabaseReferenceProjectEntry) {
this.addSystemDatabaseReferenceToProjFile(<SystemDatabaseReferenceProjectEntry>entry);
} else if (entry instanceof SqlProjectReferenceProjectEntry) {
const referenceNode = this.projFileXmlDoc.createElement(constants.ProjectReference);
referenceNode.setAttribute(constants.Include, entry.pathForSqlProj());
this.addProjectReferenceChildren(referenceNode, <SqlProjectReferenceProjectEntry>entry);
this.addDatabaseReferenceChildren(referenceNode, entry);
this.findOrCreateItemGroup(constants.ProjectReference).appendChild(referenceNode);
this.databaseReferences.push(entry);
} else {
const referenceNode = this.projFileXmlDoc.createElement(constants.ArtifactReference);
referenceNode.setAttribute(constants.Include, entry.pathForSqlProj());
@@ -579,6 +602,26 @@ export class Project {
}
}
private addProjectReferenceChildren(referenceNode: any, entry: SqlProjectReferenceProjectEntry): void {
// project name
const nameElement = this.projFileXmlDoc.createElement(constants.Name);
const nameTextNode = this.projFileXmlDoc.createTextNode(entry.projectName);
nameElement.appendChild(nameTextNode);
referenceNode.appendChild(nameElement);
// add project guid
const projectElement = this.projFileXmlDoc.createElement(constants.Project);
const projectGuidTextNode = this.projFileXmlDoc.createTextNode(entry.projectGuid);
projectElement.appendChild(projectGuidTextNode);
referenceNode.appendChild(projectElement);
// add Private (not sure what this is for)
const privateElement = this.projFileXmlDoc.createElement(constants.Private);
const privateTextNode = this.projFileXmlDoc.createTextNode(constants.True);
privateElement.appendChild(privateTextNode);
referenceNode.appendChild(privateElement);
}
public addSqlCmdVariableToProjFile(entry: SqlCmdVariableProjectEntry): void {
// Remove any entries with the same variable name. It'll be replaced with a new one
this.removeFromProjFile(entry);
@@ -821,7 +864,6 @@ export interface IDatabaseReferenceProjectEntry extends FileProjectEntry {
}
export class DacpacReferenceProjectEntry extends FileProjectEntry implements IDatabaseReferenceProjectEntry {
databaseLocation: DatabaseReferenceLocation;
databaseVariableLiteralValue?: string;
databaseSqlCmdVariable?: string;
serverName?: string;
@@ -830,7 +872,6 @@ export class DacpacReferenceProjectEntry extends FileProjectEntry implements IDa
constructor(settings: IDacpacReferenceSettings) {
super(settings.dacpacFileLocation, '', EntryType.DatabaseReference);
this.databaseLocation = settings.databaseLocation;
this.databaseSqlCmdVariable = settings.databaseVariable;
this.databaseVariableLiteralValue = settings.databaseName;
this.serverName = settings.serverName;
@@ -870,13 +911,33 @@ class SystemDatabaseReferenceProjectEntry extends FileProjectEntry implements ID
}
export class SqlProjectReferenceProjectEntry extends FileProjectEntry implements IDatabaseReferenceProjectEntry {
constructor(uri: Uri, public projectName: string, public suppressMissingDependenciesErrors: boolean) {
super(uri, '', EntryType.DatabaseReference);
projectName: string;
projectGuid: string;
databaseVariableLiteralValue?: string;
databaseSqlCmdVariable?: string;
serverName?: string;
serverSqlCmdVariable?: string;
suppressMissingDependenciesErrors: boolean;
constructor(settings: IProjectReferenceSettings) {
super(settings.projectRelativePath!, '', EntryType.DatabaseReference);
this.projectName = settings.projectName;
this.projectGuid = settings.projectGuid;
this.databaseSqlCmdVariable = settings.databaseVariable;
this.databaseVariableLiteralValue = settings.databaseName;
this.serverName = settings.serverName;
this.serverSqlCmdVariable = settings.serverVariable;
this.suppressMissingDependenciesErrors = settings.suppressMissingDependenciesErrors;
}
public get databaseName(): string {
return this.projectName;
}
public pathForSqlProj(): string {
// need to remove the leading slash from path for build to work on Windows
return utils.convertSlashesForSqlProj(this.fsUri.path.substring(1));
}
}
export class SqlCmdVariableProjectEntry extends ProjectEntry {