resend previous PR with test fixes (#19912)

* Revert "Revert "use reliable way to detect createtable statements (#19897)" (#19906)"

This reverts commit c211fb981c.

* fix tests

* fix test cases
This commit is contained in:
Alan Ren
2022-07-02 11:25:09 -07:00
committed by GitHub
parent 60026a39f9
commit 416e607f32
11 changed files with 80 additions and 19 deletions

View File

@@ -170,4 +170,7 @@ export class DacFxTestService implements mssql.IDacFxService {
}; };
return Promise.resolve(streamingJobValidationResult); return Promise.resolve(streamingJobValidationResult);
} }
parseTSqlScript(filePath: string, databaseSchemaProvider: string): Thenable<mssql.ParseTSqlScriptResult> {
return Promise.resolve({ containsCreateTableStatement: true });
}
} }

View File

@@ -1,6 +1,6 @@
{ {
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}", "downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "4.1.0.4", "version": "4.1.0.9",
"downloadFileNames": { "downloadFileNames": {
"Windows_86": "win-x86-net6.0.zip", "Windows_86": "win-x86-net6.0.zip",
"Windows_64": "win-x64-net6.0.zip", "Windows_64": "win-x64-net6.0.zip",

View File

@@ -545,6 +545,11 @@ export interface ValidateStreamingJobParams {
createStreamingJobTsql: string createStreamingJobTsql: string
} }
export interface ParseTSqlScriptParams {
filePath: string;
databaseSchemaProvider: 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');
} }
@@ -577,6 +582,10 @@ export namespace ValidateStreamingJobRequest {
export const type = new RequestType<ValidateStreamingJobParams, mssql.ValidateStreamingJobResult, void, void>('dacfx/validateStreamingJob'); export const type = new RequestType<ValidateStreamingJobParams, mssql.ValidateStreamingJobResult, void, void>('dacfx/validateStreamingJob');
} }
export namespace ParseTSqlScriptRequest {
export const type = new RequestType<ParseTSqlScriptParams, mssql.ParseTSqlScriptResult, void, void>('dacfx/parseTSqlScript');
}
// ------------------------------- </ DacFx > ------------------------------------ // ------------------------------- </ DacFx > ------------------------------------
// ------------------------------- <CMS> ---------------------------------------- // ------------------------------- <CMS> ----------------------------------------

View File

@@ -130,4 +130,15 @@ export class DacFxService implements mssql.IDacFxService {
} }
); );
} }
public async parseTSqlScript(filePath: string, databaseSchemaProvider: string): Promise<mssql.ParseTSqlScriptResult> {
const params: contracts.ParseTSqlScriptParams = { filePath, databaseSchemaProvider };
try {
const result = await this.client.sendRequest(contracts.ParseTSqlScriptRequest.type, params);
return result;
} catch (e) {
this.client.logFailedRequest(contracts.ParseTSqlScriptRequest.type, e);
throw e;
}
}
} }

View File

@@ -391,6 +391,7 @@ declare module 'mssql' {
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>; validateStreamingJob(packageFilePath: string, createStreamingJobTsql: string): Thenable<ValidateStreamingJobResult>;
parseTSqlScript(filePath: string, databaseSchemaProvider: string): Thenable<ParseTSqlScriptResult>;
} }
export interface DacFxResult extends azdata.ResultStatus { export interface DacFxResult extends azdata.ResultStatus {
@@ -408,6 +409,10 @@ declare module 'mssql' {
export interface ValidateStreamingJobResult extends azdata.ResultStatus { export interface ValidateStreamingJobResult extends azdata.ResultStatus {
} }
export interface ParseTSqlScriptResult {
containsCreateTableStatement: boolean;
}
export interface ExportParams { export interface ExportParams {
databaseName: string; databaseName: string;
packageFilePath: string; packageFilePath: string;

View File

@@ -10,6 +10,7 @@ import * as utils from '../common/utils';
import * as xmlFormat from 'xml-formatter'; import * as xmlFormat from 'xml-formatter';
import * as os from 'os'; import * as os from 'os';
import * as UUID from 'vscode-languageclient/lib/utils/uuid'; import * as UUID from 'vscode-languageclient/lib/utils/uuid';
import * as mssql from 'mssql';
import { Uri, window } from 'vscode'; import { Uri, window } from 'vscode';
import { EntryType, IDatabaseReferenceProjectEntry, IProjectEntry, ISqlProject, ItemType, SqlTargetPlatform } from 'sqldbproj'; import { EntryType, IDatabaseReferenceProjectEntry, IProjectEntry, ISqlProject, ItemType, SqlTargetPlatform } from 'sqldbproj';
@@ -247,14 +248,19 @@ export class Project implements ISqlProject {
const fileEntries: FileProjectEntry[] = []; const fileEntries: FileProjectEntry[] = [];
for (let f of Array.from(filesSet.values())) { for (let f of Array.from(filesSet.values())) {
const typeEntry = entriesWithType.find(e => e.relativePath === f); const typeEntry = entriesWithType.find(e => e.relativePath === f);
let containsCreateTableStatement; let containsCreateTableStatement = false;
// read file to check if it has a "Create Table" statement // read file to check if it has a "Create Table" statement
const fullPath = path.join(utils.getPlatformSafeFileEntryPath(this.projectFolderPath), utils.getPlatformSafeFileEntryPath(f)); const fullPath = path.join(utils.getPlatformSafeFileEntryPath(this.projectFolderPath), utils.getPlatformSafeFileEntryPath(f));
if (await utils.exists(fullPath)) { if (utils.getAzdataApi() && await utils.exists(fullPath)) {
const fileContents = await fs.readFile(fullPath); const dacFxService = await utils.getDacFxService() as mssql.IDacFxService;
containsCreateTableStatement = fileContents.toString().toLowerCase().includes('create table'); try {
const result = await dacFxService.parseTSqlScript(fullPath, this.getProjectTargetVersion());
containsCreateTableStatement = result.containsCreateTableStatement;
} catch (e) {
console.error(utils.getErrorMessage(e));
}
} }
fileEntries.push(this.createFileProjectEntry(f, EntryType.File, typeEntry ? typeEntry.typeAttribute : undefined, containsCreateTableStatement)); fileEntries.push(this.createFileProjectEntry(f, EntryType.File, typeEntry ? typeEntry.typeAttribute : undefined, containsCreateTableStatement));

View File

@@ -39,7 +39,7 @@ describe('Publish Database Dialog', () => {
sdkStyle: false sdkStyle: false
}); });
const project = new Project(projFilePath); const project = await Project.openProject(projFilePath);
const publishDatabaseDialog = new PublishDatabaseDialog(project); const publishDatabaseDialog = new PublishDatabaseDialog(project);
publishDatabaseDialog.openDialog(); publishDatabaseDialog.openDialog();
should.notEqual(publishDatabaseDialog.publishTab, undefined); should.notEqual(publishDatabaseDialog.publishTab, undefined);

View File

@@ -12,6 +12,7 @@ import * as TypeMoq from 'typemoq';
import { PublishOptionsDialog } from '../../dialogs/publishOptionsDialog'; import { PublishOptionsDialog } from '../../dialogs/publishOptionsDialog';
import { PublishDatabaseDialog } from '../../dialogs/publishDatabaseDialog'; import { PublishDatabaseDialog } from '../../dialogs/publishDatabaseDialog';
import { Project } from '../../models/project'; import { Project } from '../../models/project';
import sinon = require('sinon');
describe('Publish Database Options Dialog', () => { describe('Publish Database Options Dialog', () => {
before(async function (): Promise<void> { before(async function (): Promise<void> {
@@ -19,6 +20,8 @@ describe('Publish Database Options Dialog', () => {
}); });
it('Should open dialog successfully ', async function (): Promise<void> { it('Should open dialog successfully ', async function (): Promise<void> {
const proj = new Project('');
sinon.stub(proj, 'getProjectTargetVersion').returns('150');
const publishDatabaseDialog = new PublishDatabaseDialog(new Project('')); const publishDatabaseDialog = new PublishDatabaseDialog(new Project(''));
const optionsDialog = new PublishOptionsDialog(testData.getDeploymentOptions(), publishDatabaseDialog); const optionsDialog = new PublishOptionsDialog(testData.getDeploymentOptions(), publishDatabaseDialog);
optionsDialog.openDialog(); optionsDialog.openDialog();

View File

@@ -366,28 +366,36 @@ describe('ProjectsController', function (): void {
let projController = TypeMoq.Mock.ofType(ProjectsController); let projController = TypeMoq.Mock.ofType(ProjectsController);
projController.callBase = true; projController.callBase = true;
projController.setup(x => x.getPublishDialog(TypeMoq.It.isAny())).returns(() => publishDialog.object); projController.setup(x => x.getPublishDialog(TypeMoq.It.isAny())).returns(() => publishDialog.object);
const proj = new Project('FakePath');
void projController.object.publishProject(new Project('FakePath')); sinon.stub(proj, 'getProjectTargetVersion').returns('150');
void projController.object.publishProject(proj);
should(opened).equal(true); should(opened).equal(true);
}); });
it('Callbacks are hooked up and called from Publish dialog', async function (): Promise<void> { it('Callbacks are hooked up and called from Publish dialog', async function (): Promise<void> {
const projPath = path.dirname(await testUtils.createTestSqlProjFile(baselines.openProjectFileBaseline)); const projectFile = await testUtils.createTestSqlProjFile(baselines.openProjectFileBaseline)
await testUtils.createTestDataSources(baselines.openDataSourcesBaseline, projPath); const projFolder = path.dirname(projectFile);
const proj = new Project(projPath); await testUtils.createTestDataSources(baselines.openDataSourcesBaseline, projFolder);
const proj = await Project.openProject(projectFile);
const publishHoller = 'hello from callback for publish()'; const publishHoller = 'hello from callback for publish()';
const generateHoller = 'hello from callback for generateScript()'; const generateHoller = 'hello from callback for generateScript()';
let holler = 'nothing'; let holler = 'nothing';
let publishDialog = TypeMoq.Mock.ofType(PublishDatabaseDialog, undefined, undefined, proj); const setupPublishDialog = (): PublishDatabaseDialog => {
publishDialog.callBase = true; const dialog = new PublishDatabaseDialog(proj);
publishDialog.setup(x => x.getConnectionUri()).returns(() => Promise.resolve('fake|connection|uri')); sinon.stub(dialog, 'getConnectionUri').returns(Promise.resolve('fake|connection|uri'));
return dialog;
};
let publishDialog = setupPublishDialog();
let projController = TypeMoq.Mock.ofType(ProjectsController); let projController = TypeMoq.Mock.ofType(ProjectsController);
projController.callBase = true; projController.callBase = true;
projController.setup(x => x.getPublishDialog(TypeMoq.It.isAny())).returns(() => publishDialog.object); projController.setup(x => x.getPublishDialog(TypeMoq.It.isAny())).returns(() => {
return publishDialog;
});
projController.setup(x => x.publishOrScriptProject(TypeMoq.It.isAny(), TypeMoq.It.isAny(), true)).returns(() => { projController.setup(x => x.publishOrScriptProject(TypeMoq.It.isAny(), TypeMoq.It.isAny(), true)).returns(() => {
holler = publishHoller; holler = publishHoller;
return Promise.resolve(undefined); return Promise.resolve(undefined);
@@ -397,14 +405,15 @@ describe('ProjectsController', function (): void {
holler = generateHoller; holler = generateHoller;
return Promise.resolve(undefined); return Promise.resolve(undefined);
}); });
publishDialog.object.publishToExistingServer = true; publishDialog.publishToExistingServer = true;
void projController.object.publishProject(proj); void projController.object.publishProject(proj);
await publishDialog.object.publishClick(); await publishDialog.publishClick();
should(holler).equal(publishHoller, 'executionCallback() is supposed to have been setup and called for Publish scenario'); should(holler).equal(publishHoller, 'executionCallback() is supposed to have been setup and called for Publish scenario');
publishDialog = setupPublishDialog();
void projController.object.publishProject(proj); void projController.object.publishProject(proj);
await publishDialog.object.generateScriptClick(); await publishDialog.generateScriptClick();
should(holler).equal(generateHoller, 'executionCallback() is supposed to have been setup and called for GenerateScript scenario'); should(holler).equal(generateHoller, 'executionCallback() is supposed to have been setup and called for GenerateScript scenario');
}); });
@@ -603,6 +612,11 @@ describe('ProjectsController', function (): void {
const dataWorkspaceMock = TypeMoq.Mock.ofType<dataworkspace.IExtension>(); const dataWorkspaceMock = TypeMoq.Mock.ofType<dataworkspace.IExtension>();
dataWorkspaceMock.setup(x => x.getProjectsInWorkspace(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve([vscode.Uri.file(project1.projectFilePath), vscode.Uri.file(project2.projectFilePath)])); dataWorkspaceMock.setup(x => x.getProjectsInWorkspace(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve([vscode.Uri.file(project1.projectFilePath), vscode.Uri.file(project2.projectFilePath)]));
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: dataWorkspaceMock.object }); sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: dataWorkspaceMock.object });
sinon.stub(utils, 'getDacFxService').returns(<any>{
parseTSqlScript: (_: string, __: string) => {
return Promise.resolve({ containsCreateTableStatement: true });
}
});
// add project reference from project1 to project2 // add project reference from project1 to project2
await projController.addDatabaseReferenceCallback(project1, { await projController.addDatabaseReferenceCallback(project1, {
@@ -633,7 +647,11 @@ describe('ProjectsController', function (): void {
const showErrorMessageSpy = sinon.spy(vscode.window, 'showErrorMessage'); const showErrorMessageSpy = sinon.spy(vscode.window, 'showErrorMessage');
const dataWorkspaceMock = TypeMoq.Mock.ofType<dataworkspace.IExtension>(); const dataWorkspaceMock = TypeMoq.Mock.ofType<dataworkspace.IExtension>();
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: dataWorkspaceMock.object }); sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: dataWorkspaceMock.object });
sinon.stub(utils, 'getDacFxService').returns(<any>{
parseTSqlScript: (_: string, __: string) => {
return Promise.resolve({ containsCreateTableStatement: true });
}
});
// add dacpac reference to something in the same folder // add dacpac reference to something in the same folder
should(project1.databaseReferences.length).equal(0, 'There should not be any database references to start with'); should(project1.databaseReferences.length).equal(0, 'There should not be any database references to start with');

View File

@@ -140,6 +140,7 @@ export class MockDacFxService implements mssql.IDacFxService {
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); } public validateStreamingJob(_: string, __: string): Thenable<mssql.ValidateStreamingJobResult> { return Promise.resolve(mockDacFxResult); }
public parseTSqlScript(_: string, __: string): Thenable<mssql.ParseTSqlScriptResult> { return Promise.resolve({ containsCreateTableStatement: true }); }
} }
export function createContext(): TestContext { export function createContext(): TestContext {

View File

@@ -6,6 +6,7 @@
import * as path from 'path'; import * as path from 'path';
import * as os from 'os'; import * as os from 'os';
import * as constants from '../common/constants'; import * as constants from '../common/constants';
import * as templates from '../templates/templates';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import should = require('should'); import should = require('should');
@@ -30,6 +31,10 @@ export async function shouldThrowSpecificError(block: Function, expectedMessage:
export async function createTestSqlProjFile(contents: string, folderPath?: string): Promise<string> { export async function createTestSqlProjFile(contents: string, folderPath?: string): Promise<string> {
folderPath = folderPath ?? path.join(await generateTestFolderPath(), 'TestProject'); folderPath = folderPath ?? path.join(await generateTestFolderPath(), 'TestProject');
const macroDict: Record<string, string> = {
'PROJECT_DSP': constants.defaultDSP
};
contents = templates.macroExpansion(contents, macroDict);
return await createTestFile(contents, 'TestProject.sqlproj', folderPath); return await createTestFile(contents, 'TestProject.sqlproj', folderPath);
} }