Adding External Streaming Job I/O validation (#13195)

* Added Tools Service call for ValidateStreamingJob

* Partial addition of ESJ

* adding test mocks

* Validation working

* Modifying command visibility logic to submatch ESJs in addition to files

* Changed string literal to constant, corrected attribute order

* Added tests

* correcting casing that's causing test failures on linux

* Swapping Thenable for Promise

* excluded validate from command palette
This commit is contained in:
Benjin Dubishar
2020-11-02 19:02:20 -08:00
committed by GitHub
parent ba80000e27
commit 342ff47e51
20 changed files with 198 additions and 37 deletions

View File

@@ -10,8 +10,9 @@ export const deployOperationId = 'deploy dacpac';
export const extractOperationId = 'extract dacpac'; export const extractOperationId = 'extract dacpac';
export const exportOperationId = 'export bacpac'; export const exportOperationId = 'export bacpac';
export const importOperationId = 'import bacpac'; export const importOperationId = 'import bacpac';
export const generateScript = 'genenrate script'; export const generateScript = 'generate script';
export const generateDeployPlan = 'genenrate deploy plan'; export const generateDeployPlan = 'generate deploy plan';
export const validateStreamingJob = 'validate streaming job';
export class DacFxTestService implements mssql.IDacFxService { export class DacFxTestService implements mssql.IDacFxService {
dacfxResult: mssql.DacFxResult = { dacfxResult: mssql.DacFxResult = {
@@ -22,31 +23,31 @@ export class DacFxTestService implements mssql.IDacFxService {
constructor() { constructor() {
} }
exportBacpac(databaseName: string, packageFilePath: string, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode): Thenable<mssql.DacFxResult> { exportBacpac(databaseName: string, packageFilePath: string, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode): Promise<mssql.DacFxResult> {
this.dacfxResult.operationId = exportOperationId; this.dacfxResult.operationId = exportOperationId;
return Promise.resolve(this.dacfxResult); return Promise.resolve(this.dacfxResult);
} }
importBacpac(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode): Thenable<mssql.DacFxResult> { importBacpac(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode): Promise<mssql.DacFxResult> {
this.dacfxResult.operationId = importOperationId; this.dacfxResult.operationId = importOperationId;
return Promise.resolve(this.dacfxResult); return Promise.resolve(this.dacfxResult);
} }
extractDacpac(databaseName: string, packageFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode): Thenable<mssql.DacFxResult> { extractDacpac(databaseName: string, packageFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode): Promise<mssql.DacFxResult> {
this.dacfxResult.operationId = extractOperationId; this.dacfxResult.operationId = extractOperationId;
return Promise.resolve(this.dacfxResult); return Promise.resolve(this.dacfxResult);
} }
importDatabaseProject(databaseName: string, targetFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, extractTarget: mssql.ExtractTarget, taskExecutionMode: azdata.TaskExecutionMode): Thenable<mssql.DacFxResult> { importDatabaseProject(databaseName: string, targetFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, extractTarget: mssql.ExtractTarget, taskExecutionMode: azdata.TaskExecutionMode): Promise<mssql.DacFxResult> {
this.dacfxResult.operationId = importOperationId; this.dacfxResult.operationId = importOperationId;
return Promise.resolve(this.dacfxResult); return Promise.resolve(this.dacfxResult);
} }
deployDacpac(packageFilePath: string, databaseName: string, upgradeExisting: boolean, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode, sqlCommandVariableValues?: Record<string, string>): Thenable<mssql.DacFxResult> { deployDacpac(packageFilePath: string, databaseName: string, upgradeExisting: boolean, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode, sqlCommandVariableValues?: Record<string, string>): Promise<mssql.DacFxResult> {
this.dacfxResult.operationId = deployOperationId; this.dacfxResult.operationId = deployOperationId;
return Promise.resolve(this.dacfxResult); return Promise.resolve(this.dacfxResult);
} }
generateDeployScript(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode, sqlCommandVariableValues?: Record<string, string>): Thenable<mssql.DacFxResult> { generateDeployScript(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode, sqlCommandVariableValues?: Record<string, string>): Promise<mssql.DacFxResult> {
this.dacfxResult.operationId = generateScript; this.dacfxResult.operationId = generateScript;
return Promise.resolve(this.dacfxResult); return Promise.resolve(this.dacfxResult);
} }
generateDeployPlan(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode): Thenable<mssql.GenerateDeployPlanResult> { generateDeployPlan(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode): Promise<mssql.GenerateDeployPlanResult> {
this.dacfxResult.operationId = generateDeployPlan; this.dacfxResult.operationId = generateDeployPlan;
const deployPlan: mssql.GenerateDeployPlanResult = { const deployPlan: mssql.GenerateDeployPlanResult = {
operationId: generateDeployPlan, operationId: generateDeployPlan,
@@ -56,7 +57,7 @@ export class DacFxTestService implements mssql.IDacFxService {
}; };
return Promise.resolve(deployPlan); return Promise.resolve(deployPlan);
} }
getOptionsFromProfile(profilePath: string): Thenable<mssql.DacFxOptionsResult> { getOptionsFromProfile(profilePath: string): Promise<mssql.DacFxOptionsResult> {
const optionsResult: mssql.DacFxOptionsResult = { const optionsResult: mssql.DacFxOptionsResult = {
success: true, success: true,
errorMessage: '', errorMessage: '',
@@ -144,4 +145,12 @@ export class DacFxTestService implements mssql.IDacFxService {
return Promise.resolve(optionsResult); return Promise.resolve(optionsResult);
} }
validateStreamingJob(packageFilePath: string, createStreamingJobTsql: string): Promise<mssql.ValidateStreamingJobResult> {
this.dacfxResult.operationId = validateStreamingJob;
const streamingJobValidationResult: mssql.ValidateStreamingJobResult = {
success: true,
errorMessage: ''
};
return Promise.resolve(streamingJobValidationResult);
}
} }

View File

@@ -476,6 +476,12 @@ export interface GenerateDeployPlanParams {
export interface GetOptionsFromProfileParams { export interface GetOptionsFromProfileParams {
profilePath: string; profilePath: string;
} }
export interface ValidateStreamingJobParams {
packageFilePath: string,
createStreamingJobTsql: string
}
export namespace ExportRequest { export namespace ExportRequest {
export const type = new RequestType<ExportParams, mssql.DacFxResult, void, void>('dacfx/export'); export const type = new RequestType<ExportParams, mssql.DacFxResult, void, void>('dacfx/export');
} }
@@ -503,7 +509,12 @@ export namespace GenerateDeployPlanRequest {
export namespace GetOptionsFromProfileRequest { export namespace GetOptionsFromProfileRequest {
export const type = new RequestType<GetOptionsFromProfileParams, mssql.DacFxOptionsResult, void, void>('dacfx/getOptionsFromProfile'); export const type = new RequestType<GetOptionsFromProfileParams, mssql.DacFxOptionsResult, void, void>('dacfx/getOptionsFromProfile');
} }
// ------------------------------- < DacFx > ------------------------------------
export namespace ValidateStreamingJobRequest {
export const type = new RequestType<ValidateStreamingJobParams, mssql.ValidateStreamingJobResult, void, void>('dacfx/validateStreamingJob');
}
// ------------------------------- </ DacFx > ------------------------------------
// ------------------------------- <CMS> ---------------------------------------- // ------------------------------- <CMS> ----------------------------------------

View File

@@ -119,4 +119,15 @@ export class DacFxService implements mssql.IDacFxService {
} }
); );
} }
public validateStreamingJob(packageFilePath: string, createStreamingJobTsql: string): Thenable<mssql.ValidateStreamingJobResult> {
const params: contracts.ValidateStreamingJobParams = { packageFilePath: packageFilePath, createStreamingJobTsql: createStreamingJobTsql };
return this.client.sendRequest(contracts.ValidateStreamingJobRequest.type, params).then(
undefined,
e => {
this.client.logFailedRequest(contracts.ValidateStreamingJobRequest.type, e);
return Promise.resolve(undefined);
}
);
}
} }

View File

@@ -339,6 +339,7 @@ export interface IDacFxService {
generateDeployScript(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode, sqlCommandVariableValues?: Record<string, string>, deploymentOptions?: DeploymentOptions): Thenable<DacFxResult>; generateDeployScript(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode, sqlCommandVariableValues?: Record<string, string>, deploymentOptions?: DeploymentOptions): Thenable<DacFxResult>;
generateDeployPlan(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode): Thenable<GenerateDeployPlanResult>; generateDeployPlan(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: azdata.TaskExecutionMode): Thenable<GenerateDeployPlanResult>;
getOptionsFromProfile(profilePath: string): Thenable<DacFxOptionsResult>; getOptionsFromProfile(profilePath: string): Thenable<DacFxOptionsResult>;
validateStreamingJob(packageFilePath: string, createStreamingJobTsql: string): Thenable<ValidateStreamingJobResult>;
} }
export interface DacFxResult extends azdata.ResultStatus { export interface DacFxResult extends azdata.ResultStatus {
@@ -353,6 +354,9 @@ export interface DacFxOptionsResult extends azdata.ResultStatus {
deploymentOptions: DeploymentOptions; deploymentOptions: DeploymentOptions;
} }
export interface ValidateStreamingJobResult extends azdata.ResultStatus {
}
export interface ExportParams { export interface ExportParams {
databaseName: string; databaseName: string;
packageFilePath: string; packageFilePath: string;

View File

@@ -95,6 +95,11 @@
"title": "%sqlDatabaseProjects.newView%", "title": "%sqlDatabaseProjects.newView%",
"category": "%sqlDatabaseProjects.displayName%" "category": "%sqlDatabaseProjects.displayName%"
}, },
{
"command": "sqlDatabaseProjects.newExternalStreamingJob",
"title": "%sqlDatabaseProjects.newExternalStreamingJob%",
"category": "%sqlDatabaseProjects.displayName%"
},
{ {
"command": "sqlDatabaseProjects.newStoredProcedure", "command": "sqlDatabaseProjects.newStoredProcedure",
"title": "%sqlDatabaseProjects.newStoredProcedure%", "title": "%sqlDatabaseProjects.newStoredProcedure%",
@@ -151,6 +156,11 @@
"title": "%sqlDatabaseProjects.addDatabaseReference%", "title": "%sqlDatabaseProjects.addDatabaseReference%",
"category": "%sqlDatabaseProjects.displayName%" "category": "%sqlDatabaseProjects.displayName%"
}, },
{
"command": "sqlDatabaseProjects.validateExternalStreamingJob",
"title": "%sqlDatabaseProjects.validateExternalStreamingJob%",
"category": "%sqlDatabaseProjects.displayName%"
},
{ {
"command": "sqlDatabaseProjects.openContainingFolder", "command": "sqlDatabaseProjects.openContainingFolder",
"title": "%sqlDatabaseProjects.openContainingFolder%", "title": "%sqlDatabaseProjects.openContainingFolder%",
@@ -199,6 +209,10 @@
"command": "sqlDatabaseProjects.newView", "command": "sqlDatabaseProjects.newView",
"when": "false" "when": "false"
}, },
{
"command": "sqlDatabaseProjects.newExternalStreamingJob",
"when": "false"
},
{ {
"command": "sqlDatabaseProjects.newStoredProcedure", "command": "sqlDatabaseProjects.newStoredProcedure",
"when": "false" "when": "false"
@@ -238,6 +252,10 @@
"command": "sqlDatabaseProjects.addDatabaseReference", "command": "sqlDatabaseProjects.addDatabaseReference",
"when": "false" "when": "false"
}, },
{
"command": "sqlDatabaseProjects.validateExternalStreamingJob",
"when": "false"
},
{ {
"command": "sqlDatabaseProjects.openContainingFolder", "command": "sqlDatabaseProjects.openContainingFolder",
"when": "false" "when": "false"
@@ -308,6 +326,11 @@
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder", "when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"group": "3_dbProjects_newItem@3" "group": "3_dbProjects_newItem@3"
}, },
{
"command": "sqlDatabaseProjects.newExternalStreamingJob",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"group": "3_dbProjects_newItem@4"
},
{ {
"command": "sqlDatabaseProjects.newScript", "command": "sqlDatabaseProjects.newScript",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder", "when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
@@ -328,14 +351,19 @@
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.referencesRoot", "when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.referencesRoot",
"group": "4_dbProjects_addDatabaseReference" "group": "4_dbProjects_addDatabaseReference"
}, },
{
"command": "sqlDatabaseProjects.validateExternalStreamingJob",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.file.externalStreamingJob",
"group": "5_dbProjects_streamingJob"
},
{ {
"command": "sqlDatabaseProjects.exclude", "command": "sqlDatabaseProjects.exclude",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.folder || viewItem == databaseProject.itemType.file", "when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.folder || viewItem =~ /^databaseProject.itemType.file/",
"group": "9_dbProjectsLast@1" "group": "9_dbProjectsLast@1"
}, },
{ {
"command": "sqlDatabaseProjects.delete", "command": "sqlDatabaseProjects.delete",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.folder || viewItem == databaseProject.itemType.file || viewItem == databaseProject.itemType.reference", "when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.folder || viewItem =~ /^databaseProject.itemType.file/ || viewItem == databaseProject.itemType.reference",
"group": "9_dbProjectsLast@2" "group": "9_dbProjectsLast@2"
}, },
{ {

View File

@@ -13,6 +13,7 @@
"sqlDatabaseProjects.schemaCompare": "Schema Compare", "sqlDatabaseProjects.schemaCompare": "Schema Compare",
"sqlDatabaseProjects.delete": "Delete", "sqlDatabaseProjects.delete": "Delete",
"sqlDatabaseProjects.exclude": "Exclude from project", "sqlDatabaseProjects.exclude": "Exclude from project",
"sqlDatabaseProjects.validateExternalStreamingJob": "Validate External Streaming Job",
"sqlDatabaseProjects.newScript": "Add Script", "sqlDatabaseProjects.newScript": "Add Script",
"sqlDatabaseProjects.newPreDeploymentScript": "Add Pre-Deployment Script", "sqlDatabaseProjects.newPreDeploymentScript": "Add Pre-Deployment Script",
@@ -20,6 +21,7 @@
"sqlDatabaseProjects.newTable": "Add Table", "sqlDatabaseProjects.newTable": "Add Table",
"sqlDatabaseProjects.newView": "Add View", "sqlDatabaseProjects.newView": "Add View",
"sqlDatabaseProjects.newStoredProcedure": "Add Stored Procedure", "sqlDatabaseProjects.newStoredProcedure": "Add Stored Procedure",
"sqlDatabaseProjects.newExternalStreamingJob": "Add External Streaming Job",
"sqlDatabaseProjects.newItem": "Add Item...", "sqlDatabaseProjects.newItem": "Add Item...",
"sqlDatabaseProjects.newFolder": "Add Folder", "sqlDatabaseProjects.newFolder": "Add Folder",

View File

@@ -0,0 +1,7 @@
EXEC sys.sp_create_streaming_job @NAME = '@@OBJECT_NAME@@', @STATEMENT = 'INSERT INTO SqlOutputStream SELECT
timeCreated,
machine.temperature as machine_temperature,
machine.pressure as machine_pressure,
ambient.temperature as ambient_temperature,
ambient.humidity as ambient_humidity
FROM EdgeHubInputStream'

View File

@@ -149,6 +149,7 @@ export const ousiderFolderPath = localize('outsideFolderPath', "Items with absol
export const parentTreeItemUnknown = localize('parentTreeItemUnknown', "Cannot access parent of provided tree item"); export const parentTreeItemUnknown = localize('parentTreeItemUnknown', "Cannot access parent of provided tree item");
export const prePostDeployCount = localize('prePostDeployCount', "To successfully build, update the project to have one pre-deployment script and/or one post-deployment script"); export const prePostDeployCount = localize('prePostDeployCount', "To successfully build, update the project to have one pre-deployment script and/or one post-deployment script");
export const invalidProjectReload = localize('invalidProjectReload', "Cannot access provided database project. Only valid, open database projects can be reloaded."); export const invalidProjectReload = localize('invalidProjectReload', "Cannot access provided database project. Only valid, open database projects can be reloaded.");
export const externalStreamingJobValidationPassed = localize('externalStreamingJobValidationPassed', "Validation of external streaming job passed.");
export function projectAlreadyOpened(path: string) { return localize('projectAlreadyOpened', "Project '{0}' is already opened.", path); } 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 projectAlreadyExists(name: string, path: string) { return localize('projectAlreadyExists', "A project named {0} already exists in {1}.", name, path); }
export function noFileExist(fileName: string) { return localize('noFileExist', "File {0} doesn't exist", fileName); } export function noFileExist(fileName: string) { return localize('noFileExist', "File {0} doesn't exist", fileName); }
@@ -184,6 +185,7 @@ export const scriptFriendlyName = localize('scriptFriendlyName', "Script");
export const tableFriendlyName = localize('tableFriendlyName', "Table"); export const tableFriendlyName = localize('tableFriendlyName', "Table");
export const viewFriendlyName = localize('viewFriendlyName', "View"); export const viewFriendlyName = localize('viewFriendlyName', "View");
export const storedProcedureFriendlyName = localize('storedProcedureFriendlyName', "Stored Procedure"); export const storedProcedureFriendlyName = localize('storedProcedureFriendlyName', "Stored Procedure");
export const externalStreamingJobFriendlyName = localize('externalStreamingJobFriendlyName', "External Streaming Job");
export const preDeployScriptFriendlyName = localize('preDeployScriptFriendlyName', "Script.PreDeployment"); export const preDeployScriptFriendlyName = localize('preDeployScriptFriendlyName', "Script.PreDeployment");
export const postDeployScriptFriendlyName = localize('postDeployScriptFriendlyName', "Script.PostDeployment"); export const postDeployScriptFriendlyName = localize('postDeployScriptFriendlyName', "Script.PostDeployment");
@@ -223,6 +225,8 @@ export const True = 'True';
export const False = 'False'; export const False = 'False';
export const Private = 'Private'; export const Private = 'Private';
export const ProjectGuid = 'ProjectGuid'; export const ProjectGuid = 'ProjectGuid';
export const Type = 'Type';
export const ExternalStreamingJob: string = 'ExternalStreamingJob';
// SqlProj File targets // SqlProj File targets
export const NetCoreTargets = '$(NETCoreTargetsPath)\\Microsoft.Data.Tools.Schema.SqlTasks.targets'; export const NetCoreTargets = '$(NETCoreTargetsPath)\\Microsoft.Data.Tools.Schema.SqlTasks.targets';
@@ -270,6 +274,7 @@ export enum DatabaseProjectItemType {
project = 'databaseProject.itemType.project', project = 'databaseProject.itemType.project',
folder = 'databaseProject.itemType.folder', folder = 'databaseProject.itemType.folder',
file = 'databaseProject.itemType.file', file = 'databaseProject.itemType.file',
externalStreamingJob = 'databaseProject.itemType.file.externalStreamingJob',
referencesRoot = 'databaseProject.itemType.referencesRoot', referencesRoot = 'databaseProject.itemType.referencesRoot',
reference = 'databaseProject.itemType.reference', reference = 'databaseProject.itemType.reference',
dataSourceRoot = 'databaseProject.itemType.dataSourceRoot', dataSourceRoot = 'databaseProject.itemType.dataSourceRoot',

View File

@@ -16,7 +16,7 @@ import { ProjectsController } from './projectController';
import { BaseProjectTreeItem } from '../models/tree/baseTreeItem'; import { BaseProjectTreeItem } from '../models/tree/baseTreeItem';
import { NetCoreTool } from '../tools/netcoreTool'; import { NetCoreTool } from '../tools/netcoreTool';
import { Project } from '../models/project'; import { Project } from '../models/project';
import { FileNode, FolderNode } from '../models/tree/fileFolderTreeItem'; import { ExternalStreamingJobFileNode, FileNode, FolderNode } from '../models/tree/fileFolderTreeItem';
import { IconPathHelper } from '../common/iconHelper'; import { IconPathHelper } from '../common/iconHelper';
import { IProjectProvider } from 'dataworkspace'; import { IProjectProvider } from 'dataworkspace';
import { SqlDatabaseProjectProvider } from '../projectProvider/projectProvider'; import { SqlDatabaseProjectProvider } from '../projectProvider/projectProvider';
@@ -70,6 +70,7 @@ export default class MainController implements vscode.Disposable {
vscode.commands.registerCommand('sqlDatabaseProjects.newTable', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPromptFromNode(node, templates.table); }); vscode.commands.registerCommand('sqlDatabaseProjects.newTable', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPromptFromNode(node, templates.table); });
vscode.commands.registerCommand('sqlDatabaseProjects.newView', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPromptFromNode(node, templates.view); }); vscode.commands.registerCommand('sqlDatabaseProjects.newView', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPromptFromNode(node, templates.view); });
vscode.commands.registerCommand('sqlDatabaseProjects.newStoredProcedure', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPromptFromNode(node, templates.storedProcedure); }); vscode.commands.registerCommand('sqlDatabaseProjects.newStoredProcedure', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPromptFromNode(node, templates.storedProcedure); });
vscode.commands.registerCommand('sqlDatabaseProjects.newExternalStreamingJob', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPromptFromNode(node, templates.externalStreamingJob); });
vscode.commands.registerCommand('sqlDatabaseProjects.newItem', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPromptFromNode(node); }); vscode.commands.registerCommand('sqlDatabaseProjects.newItem', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPromptFromNode(node); });
vscode.commands.registerCommand('sqlDatabaseProjects.newFolder', async (node: BaseProjectTreeItem) => { await this.projectsController.addFolderPrompt(node); }); vscode.commands.registerCommand('sqlDatabaseProjects.newFolder', async (node: BaseProjectTreeItem) => { await this.projectsController.addFolderPrompt(node); });
@@ -79,6 +80,7 @@ export default class MainController implements vscode.Disposable {
vscode.commands.registerCommand('sqlDatabaseProjects.delete', async (node: BaseProjectTreeItem) => { await this.projectsController.delete(node); }); vscode.commands.registerCommand('sqlDatabaseProjects.delete', async (node: BaseProjectTreeItem) => { await this.projectsController.delete(node); });
vscode.commands.registerCommand('sqlDatabaseProjects.exclude', async (node: FileNode | FolderNode) => { await this.projectsController.exclude(node); }); vscode.commands.registerCommand('sqlDatabaseProjects.exclude', async (node: FileNode | FolderNode) => { await this.projectsController.exclude(node); });
vscode.commands.registerCommand('sqlDatabaseProjects.changeTargetPlatform', async (node: BaseProjectTreeItem) => { await this.projectsController.changeTargetPlatform(node); }); vscode.commands.registerCommand('sqlDatabaseProjects.changeTargetPlatform', async (node: BaseProjectTreeItem) => { await this.projectsController.changeTargetPlatform(node); });
vscode.commands.registerCommand('sqlDatabaseProjects.validateExternalStreamingJob', async (node: ExternalStreamingJobFileNode) => { await this.projectsController.validateExternalStreamingJob(node); });
IconPathHelper.setExtensionContext(this.extensionContext); IconPathHelper.setExtensionContext(this.extensionContext);

View File

@@ -19,7 +19,7 @@ import { promises as fs } from 'fs';
import { PublishDatabaseDialog } from '../dialogs/publishDatabaseDialog'; import { PublishDatabaseDialog } from '../dialogs/publishDatabaseDialog';
import { Project, reservedProjectFolders, FileProjectEntry, SqlProjectReferenceProjectEntry, IDatabaseReferenceProjectEntry } from '../models/project'; import { Project, reservedProjectFolders, FileProjectEntry, SqlProjectReferenceProjectEntry, IDatabaseReferenceProjectEntry } from '../models/project';
import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider'; import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider';
import { FolderNode, FileNode } from '../models/tree/fileFolderTreeItem'; import { FolderNode, FileNode, ExternalStreamingJobFileNode } from '../models/tree/fileFolderTreeItem';
import { IPublishSettings, IGenerateScriptSettings } from '../models/IPublishSettings'; import { IPublishSettings, IGenerateScriptSettings } from '../models/IPublishSettings';
import { BaseProjectTreeItem } from '../models/tree/baseTreeItem'; import { BaseProjectTreeItem } from '../models/tree/baseTreeItem';
import { ProjectRootTreeItem } from '../models/tree/projectTreeItem'; import { ProjectRootTreeItem } from '../models/tree/projectTreeItem';
@@ -204,7 +204,7 @@ export class ProjectsController {
try { try {
await this.netCoreTool.runDotnetCommand(options); await this.netCoreTool.runDotnetCommand(options);
return path.join(project.projectFolderPath, 'bin', 'Debug', `${project.projectFileName}.dacpac`); return project.dacpacOutputPath;
} }
catch (err) { catch (err) {
vscode.window.showErrorMessage(constants.projBuildFailed(utils.getErrorMessage(err))); vscode.window.showErrorMessage(constants.projBuildFailed(utils.getErrorMessage(err)));
@@ -577,6 +577,31 @@ export class ProjectsController {
} }
} }
public async validateExternalStreamingJob(node: ExternalStreamingJobFileNode): Promise<mssql.ValidateStreamingJobResult> {
const project: Project = this.getProjectFromContext(node);
let dacpacPath: string = project.dacpacOutputPath;
if (!await utils.exists(dacpacPath)) {
dacpacPath = await this.buildProject(node);
}
const streamingJobDefinition: string = (await fs.readFile(node.fileSystemUri.fsPath)).toString();
const dacFxService = await this.getDaxFxService();
const result: mssql.ValidateStreamingJobResult = await dacFxService.validateStreamingJob(dacpacPath, streamingJobDefinition);
if (result.success) {
vscode.window.showInformationMessage(constants.externalStreamingJobValidationPassed);
}
else {
vscode.window.showErrorMessage(result.errorMessage);
}
return result;
}
//#region Helper methods //#region Helper methods
public getPublishDialog(project: Project): PublishDatabaseDialog { public getPublishDialog(project: Project): PublishDatabaseDialog {

View File

@@ -32,6 +32,10 @@ export class Project {
public postDeployScripts: FileProjectEntry[] = []; public postDeployScripts: FileProjectEntry[] = [];
public noneDeployScripts: FileProjectEntry[] = []; public noneDeployScripts: FileProjectEntry[] = [];
public get dacpacOutputPath(): string {
return path.join(this.projectFolderPath, 'bin', 'Debug', `${this.projectFileName}.dacpac`);
}
public get projectFolderPath() { public get projectFolderPath() {
return Uri.file(path.dirname(this.projectFilePath)).fsPath; return Uri.file(path.dirname(this.projectFilePath)).fsPath;
} }
@@ -71,7 +75,7 @@ export class Project {
const buildElements = itemGroup.getElementsByTagName(constants.Build); const buildElements = itemGroup.getElementsByTagName(constants.Build);
for (let b = 0; b < buildElements.length; b++) { for (let b = 0; b < buildElements.length; b++) {
this.files.push(this.createFileProjectEntry(buildElements[b].getAttribute(constants.Include), EntryType.File)); this.files.push(this.createFileProjectEntry(buildElements[b].getAttribute(constants.Include), EntryType.File, buildElements[b].getAttribute(constants.Type)));
} }
const folderElements = itemGroup.getElementsByTagName(constants.Folder); const folderElements = itemGroup.getElementsByTagName(constants.Folder);
@@ -291,7 +295,13 @@ export class Project {
this.files.push(fileEntry); this.files.push(fileEntry);
} }
await this.addToProjFile(fileEntry, xmlTag); const attributes = new Map<string, string>();
if (itemType === templates.externalStreamingJob) {
attributes.set(constants.Type, constants.ExternalStreamingJob);
}
await this.addToProjFile(fileEntry, xmlTag, attributes);
return fileEntry; return fileEntry;
} }
@@ -454,9 +464,9 @@ export class Project {
await this.addToProjFile(sqlCmdVariableEntry); await this.addToProjFile(sqlCmdVariableEntry);
} }
public createFileProjectEntry(relativePath: string, entryType: EntryType): FileProjectEntry { public createFileProjectEntry(relativePath: string, entryType: EntryType, sqlObjectType?: string): FileProjectEntry {
let platformSafeRelativePath = utils.getPlatformSafeFileEntryPath(relativePath); let platformSafeRelativePath = utils.getPlatformSafeFileEntryPath(relativePath);
return new FileProjectEntry(Uri.file(path.join(this.projectFolderPath, platformSafeRelativePath)), relativePath, entryType); return new FileProjectEntry(Uri.file(path.join(this.projectFolderPath, platformSafeRelativePath)), relativePath, entryType, sqlObjectType);
} }
private findOrCreateItemGroup(containedTag?: string, prePostScriptExist?: { scriptExist: boolean; }): any { private findOrCreateItemGroup(containedTag?: string, prePostScriptExist?: { scriptExist: boolean; }): any {
@@ -480,6 +490,7 @@ export class Project {
if (!outputItemGroup) { if (!outputItemGroup) {
outputItemGroup = this.projFileXmlDoc.createElement(constants.ItemGroup); outputItemGroup = this.projFileXmlDoc.createElement(constants.ItemGroup);
this.projFileXmlDoc.documentElement.appendChild(outputItemGroup); this.projFileXmlDoc.documentElement.appendChild(outputItemGroup);
if (prePostScriptExist) { if (prePostScriptExist) {
prePostScriptExist.scriptExist = false; prePostScriptExist.scriptExist = false;
} }
@@ -488,12 +499,13 @@ export class Project {
return outputItemGroup; return outputItemGroup;
} }
private addFileToProjFile(path: string, xmlTag: string): void { private addFileToProjFile(path: string, xmlTag: string, attributes?: Map<string, string>): void {
let itemGroup; let itemGroup;
if (xmlTag === constants.PreDeploy || xmlTag === constants.PostDeploy) { if (xmlTag === constants.PreDeploy || xmlTag === constants.PostDeploy) {
let prePostScriptExist = { scriptExist: true }; let prePostScriptExist = { scriptExist: true };
itemGroup = this.findOrCreateItemGroup(xmlTag, prePostScriptExist); itemGroup = this.findOrCreateItemGroup(xmlTag, prePostScriptExist);
if (prePostScriptExist.scriptExist === true) { if (prePostScriptExist.scriptExist === true) {
window.showInformationMessage(constants.deployScriptExists(xmlTag)); window.showInformationMessage(constants.deployScriptExists(xmlTag));
xmlTag = constants.None; // Add only one pre-deploy and post-deploy script. All additional ones get added in the same item group with None tag xmlTag = constants.None; // Add only one pre-deploy and post-deploy script. All additional ones get added in the same item group with None tag
@@ -504,7 +516,15 @@ export class Project {
} }
const newFileNode = this.projFileXmlDoc.createElement(xmlTag); const newFileNode = this.projFileXmlDoc.createElement(xmlTag);
newFileNode.setAttribute(constants.Include, utils.convertSlashesForSqlProj(path)); newFileNode.setAttribute(constants.Include, utils.convertSlashesForSqlProj(path));
if (attributes) {
for (const key of attributes.keys()) {
newFileNode.setAttribute(key, attributes.get(key));
}
}
itemGroup.appendChild(newFileNode); itemGroup.appendChild(newFileNode);
} }
@@ -530,12 +550,14 @@ export class Project {
private removeNode(includeString: string, nodes: any): boolean { private removeNode(includeString: string, nodes: any): boolean {
for (let i = 0; i < nodes.length; i++) { for (let i = 0; i < nodes.length; i++) {
const parent = nodes[i].parentNode; const parent = nodes[i].parentNode;
if (nodes[i].getAttribute(constants.Include) === utils.convertSlashesForSqlProj(includeString)) { if (nodes[i].getAttribute(constants.Include) === utils.convertSlashesForSqlProj(includeString)) {
parent.removeChild(nodes[i]); parent.removeChild(nodes[i]);
// delete ItemGroup if this was the only entry // delete ItemGroup if this was the only entry
// only want element nodes, not text nodes // only want element nodes, not text nodes
const otherChildren = Array.from(parent.childNodes).filter((c: any) => c.childNodes); const otherChildren = Array.from(parent.childNodes).filter((c: any) => c.childNodes);
if (otherChildren.length === 0) { if (otherChildren.length === 0) {
parent.parentNode.removeChild(parent); parent.parentNode.removeChild(parent);
} }
@@ -809,10 +831,10 @@ export class Project {
} }
} }
private async addToProjFile(entry: ProjectEntry, xmlTag?: string): Promise<void> { private async addToProjFile(entry: ProjectEntry, xmlTag?: string, attributes?: Map<string, string>): Promise<void> {
switch (entry.type) { switch (entry.type) {
case EntryType.File: case EntryType.File:
this.addFileToProjFile((<FileProjectEntry>entry).relativePath, xmlTag ? xmlTag : constants.Build); this.addFileToProjFile((<FileProjectEntry>entry).relativePath, xmlTag ? xmlTag : constants.Build, attributes);
break; break;
case EntryType.Folder: case EntryType.Folder:
this.addFolderToProjFile((<FileProjectEntry>entry).relativePath); this.addFolderToProjFile((<FileProjectEntry>entry).relativePath);
@@ -901,11 +923,13 @@ export class FileProjectEntry extends ProjectEntry {
*/ */
fsUri: Uri; fsUri: Uri;
relativePath: string; relativePath: string;
sqlObjectType: string | undefined;
constructor(uri: Uri, relativePath: string, type: EntryType) { constructor(uri: Uri, relativePath: string, entryType: EntryType, sqlObjectType?: string) {
super(type); super(entryType);
this.fsUri = uri; this.fsUri = uri;
this.relativePath = relativePath; this.relativePath = relativePath;
this.sqlObjectType = sqlObjectType;
} }
public toString(): string { public toString(): string {

View File

@@ -71,6 +71,15 @@ export class FileNode extends BaseProjectTreeItem {
} }
} }
export class ExternalStreamingJobFileNode extends FileNode {
public get treeItem(): vscode.TreeItem {
const treeItem = super.treeItem;
treeItem.contextValue = DatabaseProjectItemType.externalStreamingJob;
return treeItem;
}
}
/** /**
* Compares two folder/file tree nodes so that folders come before files, then alphabetically * Compares two folder/file tree nodes so that folders come before files, then alphabetically
* @param a a folder or file tree node * @param a a folder or file tree node

View File

@@ -11,7 +11,7 @@ import * as fileTree from './fileFolderTreeItem';
import { Project, EntryType, FileProjectEntry } from '../project'; import { Project, EntryType, FileProjectEntry } from '../project';
import * as utils from '../../common/utils'; import * as utils from '../../common/utils';
import { DatabaseReferencesTreeItem } from './databaseReferencesTreeItem'; import { DatabaseReferencesTreeItem } from './databaseReferencesTreeItem';
import { DatabaseProjectItemType, RelativeOuterPath } from '../../common/constants'; import { DatabaseProjectItemType, RelativeOuterPath, ExternalStreamingJob } from '../../common/constants';
import { IconPathHelper } from '../../common/iconHelper'; import { IconPathHelper } from '../../common/iconHelper';
/** /**
@@ -76,7 +76,13 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem {
switch (entry.type) { switch (entry.type) {
case EntryType.File: case EntryType.File:
newNode = new fileTree.FileNode(entry.fsUri, parentNode); if (entry.sqlObjectType === ExternalStreamingJob) {
newNode = new fileTree.ExternalStreamingJobFileNode(entry.fsUri, parentNode);
}
else {
newNode = new fileTree.FileNode(entry.fsUri, parentNode);
}
break; break;
case EntryType.Folder: case EntryType.Folder:
newNode = new fileTree.FolderNode(entry.fsUri, parentNode); newNode = new fileTree.FolderNode(entry.fsUri, parentNode);

View File

@@ -15,6 +15,8 @@ export const script: string = 'script';
export const table: string = 'table'; export const table: string = 'table';
export const view: string = 'view'; export const view: string = 'view';
export const storedProcedure: string = 'storedProcedure'; export const storedProcedure: string = 'storedProcedure';
export const externalStreamingJob: string = 'externalStreamingJob';
export const folder: string = 'folder'; export const folder: string = 'folder';
export const preDeployScript: string = 'preDeployScript'; export const preDeployScript: string = 'preDeployScript';
export const postDeployScript: string = 'postDeployScript'; export const postDeployScript: string = 'postDeployScript';
@@ -49,7 +51,8 @@ export async function loadTemplates(templateFolderPath: string) {
loadObjectTypeInfo(view, constants.viewFriendlyName, templateFolderPath, 'newTsqlViewTemplate.sql'), loadObjectTypeInfo(view, constants.viewFriendlyName, templateFolderPath, 'newTsqlViewTemplate.sql'),
loadObjectTypeInfo(storedProcedure, constants.storedProcedureFriendlyName, templateFolderPath, 'newTsqlStoredProcedureTemplate.sql'), loadObjectTypeInfo(storedProcedure, constants.storedProcedureFriendlyName, templateFolderPath, 'newTsqlStoredProcedureTemplate.sql'),
loadObjectTypeInfo(preDeployScript, constants.preDeployScriptFriendlyName, templateFolderPath, 'newTsqlPreDeployScriptTemplate.sql'), loadObjectTypeInfo(preDeployScript, constants.preDeployScriptFriendlyName, templateFolderPath, 'newTsqlPreDeployScriptTemplate.sql'),
loadObjectTypeInfo(postDeployScript, constants.postDeployScriptFriendlyName, templateFolderPath, 'newTsqlPostDeployScriptTemplate.sql') loadObjectTypeInfo(postDeployScript, constants.postDeployScriptFriendlyName, templateFolderPath, 'newTsqlPostDeployScriptTemplate.sql'),
loadObjectTypeInfo(externalStreamingJob, constants.externalStreamingJobFriendlyName, templateFolderPath, 'newTsqlExternalStreamingJobTemplate.sql')
]); ]);
for (const scriptType of scriptTypes) { for (const scriptType of scriptTypes) {

View File

@@ -74,6 +74,9 @@
<Folder Include="Views\User" /> <Folder Include="Views\User" />
<Build Include="Views\User\Profile.sql" /> <Build Include="Views\User\Profile.sql" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Build Include="MyExternalStreamingJob.sql" Type="ExternalStreamingJob" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<SqlCmdVariable Include="ProdDatabaseName"> <SqlCmdVariable Include="ProdDatabaseName">
<DefaultValue>MyProdDatabase</DefaultValue> <DefaultValue>MyProdDatabase</DefaultValue>

View File

@@ -74,6 +74,9 @@
<Folder Include="Views\User"/> <Folder Include="Views\User"/>
<Build Include="Views\User\Profile.sql"/> <Build Include="Views\User\Profile.sql"/>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Build Include="MyExternalStreamingJob.sql" Type="ExternalStreamingJob"/>
</ItemGroup>
<ItemGroup> <ItemGroup>
<SqlCmdVariable Include="BackupDatabaseName"> <SqlCmdVariable Include="BackupDatabaseName">
<DefaultValue>MyBackupDatabase</DefaultValue> <DefaultValue>MyBackupDatabase</DefaultValue>

View File

@@ -31,12 +31,14 @@ describe('Project: sqlproj content operations', function (): void {
const project: Project = await Project.openProject(projFilePath); const project: Project = await Project.openProject(projFilePath);
// Files and folders // Files and folders
should(project.files.filter(f => f.type === EntryType.File).length).equal(5); should(project.files.filter(f => f.type === EntryType.File).length).equal(6);
should(project.files.filter(f => f.type === EntryType.Folder).length).equal(4); should(project.files.filter(f => f.type === EntryType.Folder).length).equal(4);
should(project.files.find(f => f.type === EntryType.Folder && f.relativePath === 'Views\\User')).not.equal(undefined); // mixed ItemGroup folder should(project.files.find(f => f.type === EntryType.Folder && f.relativePath === 'Views\\User')).not.equal(undefined); // mixed ItemGroup folder
should(project.files.find(f => f.type === EntryType.File && f.relativePath === 'Views\\User\\Profile.sql')).not.equal(undefined); // mixed ItemGroup file should(project.files.find(f => f.type === EntryType.File && f.relativePath === 'Views\\User\\Profile.sql')).not.equal(undefined); // mixed ItemGroup file
should(project.files.find(f => f.type === EntryType.File && f.relativePath === '..\\Test\\Test.sql')).not.equal(undefined); // mixed ItemGroup file should(project.files.find(f => f.type === EntryType.File && f.relativePath === '..\\Test\\Test.sql')).not.equal(undefined); // mixed ItemGroup file
should(project.files.find(f => f.type === EntryType.File && f.relativePath === 'MyExternalStreamingJob.sql')).not.equal(undefined); // entry with custom attribute
// SqlCmdVariables // SqlCmdVariables
should(Object.keys(project.sqlCmdVariables).length).equal(2); should(Object.keys(project.sqlCmdVariables).length).equal(2);
@@ -94,20 +96,26 @@ describe('Project: sqlproj content operations', function (): void {
const project = await Project.openProject(projFilePath); const project = await Project.openProject(projFilePath);
const folderPath = 'Stored Procedures'; const folderPath = 'Stored Procedures';
const filePath = path.join(folderPath, 'Fake Stored Proc.sql'); const scriptPath = path.join(folderPath, 'Fake Stored Proc.sql');
const fileContents = 'SELECT \'This is not actually a stored procedure.\''; const scriptContents = 'SELECT \'This is not actually a stored procedure.\'';
const scriptPathTagged = path.join(folderPath, 'Fake External Streaming Job.sql');
const scriptContentsTagged = 'EXEC sys.sp_create_streaming_job \'job\', \'SELECT 7\'';
await project.addFolderItem(folderPath); await project.addFolderItem(folderPath);
await project.addScriptItem(filePath, fileContents); await project.addScriptItem(scriptPath, scriptContents);
await project.addScriptItem(scriptPathTagged, scriptContentsTagged, templates.externalStreamingJob);
const newProject = await Project.openProject(projFilePath); const newProject = await Project.openProject(projFilePath);
should(newProject.files.find(f => f.type === EntryType.Folder && f.relativePath === convertSlashesForSqlProj(folderPath))).not.equal(undefined); should(newProject.files.find(f => f.type === EntryType.Folder && f.relativePath === convertSlashesForSqlProj(folderPath))).not.equal(undefined);
should(newProject.files.find(f => f.type === EntryType.File && f.relativePath === convertSlashesForSqlProj(filePath))).not.equal(undefined); should(newProject.files.find(f => f.type === EntryType.File && f.relativePath === convertSlashesForSqlProj(scriptPath))).not.equal(undefined);
should(newProject.files.find(f => f.type === EntryType.File && f.relativePath === convertSlashesForSqlProj(scriptPathTagged))).not.equal(undefined);
should(newProject.files.find(f => f.type === EntryType.File && f.relativePath === convertSlashesForSqlProj(scriptPathTagged))?.sqlObjectType).equal(constants.ExternalStreamingJob);
const newFileContents = (await fs.readFile(path.join(newProject.projectFolderPath, filePath))).toString(); const newScriptContents = (await fs.readFile(path.join(newProject.projectFolderPath, scriptPath))).toString();
should(newFileContents).equal(fileContents); should(newScriptContents).equal(scriptContents);
}); });
it('Should add Folder and Build entries to sqlproj with pre-existing scripts on disk', async function (): Promise<void> { it('Should add Folder and Build entries to sqlproj with pre-existing scripts on disk', async function (): Promise<void> {

View File

@@ -85,7 +85,7 @@ describe('ProjectsController', function (): void {
const project = await projController.openProject(vscode.Uri.file(sqlProjPath)); const project = await projController.openProject(vscode.Uri.file(sqlProjPath));
should(project.files.length).equal(9); // detailed sqlproj tests in their own test file should(project.files.length).equal(10); // detailed sqlproj tests in their own test file
should(project.dataSources.length).equal(3); // detailed datasources tests in their own test file should(project.dataSources.length).equal(3); // detailed datasources tests in their own test file
}); });

View File

@@ -23,7 +23,7 @@ describe('Templates: loading templates from disk', function (): void {
// check expected counts // check expected counts
const numScriptObjectTypes = 6; const numScriptObjectTypes = 7;
should(templates.projectScriptTypes().length).equal(numScriptObjectTypes); should(templates.projectScriptTypes().length).equal(numScriptObjectTypes);
should(Object.keys(templates.projectScriptTypes()).length).equal(numScriptObjectTypes); should(Object.keys(templates.projectScriptTypes()).length).equal(numScriptObjectTypes);

View File

@@ -115,6 +115,7 @@ export class MockDacFxService implements mssql.IDacFxService {
public generateDeployScript(_: string, __: string, ___: string, ____: azdata.TaskExecutionMode, ______?: Record<string, string>): Thenable<mssql.DacFxResult> { return Promise.resolve(mockDacFxResult); } public generateDeployScript(_: string, __: string, ___: string, ____: azdata.TaskExecutionMode, ______?: Record<string, string>): Thenable<mssql.DacFxResult> { return Promise.resolve(mockDacFxResult); }
public generateDeployPlan(_: string, __: string, ___: string, ____: azdata.TaskExecutionMode): Thenable<mssql.GenerateDeployPlanResult> { return Promise.resolve(mockDacFxResult); } public generateDeployPlan(_: string, __: string, ___: string, ____: azdata.TaskExecutionMode): Thenable<mssql.GenerateDeployPlanResult> { return Promise.resolve(mockDacFxResult); }
public getOptionsFromProfile(_: string): Thenable<mssql.DacFxOptionsResult> { return Promise.resolve(mockDacFxOptionsResult); } public getOptionsFromProfile(_: string): Thenable<mssql.DacFxOptionsResult> { return Promise.resolve(mockDacFxOptionsResult); }
public validateStreamingJob(_: string, __: string): Thenable<mssql.ValidateStreamingJobResult> { return Promise.resolve(mockDacFxResult); }
} }
export function createContext(): TestContext { export function createContext(): TestContext {