diff --git a/extensions/mssql/package.json b/extensions/mssql/package.json index 601d073b1d..9ab12f1c4b 100644 --- a/extensions/mssql/package.json +++ b/extensions/mssql/package.json @@ -505,7 +505,7 @@ }, { "command": "mssql.deleteObject", - "when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole)$/ && config.workbench.enablePreviewFeatures", + "when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole|Database)$/ && config.workbench.enablePreviewFeatures", "group": "0_query@2" }, { @@ -550,7 +550,7 @@ }, { "command": "mssql.deleteObject", - "when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole)$/ && config.workbench.enablePreviewFeatures", + "when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole|Database)$/ && config.workbench.enablePreviewFeatures", "group": "connection@2" }, { diff --git a/extensions/mssql/src/mssql.d.ts b/extensions/mssql/src/mssql.d.ts index d130ac1db0..7b5a9b83a5 100644 --- a/extensions/mssql/src/mssql.d.ts +++ b/extensions/mssql/src/mssql.d.ts @@ -1300,6 +1300,22 @@ declare module 'mssql' { */ schema: string | undefined; } + + export interface Database extends SqlObject { + owner?: string; + collationName?: string; + recoveryModel?: string; + compatibilityLevel?: string; + containmentType?: string; + } + + export interface DatabaseViewInfo extends ObjectViewInfo { + loginNames: string[]; + collationNames: string[]; + compatibilityLevels: string[]; + containmentTypes: string[]; + recoveryModels: string[]; + } } export interface IObjectManagementService { diff --git a/extensions/mssql/src/objectManagement/commands.ts b/extensions/mssql/src/objectManagement/commands.ts index 6825492de3..a795429c38 100644 --- a/extensions/mssql/src/objectManagement/commands.ts +++ b/extensions/mssql/src/objectManagement/commands.ts @@ -21,6 +21,7 @@ import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './ui/ import { ServerRoleDialog } from './ui/serverRoleDialog'; import { DatabaseRoleDialog } from './ui/databaseRoleDialog'; import { ApplicationRoleDialog } from './ui/applicationRoleDialog'; +import { DatabaseDialog } from './ui/databaseDialog'; export function registerObjectManagementCommands(appContext: AppContext) { // Notes: Change the second parameter to false to use the actual object management service. @@ -69,6 +70,9 @@ async function handleNewObjectDialogCommand(context: azdata.ObjectExplorerContex case FolderType.Users: objectType = ObjectManagement.NodeType.User; break; + case FolderType.Databases: + objectType = ObjectManagement.NodeType.Database; + break; default: throw new Error(`Unsupported folder type: ${context.nodeInfo!.objectType}`); } @@ -240,6 +244,8 @@ function getDialog(service: IObjectManagementService, dialogOptions: ObjectManag return new ServerRoleDialog(service, dialogOptions); case ObjectManagement.NodeType.User: return new UserDialog(service, dialogOptions); + case ObjectManagement.NodeType.Database: + return new DatabaseDialog(service, dialogOptions); default: throw new Error(`Unsupported object type: ${dialogOptions.objectType}`); } diff --git a/extensions/mssql/src/objectManagement/constants.ts b/extensions/mssql/src/objectManagement/constants.ts index ff412f5e22..ef4255445f 100644 --- a/extensions/mssql/src/objectManagement/constants.ts +++ b/extensions/mssql/src/objectManagement/constants.ts @@ -11,7 +11,8 @@ export const enum FolderType { DatabaseRoles = 'DatabaseRoles', ServerLevelLogins = 'ServerLevelLogins', ServerLevelServerRoles = 'ServerLevelServerRoles', - Users = 'Users' + Users = 'Users', + Databases = 'Databases' } export const PublicServerRoleName = 'public'; @@ -26,6 +27,7 @@ export const CreateApplicationRoleDocUrl = 'https://learn.microsoft.com/sql/t-sq export const AlterApplicationRoleDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/alter-application-role-transact-sql'; export const CreateDatabaseRoleDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/create-role-transact-sql'; export const AlterDatabaseRoleDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/alter-role-transact-sql'; +export const CreateDatabaseDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/create-database-transact-sql'; export const enum TelemetryActions { CreateObject = 'CreateObject', diff --git a/extensions/mssql/src/objectManagement/localizedConstants.ts b/extensions/mssql/src/objectManagement/localizedConstants.ts index 745f561b06..5ffce7aee9 100644 --- a/extensions/mssql/src/objectManagement/localizedConstants.ts +++ b/extensions/mssql/src/objectManagement/localizedConstants.ts @@ -22,6 +22,7 @@ export const ApplicationRoleTypeDisplayName: string = localize('objectManagement export const ApplicationRoleTypeDisplayNameInTitle: string = localize('objectManagement.ApplicationRoleTypeDisplayNameInTitle', "Application Role"); export const DatabaseRoleTypeDisplayName: string = localize('objectManagement.DatabaseRoleTypeDisplayName', "database role"); export const DatabaseRoleTypeDisplayNameInTitle: string = localize('objectManagement.DatabaseRoleTypeDisplayNameInTitle', "Database Role"); +export const DatabaseTypeDisplayNameInTitle: string = localize('objectManagement.DatabaseDisplayNameInTitle', "Database"); // Shared Strings export const FailedToRetrieveConnectionInfoErrorMessage: string = localize('objectManagement.noConnectionUriError', "Failed to retrieve the connection information, please reconnect and try again.") @@ -120,6 +121,7 @@ export function RenameObjectError(objectType: string, originalName: string, newN export const NameText = localize('objectManagement.nameLabel', "Name"); export const GeneralSectionHeader = localize('objectManagement.generalSectionHeader', "General"); export const AdvancedSectionHeader = localize('objectManagement.advancedSectionHeader', "Advanced"); +export const OptionsSectionHeader = localize('objectManagement.optionsSectionHeader', "Options"); export const PasswordText = localize('objectManagement.passwordLabel', "Password"); export const ConfirmPasswordText = localize('objectManagement.confirmPasswordLabel', "Confirm password"); export const EnabledText = localize('objectManagement.enabledLabel', "Enabled"); @@ -131,6 +133,11 @@ export const LoginNotSelectedError = localize('objectManagement.loginNotSelected export const MembershipSectionHeader = localize('objectManagement.membershipLabel', "Membership"); export const MemberSectionHeader = localize('objectManagement.membersLabel', "Members"); export const SchemaText = localize('objectManagement.schemaLabel', "Schema"); +export const DatabaseExistsError = (dbName: string) => localize('objectManagement.databaseExistsError', "Database '{0}' already exists. Choose a different database name.", dbName); +export const CollationText = localize('objectManagement.collationLabel', "Collation"); +export const RecoveryModelText = localize('objectManagement.recoveryModelLabel', "Recovery Model"); +export const CompatibilityLevelText = localize('objectManagement.compatibilityLevelLabel', "Compatibility Level"); +export const ContainmentTypeText = localize('objectManagement.containmentTypeLabel', "Containment Type"); // 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?"); @@ -203,7 +210,7 @@ export function getNodeTypeDisplayName(type: string, inTitle: boolean = false): case ObjectManagement.NodeType.Column: return ColumnTypeDisplayName; case ObjectManagement.NodeType.Database: - return DatabaseTypeDisplayName; + return inTitle ? DatabaseTypeDisplayNameInTitle : DatabaseTypeDisplayName; default: throw new Error(`Unknown node type: ${type}`); } diff --git a/extensions/mssql/src/objectManagement/ui/databaseDialog.ts b/extensions/mssql/src/objectManagement/ui/databaseDialog.ts new file mode 100644 index 0000000000..916c137691 --- /dev/null +++ b/extensions/mssql/src/objectManagement/ui/databaseDialog.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as azdata from 'azdata'; +import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase'; +import { IObjectManagementService, ObjectManagement } from 'mssql'; +import * as localizedConstants from '../localizedConstants'; +import { CreateDatabaseDocUrl } from '../constants'; + +export class DatabaseDialog extends ObjectManagementDialogBase { + private _nameInput: azdata.InputBoxComponent; + + constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) { + super(objectManagementService, options); + } + + protected override get helpUrl(): string { + return CreateDatabaseDocUrl; + } + + protected async initializeUI(): Promise { + let generalSection = this.initializeGeneralSection(); + let optionsSection = this.initializeOptionsSection(); + this.formContainer.addItems([generalSection, optionsSection]); + } + + private initializeGeneralSection(): azdata.GroupContainer { + let containers: azdata.Component[] = []; + this._nameInput = this.createInputBox(localizedConstants.NameText, async () => { + this.objectInfo.name = this._nameInput.value; + await this.runValidation(false); + }); + containers.push(this.createLabelInputContainer(localizedConstants.NameText, this._nameInput)); + + if (this.viewInfo.loginNames?.length > 0) { + let ownerDropbox = this.createDropdown(localizedConstants.OwnerText, async () => { + this.objectInfo.owner = ownerDropbox.value as string; + }, this.viewInfo.loginNames, this.viewInfo.loginNames[0]); + containers.push(this.createLabelInputContainer(localizedConstants.OwnerText, ownerDropbox)); + } + + return this.createGroup(localizedConstants.GeneralSectionHeader, containers, false); + } + + private initializeOptionsSection(): azdata.GroupContainer { + let containers: azdata.Component[] = []; + if (this.viewInfo.collationNames?.length > 0) { + let collationDropbox = this.createDropdown(localizedConstants.CollationText, async () => { + this.objectInfo.collationName = collationDropbox.value as string; + }, this.viewInfo.collationNames, this.viewInfo.collationNames[0]); + containers.push(this.createLabelInputContainer(localizedConstants.CollationText, collationDropbox)); + } + + if (this.viewInfo.recoveryModels?.length > 0) { + this.objectInfo.recoveryModel = this.viewInfo.recoveryModels[0]; + let recoveryDropbox = this.createDropdown(localizedConstants.RecoveryModelText, async () => { + this.objectInfo.recoveryModel = recoveryDropbox.value as string; + }, this.viewInfo.recoveryModels, this.viewInfo.recoveryModels[0]); + containers.push(this.createLabelInputContainer(localizedConstants.RecoveryModelText, recoveryDropbox)); + } + + if (this.viewInfo.compatibilityLevels?.length > 0) { + this.objectInfo.compatibilityLevel = this.viewInfo.compatibilityLevels[0]; + let compatibilityDropbox = this.createDropdown(localizedConstants.CompatibilityLevelText, async () => { + this.objectInfo.compatibilityLevel = compatibilityDropbox.value as string; + }, this.viewInfo.compatibilityLevels, this.viewInfo.compatibilityLevels[0]); + containers.push(this.createLabelInputContainer(localizedConstants.CompatibilityLevelText, compatibilityDropbox)); + } + + if (this.viewInfo.containmentTypes?.length > 0) { + this.objectInfo.containmentType = this.viewInfo.containmentTypes[0]; + let containmentDropbox = this.createDropdown(localizedConstants.ContainmentTypeText, async () => { + this.objectInfo.containmentType = containmentDropbox.value as string; + }, this.viewInfo.containmentTypes, this.viewInfo.containmentTypes[0]); + containers.push(this.createLabelInputContainer(localizedConstants.ContainmentTypeText, containmentDropbox)); + } + + return this.createGroup(localizedConstants.OptionsSectionHeader, containers, true, true); + } +}