diff --git a/extensions/mssql/config.json b/extensions/mssql/config.json index 9ae583e52d..cdb48fd76b 100644 --- a/extensions/mssql/config.json +++ b/extensions/mssql/config.json @@ -1,6 +1,6 @@ { "downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}", - "version": "4.9.0.10", + "version": "4.9.0.13", "downloadFileNames": { "Windows_86": "win-x86-net7.0.zip", "Windows_64": "win-x64-net7.0.zip", diff --git a/extensions/mssql/package.json b/extensions/mssql/package.json index 66b7a69b68..ee9f57bc94 100644 --- a/extensions/mssql/package.json +++ b/extensions/mssql/package.json @@ -95,6 +95,12 @@ "title": "%title.objectProperties%", "icon": "$(edit)" }, + { + "command": "mssql.deleteDatabase", + "category": "MSSQL", + "title": "%title.deleteObject%", + "icon": "$(trash)" + }, { "command": "mssql.deleteObject", "category": "MSSQL", @@ -504,6 +510,10 @@ "command": "mssql.objectProperties", "when": "false" }, + { + "command": "mssql.deleteDatabase", + "when": "false" + }, { "command": "mssql.deleteObject", "when": "false" @@ -569,9 +579,14 @@ "when": "connectionProvider == MSSQL && nodeType == Database && !isCloud && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures && (productQualityType =~ /^(insider|dev)$/ || isDevelopment)", "group": "1_objectManagement@2" }, + { + "command": "mssql.deleteDatabase", + "when": "connectionProvider == MSSQL && nodeType == Database && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures", + "group": "1_objectManagement@3" + }, { "command": "mssql.deleteObject", - "when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole|Database)$/ && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures", + "when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole)$/ && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures", "group": "1_objectManagement@3" }, { @@ -614,9 +629,14 @@ "when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole)$/ && config.workbench.enablePreviewFeatures", "group": "inline@2" }, + { + "command": "mssql.deleteDatabase", + "when": "connectionProvider == MSSQL && nodeType == Database && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures", + "group": "inline@3" + }, { "command": "mssql.deleteObject", - "when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole|Database)$/ && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures", + "when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole)$/ && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures", "group": "inline@3" } ], @@ -661,9 +681,14 @@ "when": "connectionProvider == MSSQL && nodeType == Column && config.workbench.enablePreviewFeatures && nodePath =~ /^.*\\/Tables\\/.*\\/Columns\\/.*$/", "group": "1_objectManagement@1" }, + { + "command": "mssql.deleteDatabase", + "when": "connectionProvider == MSSQL && nodeType == Database && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures", + "group": "1_objectManagement@3" + }, { "command": "mssql.deleteObject", - "when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole|Database)$/ && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures", + "when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole)$/ && !(nodePath =~ /^.*\\/System Databases\\/.*$/) && config.workbench.enablePreviewFeatures", "group": "1_objectManagement@3" }, { diff --git a/extensions/mssql/src/contracts.ts b/extensions/mssql/src/contracts.ts index c57b16a0fe..2c77727c50 100644 --- a/extensions/mssql/src/contracts.ts +++ b/extensions/mssql/src/contracts.ts @@ -1645,6 +1645,18 @@ export namespace DetachDatabaseRequest { export const type = new RequestType('objectManagement/detachDatabase'); } +export interface DropDatabaseRequestParams { + connectionUri: string; + objectUrn: string; + dropConnections: boolean; + deleteBackupHistory: boolean; + generateScript: boolean; +} + +export namespace DropDatabaseRequest { + export const type = new RequestType('objectManagement/dropDatabase'); +} + // ------------------------------- < Object Management > ------------------------------------ // ------------------------------- < Encryption IV/KEY updation Event > ------------------------------------ diff --git a/extensions/mssql/src/mssql.d.ts b/extensions/mssql/src/mssql.d.ts index 412a979b02..88a48fa75a 100644 --- a/extensions/mssql/src/mssql.d.ts +++ b/extensions/mssql/src/mssql.d.ts @@ -984,6 +984,8 @@ declare module 'mssql' { * @returns A string value representing the generated TSQL query if generateScript was set to true, and an empty string otherwise. */ detachDatabase(connectionUri: string, objectUrn: string, dropConnections: boolean, updateStatistics: boolean, generateScript: boolean): Thenable; + + deleteDatabase(connectionUri: string, objectUrn: string, dropConnections: boolean, deleteBackupHistory: boolean, generateScript: boolean): Thenable; } // Object Management - End. } diff --git a/extensions/mssql/src/objectManagement/commands.ts b/extensions/mssql/src/objectManagement/commands.ts index 87a24feb26..53b8e4979b 100644 --- a/extensions/mssql/src/objectManagement/commands.ts +++ b/extensions/mssql/src/objectManagement/commands.ts @@ -24,6 +24,7 @@ import { ApplicationRoleDialog } from './ui/applicationRoleDialog'; import { DatabaseDialog } from './ui/databaseDialog'; import { ServerPropertiesDialog } from './ui/serverPropertiesDialog'; import { DetachDatabaseDialog } from './ui/detachDatabaseDialog'; +import { DeleteDatabaseDialog } from './ui/deleteDatabaseDialog'; export function registerObjectManagementCommands(appContext: AppContext) { // Notes: Change the second parameter to false to use the actual object management service. @@ -46,6 +47,9 @@ export function registerObjectManagementCommands(appContext: AppContext) { appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.detachDatabase', async (context: azdata.ObjectExplorerContext) => { await handleDetachDatabase(context, service); })); + appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.deleteDatabase', async (context: azdata.ObjectExplorerContext) => { + await handleDeleteDatabase(context, service); + })); } function getObjectManagementService(appContext: AppContext, useTestService: boolean): IObjectManagementService { @@ -286,6 +290,35 @@ async function handleDetachDatabase(context: azdata.ObjectExplorerContext, servi } } +async function handleDeleteDatabase(context: azdata.ObjectExplorerContext, service: IObjectManagementService): Promise { + const connectionUri = await getConnectionUri(context); + if (!connectionUri) { + return; + } + try { + const parentUrn = await getParentUrn(context); + const options: ObjectManagementDialogOptions = { + connectionUri: connectionUri, + isNewObject: false, + database: context.connectionProfile!.databaseName!, + objectType: context.nodeInfo.nodeType as ObjectManagement.NodeType, + objectName: context.nodeInfo.label, + parentUrn: parentUrn, + objectUrn: context.nodeInfo!.metadata!.urn, + objectExplorerContext: context + }; + const dialog = new DeleteDatabaseDialog(service, options); + await dialog.open(); + } + catch (err) { + TelemetryReporter.createErrorEvent2(ObjectManagementViewName, TelemetryActions.OpenDeleteDatabaseDialog, err).withAdditionalProperties({ + objectType: context.nodeInfo!.nodeType + }).send(); + console.error(err); + await vscode.window.showErrorMessage(objectManagementLoc.OpenDeleteDatabaseDialogError(getErrorMessage(err))); + } +} + function getDialog(service: IObjectManagementService, dialogOptions: ObjectManagementDialogOptions): ObjectManagementDialogBase> { switch (dialogOptions.objectType) { case ObjectManagement.NodeType.ApplicationRole: diff --git a/extensions/mssql/src/objectManagement/constants.ts b/extensions/mssql/src/objectManagement/constants.ts index 2e8c5f6417..0e3c81f454 100644 --- a/extensions/mssql/src/objectManagement/constants.ts +++ b/extensions/mssql/src/objectManagement/constants.ts @@ -33,6 +33,7 @@ export const ViewMemoryServerPropertiesDocUrl = 'https://learn.microsoft.com/sql export const DetachDatabaseDocUrl = 'https://go.microsoft.com/fwlink/?linkid=2240322'; export const DatabaseGeneralPropertiesDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/database-properties-general-page'; export const DatabaseOptionsPropertiesDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/database-properties-options-page' +export const DeleteDatabaseDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/drop-database-transact-sql'; export const enum TelemetryActions { CreateObject = 'CreateObject', @@ -41,7 +42,8 @@ export const enum TelemetryActions { OpenPropertiesDialog = 'OpenPropertiesDialog', RenameObject = 'RenameObject', UpdateObject = 'UpdateObject', - OpenDetachDatabaseDialog = 'OpenDetachDatabaseDialog' + OpenDetachDatabaseDialog = 'OpenDetachDatabaseDialog', + OpenDeleteDatabaseDialog = 'OpenDeleteDatabaseDialog' } export const ObjectManagementViewName = 'ObjectManagement'; diff --git a/extensions/mssql/src/objectManagement/interfaces.ts b/extensions/mssql/src/objectManagement/interfaces.ts index a108821c02..73eeb953bc 100644 --- a/extensions/mssql/src/objectManagement/interfaces.ts +++ b/extensions/mssql/src/objectManagement/interfaces.ts @@ -458,6 +458,8 @@ export interface Database extends ObjectManagement.SqlObject { export interface DatabaseViewInfo extends ObjectManagement.ObjectViewInfo { isAzureDB: boolean; + isManagedInstance: boolean; + isSqlOnDemand: boolean; loginNames?: OptionsCollection; collationNames?: OptionsCollection; compatibilityLevels?: OptionsCollection; diff --git a/extensions/mssql/src/objectManagement/localizedConstants.ts b/extensions/mssql/src/objectManagement/localizedConstants.ts index 2e69a22a05..3fc119b681 100644 --- a/extensions/mssql/src/objectManagement/localizedConstants.ts +++ b/extensions/mssql/src/objectManagement/localizedConstants.ts @@ -102,6 +102,13 @@ export function OpenDetachDatabaseDialogError(error: string): string { }, "An error occurred while opening the detach database dialog. {0}", error); } +export function OpenDeleteDatabaseDialogError(error: string): string { + return localize({ + key: 'objectManagement.openDeleteDatabaseDialogError', + comment: ['{0}: error message.'] + }, "An error occurred while opening the delete database dialog. {0}", error); +} + export function OpenObjectPropertiesDialogError(objectType: string, objectName: string, error: string): string { return localize({ key: 'objectManagement.openObjectPropertiesDialogError', @@ -182,6 +189,12 @@ export const DatabaseFilePathLabel = localize('objectManagement.databaseFilePath export const DatabaseFileGroupLabel = localize('objectManagement.databaseFileGroup', "File Group"); export const DetachDatabaseOptions = localize('objectManagement.detachDatabaseOptions', "Detach Database Options"); export const DetachButtonLabel = localize('objectManagement.detachButtonLabel', "Detach"); +export const DeleteDatabaseDialogTitle = (dbName: string) => localize('objectManagement.deleteDatabaseDialogTitle', "Delete Database - {0} (Preview)", dbName); +export const DeleteButtonLabel = localize('objectManagement.deleteButtonLabel', "Delete"); +export const DeleteDatabaseOptions = localize('objectManagement.deleteDatabaseOptions', "Delete Database Options"); +export const DeleteDropConnections = localize('objectManagement.deleteDropConnections', "Close existing connections"); +export const DeleteDropBackupHistory = localize('objectManagement.deleteDropBackupHistory', "Delete backup and restore history information for database"); +export const DatabaseDetailsLabel = localize('objectManagement.databaseDetails', "Database Details"); // Login export const BlankPasswordConfirmationText: string = localize('objectManagement.blankPasswordConfirmation', "Creating a login with a blank password is a security risk. Are you sure you want to continue?"); diff --git a/extensions/mssql/src/objectManagement/objectManagementService.ts b/extensions/mssql/src/objectManagement/objectManagementService.ts index 07d53dc0ca..bf0ab454bd 100644 --- a/extensions/mssql/src/objectManagement/objectManagementService.ts +++ b/extensions/mssql/src/objectManagement/objectManagementService.ts @@ -70,6 +70,11 @@ export class ObjectManagementService extends BaseService implements IObjectManag const params: contracts.DetachDatabaseRequestParams = { connectionUri, objectUrn, dropConnections, updateStatistics, generateScript }; return this.runWithErrorHandling(contracts.DetachDatabaseRequest.type, params); } + + async deleteDatabase(connectionUri: string, objectUrn: string, dropConnections: boolean, deleteBackupHistory: boolean, generateScript: boolean): Promise { + const params: contracts.DropDatabaseRequestParams = { connectionUri, objectUrn, dropConnections, deleteBackupHistory, generateScript }; + return this.runWithErrorHandling(contracts.DropDatabaseRequest.type, params); + } } const ServerLevelSecurableTypes: SecurableTypeMetadata[] = [ @@ -241,6 +246,10 @@ export class TestObjectManagementService implements IObjectManagementService { return this.delayAndResolve(''); } + deleteDatabase(connectionUri: string, objectUrn: string, dropConnections: boolean, deleteBackupHistory: boolean, generateScript: boolean): Thenable { + return this.delayAndResolve(''); + } + private generateSearchResult(objectType: ObjectManagement.NodeType, schema: string | undefined, count: number): ObjectManagement.SearchResultItem[] { let items: ObjectManagement.SearchResultItem[] = []; for (let i = 0; i < count; i++) { diff --git a/extensions/mssql/src/objectManagement/ui/deleteDatabaseDialog.ts b/extensions/mssql/src/objectManagement/ui/deleteDatabaseDialog.ts new file mode 100644 index 0000000000..2cc4f59f48 --- /dev/null +++ b/extensions/mssql/src/objectManagement/ui/deleteDatabaseDialog.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase'; +import { IObjectManagementService, ObjectManagement } from 'mssql'; +import { Database, DatabaseViewInfo } from '../interfaces'; +import { DeleteDatabaseDocUrl } from '../constants'; +import { DeleteButtonLabel, DeleteDatabaseDialogTitle, DeleteDropBackupHistory, DeleteDropConnections, DeleteDatabaseOptions, NameText, OwnerText, StatusText, DatabaseDetailsLabel } from '../localizedConstants'; + +export class DeleteDatabaseDialog extends ObjectManagementDialogBase { + private _dropConnections = false; + private _deleteBackupHistory = false; + + constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) { + super(objectManagementService, options, DeleteDatabaseDialogTitle(options.database), 'DeleteDatabase'); + this.dialogObject.okButton.label = DeleteButtonLabel; + } + + protected override get isDirty(): boolean { + return true; + } + + protected async initializeUI(): Promise { + let components = []; + + let tableData = [[this.objectInfo.name, this.objectInfo.owner ?? '', this.objectInfo.status ?? '']]; + let columnNames = [NameText, OwnerText, StatusText]; + let fileTable = this.createTable(DatabaseDetailsLabel, columnNames, tableData); + let tableGroup = this.createGroup(DatabaseDetailsLabel, [fileTable], false); + components.push(tableGroup); + + if (!this.viewInfo.isAzureDB && !this.viewInfo.isManagedInstance && !this.viewInfo.isSqlOnDemand) { + let connCheckbox = this.createCheckbox(DeleteDropConnections, async checked => { + this._dropConnections = checked; + }); + let updateCheckbox = this.createCheckbox(DeleteDropBackupHistory, async checked => { + this._deleteBackupHistory = checked; + }); + let checkboxGroup = this.createGroup(DeleteDatabaseOptions, [connCheckbox, updateCheckbox], false); + components.push(checkboxGroup); + } + this.formContainer.addItems(components); + } + + protected override get helpUrl(): string { + return DeleteDatabaseDocUrl; + } + + protected override async saveChanges(contextId: string, object: ObjectManagement.SqlObject): Promise { + await this.objectManagementService.deleteDatabase(this.options.connectionUri, this.options.objectUrn, this._dropConnections, this._deleteBackupHistory, false); + } + + protected override async generateScript(): Promise { + return await this.objectManagementService.deleteDatabase(this.options.connectionUri, this.options.objectUrn, this._dropConnections, this._deleteBackupHistory, true); + } + + protected override async validateInput(): Promise { + return []; + } +}