Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import * as vscode from "vscode";
import { SqlMoveToSchema as loc } from "../constants/locConstants";
import { SqlMoveToSchema as loc, msgYes } from "../constants/locConstants";
import { cmdMoveToSchema } from "../constants/constants";
import SqlToolsServerClient from "./serviceclient";
import {
Expand Down Expand Up @@ -187,6 +187,18 @@ export class SqlMoveToSchemaProvider implements vscode.CodeActionProvider {
return;
}

// Warn if an object with the same name already exists in the target schema.
if (response.message && response.isWarning) {
const choice = await vscode.window.showWarningMessage(
response.message,
{ modal: true },
Comment thread
ssreerama marked this conversation as resolved.
msgYes,
);
if (choice !== msgYes) {
return; // user declined — do nothing silently
}
}
Comment thread
ssreerama marked this conversation as resolved.

const changes = response.changes as Record<string, SqlSymbolRenameTextEdit[]>;
const label = loc.previewLabel(targetSchema);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
SqlSymbolRenameRequest,
SqlSymbolRenameTextEdit,
} from "../models/contracts/languageService";
import { SqlSymbolRename as loc } from "../constants/locConstants";
import { SqlSymbolRename as loc, msgYes } from "../constants/locConstants";

/** Escapes a string for safe use inside an XML attribute value (e.g. an Include path). */
function escapeXmlAttribute(value: string): string {
Expand Down Expand Up @@ -152,6 +152,23 @@ export class SqlSymbolRenameProvider implements vscode.RenameProvider {
throw new Error(loc.renameOnlyInProjectFiles);
}

// Hard rejection — surface the error message and abort.
if (response.message && !response.isWarning) {
throw new Error(response.message);
}

// Name collision warning — ask the user before proceeding.
if (response.message && response.isWarning) {
const choice = await vscode.window.showWarningMessage(
response.message,
{ modal: true },
msgYes,
);
if (choice !== msgYes) {
return new vscode.WorkspaceEdit(); // user declined — apply nothing silently
}
}
Comment thread
ssreerama marked this conversation as resolved.

const workspaceEdit = new vscode.WorkspaceEdit();

if (!response.changes || Object.keys(response.changes).length === 0) {
Expand Down
14 changes: 14 additions & 0 deletions extensions/mssql/src/models/contracts/languageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,13 @@ export interface SqlSymbolRenameResponse {
*/
refactorLogContent: string | null;
newName: string;
/**
* When non-null, a message to surface to the user.
* If isWarning is true, show a confirmation dialog; otherwise show a blocking error.
*/
message?: string | null;
/** True when message is a confirmation warning; false (default) when it is a hard rejection. */
isWarning?: boolean;
Comment thread
ssreerama marked this conversation as resolved.
}

export namespace SqlSymbolRenameRequest {
Expand Down Expand Up @@ -204,6 +211,13 @@ export interface SqlMoveToSchemaResponse {
*/
refactorLogContent: string | null;
targetSchema: string;
/**
* When non-null, a message to surface to the user.
* If isWarning is true, show a confirmation dialog; otherwise show a blocking error.
*/
message?: string | null;
/** True when message is a confirmation warning; false (default) when it is a hard rejection. */
isWarning?: boolean;
Comment thread
ssreerama marked this conversation as resolved.
}

export namespace SqlMoveToSchemaRequest {
Expand Down
134 changes: 134 additions & 0 deletions extensions/mssql/test/unit/sqlSymbolRenameProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,79 @@ suite("SqlSymbolRenameProvider Tests", () => {
}),
);
});

test("returns empty WorkspaceEdit when STS returns warning message and user cancels", async () => {
const projUri = vscode.Uri.file(defaultProjFile);
findFilesStub.resolves([projUri]);
const fileUri = vscode.Uri.file(defaultSqlFile).toString();
sendRequestStub.withArgs(SqlSymbolRenameRequest.type).resolves({
changes: {
[fileUri]: [
{
range: {
start: { line: 0, character: 0 },
end: { line: 0, character: 7 },
},
newText: "Orders",
},
],
},
newName: "Orders",
message:
"A schema object with the name [Orders] already exists. Would you like to continue?",
isWarning: true,
Comment thread
ssreerama marked this conversation as resolved.
});
sandbox.stub(vscode.window, "showWarningMessage").resolves(undefined); // user dismissed/cancelled

const doc = makeDocument(sandbox);
const edit = await provider.provideRenameEdits(
doc,
new vscode.Position(0, 0),
"Orders",
token,
);

expect(edit).to.be.instanceOf(vscode.WorkspaceEdit);
expect(edit!.entries()).to.have.length(0); // empty — no changes applied
});

test("applies edits when STS returns warning message and user confirms", async () => {
const projUri = vscode.Uri.file(defaultProjFile);
findFilesStub.resolves([projUri]);
const fileUri = vscode.Uri.file(defaultSqlFile).toString();
sendRequestStub.withArgs(SqlSymbolRenameRequest.type).resolves({
changes: {
[fileUri]: [
{
range: {
start: { line: 0, character: 0 },
end: { line: 0, character: 7 },
},
newText: "Orders",
},
],
},
newName: "Orders",
message:
"A schema object with the name [Orders] already exists. Would you like to continue?",
isWarning: true,
Comment thread
ssreerama marked this conversation as resolved.
});
sandbox
.stub(vscode.window, "showWarningMessage")
.resolves("Yes" as vscode.MessageItem & string);

const doc = makeDocument(sandbox);
const edit = await provider.provideRenameEdits(
doc,
new vscode.Position(0, 0),
"Orders",
token,
);

expect(edit).to.be.instanceOf(vscode.WorkspaceEdit);
expect(edit!.entries()).to.have.length(1); // edits applied
expect(edit!.entries()[0][0].toString()).to.equal(fileUri);
});
});

// -------------------------------------------------------------------------
Expand Down Expand Up @@ -736,6 +809,67 @@ suite("SqlMoveToSchemaProvider Tests", () => {
moveLoc.applyEditFailed,
);
});

test("does not call applyEdit when warning message is returned and user dismisses", async () => {
const fileUri = vscode.Uri.file(defaultSqlFile).toString();
sendRequestStub
.withArgs(ListProjectSchemasRequest.type)
.resolves({ schemas: ["hr"] });
sendRequestStub.withArgs(SqlMoveToSchemaRequest.type).resolves({
changes: {
[fileUri]: [
{
range: {
start: { line: 0, character: 7 },
end: { line: 0, character: 14 },
},
newText: "[hr].[MyTable]",
},
],
},
message:
"A schema object with the name [hr].[MyTable] already exists. Would you like to continue?",
isWarning: true,
Comment thread
ssreerama marked this conversation as resolved.
});
messageBoxes.showWarningMessage.resolves(undefined); // user dismissed

const doc = makeMoveDocument(sandbox, { lineText: "SELECT MyTable" });
await provider.runMoveToSchema(doc, new vscode.Position(0, 7));

expect(applyEditStub).to.not.have.been.called;
});

test("calls applyEdit when warning message is returned and user confirms Yes", async () => {
const fileUri = vscode.Uri.file(defaultSqlFile).toString();
sendRequestStub
.withArgs(ListProjectSchemasRequest.type)
.resolves({ schemas: ["hr"] });
sendRequestStub.withArgs(SqlMoveToSchemaRequest.type).resolves({
changes: {
[fileUri]: [
{
range: {
start: { line: 0, character: 7 },
end: { line: 0, character: 14 },
},
newText: "[hr].[MyTable]",
},
],
},
message:
"A schema object with the name [hr].[MyTable] already exists. Would you like to continue?",
isWarning: true,
Comment thread
ssreerama marked this conversation as resolved.
});
messageBoxes.showWarningMessage.resolves("Yes" as vscode.MessageItem & string);

const doc = makeMoveDocument(sandbox, { lineText: "SELECT MyTable" });
await provider.runMoveToSchema(doc, new vscode.Position(0, 7));

expect(applyEditStub).to.have.been.calledWith(
sinon.match.instanceOf(vscode.WorkspaceEdit),
sinon.match({ isRefactoring: true }),
);
});
});
});
});
Loading