mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-13 17:22:15 -05:00
Add Attach Database dialog (#24225)
This commit is contained in:
@@ -117,6 +117,11 @@
|
|||||||
"category": "MSSQL",
|
"category": "MSSQL",
|
||||||
"title": "%title.detachDatabase%"
|
"title": "%title.detachDatabase%"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "mssql.attachDatabase",
|
||||||
|
"category": "MSSQL",
|
||||||
|
"title": "%title.attachDatabase%"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "mssql.enableGroupBySchema",
|
"command": "mssql.enableGroupBySchema",
|
||||||
"category": "MSSQL",
|
"category": "MSSQL",
|
||||||
@@ -525,6 +530,10 @@
|
|||||||
{
|
{
|
||||||
"command": "mssql.detachDatabase",
|
"command": "mssql.detachDatabase",
|
||||||
"when": "false"
|
"when": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "mssql.attachDatabase",
|
||||||
|
"when": "false"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"objectExplorer/item/context": [
|
"objectExplorer/item/context": [
|
||||||
@@ -589,6 +598,11 @@
|
|||||||
"when": "connectionProvider == MSSQL && nodeType =~ /^(ServerLevelLogin|User|ServerLevelServerRole|ApplicationRole|DatabaseRole)$/ && !(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"
|
"group": "1_objectManagement@3"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "mssql.attachDatabase",
|
||||||
|
"when": "connectionProvider == MSSQL && nodeType == Folder && objectType == Databases && !isCloud && config.workbench.enablePreviewFeatures",
|
||||||
|
"group": "1_objectManagement"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "mssql.enableGroupBySchema",
|
"command": "mssql.enableGroupBySchema",
|
||||||
"when": "connectionProvider == MSSQL && nodeType && nodeType =~ /^(Server|Database)$/ && !config.mssql.objectExplorer.groupBySchema",
|
"when": "connectionProvider == MSSQL && nodeType && nodeType =~ /^(Server|Database)$/ && !config.mssql.objectExplorer.groupBySchema",
|
||||||
|
|||||||
@@ -190,5 +190,6 @@
|
|||||||
"title.objectProperties": "Properties (Preview)",
|
"title.objectProperties": "Properties (Preview)",
|
||||||
"title.dropObject": "Drop (Preview)",
|
"title.dropObject": "Drop (Preview)",
|
||||||
"title.renameObject": "Rename (Preview)",
|
"title.renameObject": "Rename (Preview)",
|
||||||
"title.detachDatabase": "Detach (Preview)"
|
"title.detachDatabase": "Detach (Preview)",
|
||||||
|
"title.attachDatabase": "Attach (Preview)"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import * as telemetry from '@microsoft/ads-extension-telemetry';
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import { ConnectParams } from 'dataprotocol-client/lib/protocol';
|
import { ConnectParams } from 'dataprotocol-client/lib/protocol';
|
||||||
import * as mssql from 'mssql';
|
import * as mssql from 'mssql';
|
||||||
|
import { DatabaseFileData } from 'mssql';
|
||||||
|
|
||||||
// ------------------------------- < Telemetry Sent Event > ------------------------------------
|
// ------------------------------- < Telemetry Sent Event > ------------------------------------
|
||||||
|
|
||||||
@@ -1673,6 +1674,33 @@ export namespace DropDatabaseRequest {
|
|||||||
export const type = new RequestType<DropDatabaseRequestParams, string, void, void>('objectManagement/dropDatabase');
|
export const type = new RequestType<DropDatabaseRequestParams, string, void, void>('objectManagement/dropDatabase');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AttachDatabaseRequestParams {
|
||||||
|
connectionUri: string;
|
||||||
|
databases: DatabaseFileData[];
|
||||||
|
generateScript: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace AttachDatabaseRequest {
|
||||||
|
export const type = new RequestType<AttachDatabaseRequestParams, string, void, void>('objectManagement/attachDatabase');
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetDataFolderRequestParams {
|
||||||
|
connectionUri: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace GetDataFolderRequest {
|
||||||
|
export const type = new RequestType<GetDataFolderRequestParams, string, void, void>('admin/getdatafolder');
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetAssociatedFilesRequestParams {
|
||||||
|
connectionUri: string;
|
||||||
|
primaryFilePath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace GetAssociatedFilesRequest {
|
||||||
|
export const type = new RequestType<GetAssociatedFilesRequestParams, string[], void, void>('admin/getassociatedfiles');
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------- < Object Management > ------------------------------------
|
// ------------------------------- < Object Management > ------------------------------------
|
||||||
|
|
||||||
// ------------------------------- < Encryption IV/KEY updation Event > ------------------------------------
|
// ------------------------------- < Encryption IV/KEY updation Event > ------------------------------------
|
||||||
|
|||||||
27
extensions/mssql/src/mssql.d.ts
vendored
27
extensions/mssql/src/mssql.d.ts
vendored
@@ -984,6 +984,14 @@ declare module 'mssql' {
|
|||||||
* @returns A string value representing the generated TSQL query if generateScript was set to true, and an empty string otherwise.
|
* @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<string>;
|
detachDatabase(connectionUri: string, objectUrn: string, dropConnections: boolean, updateStatistics: boolean, generateScript: boolean): Thenable<string>;
|
||||||
|
/**
|
||||||
|
* Attach one or more databases.
|
||||||
|
* @param connectionUri The URI of the server connection.
|
||||||
|
* @param databases The name, owner, and file paths for each database that will be attached.
|
||||||
|
* @param generateScript Whether to generate a TSQL script for the operation instead of detaching the database.
|
||||||
|
* @returns A string value representing the generated TSQL query if generateScript was set to true, and an empty string otherwise.
|
||||||
|
*/
|
||||||
|
attachDatabases(connectionUri: string, databases: DatabaseFileData[], generateScript: boolean): Thenable<string>;
|
||||||
/**
|
/**
|
||||||
* Drop a database.
|
* Drop a database.
|
||||||
* @param connectionUri The URI of the server connection.
|
* @param connectionUri The URI of the server connection.
|
||||||
@@ -994,6 +1002,25 @@ declare module 'mssql' {
|
|||||||
* @returns A string value representing the generated TSQL query if generateScript was set to true, and an empty string otherwise.
|
* @returns A string value representing the generated TSQL query if generateScript was set to true, and an empty string otherwise.
|
||||||
*/
|
*/
|
||||||
dropDatabase(connectionUri: string, objectUrn: string, dropConnections: boolean, deleteBackupHistory: boolean, generateScript: boolean): Thenable<string>;
|
dropDatabase(connectionUri: string, objectUrn: string, dropConnections: boolean, deleteBackupHistory: boolean, generateScript: boolean): Thenable<string>;
|
||||||
|
/**
|
||||||
|
* Gets the file path for the default database file folder for a SQL Server instance.
|
||||||
|
* @param connectionUri The URI of the connection for the specific server.
|
||||||
|
* @returns The file path to the data folder.
|
||||||
|
*/
|
||||||
|
getDataFolder(connectionUri: string): Thenable<string>;
|
||||||
|
/**
|
||||||
|
* Retrieves other database files associated with a specified primary file, such as Data, Log, and FileStream files.
|
||||||
|
* @param connectionUri The URI of the connection for the specific server.
|
||||||
|
* @param primaryFilePath The file path for the primary database file on the target server.
|
||||||
|
* @returns An array of file path strings for each of the associated files.
|
||||||
|
*/
|
||||||
|
getAssociatedFiles(connectionUri: string, primaryFilePath: string): Thenable<string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DatabaseFileData {
|
||||||
|
databaseName: string;
|
||||||
|
databaseFilePaths: string[];
|
||||||
|
owner: string;
|
||||||
}
|
}
|
||||||
// Object Management - End.
|
// Object Management - End.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ import { ApplicationRoleDialog } from './ui/applicationRoleDialog';
|
|||||||
import { DatabaseDialog } from './ui/databaseDialog';
|
import { DatabaseDialog } from './ui/databaseDialog';
|
||||||
import { ServerPropertiesDialog } from './ui/serverPropertiesDialog';
|
import { ServerPropertiesDialog } from './ui/serverPropertiesDialog';
|
||||||
import { DetachDatabaseDialog } from './ui/detachDatabaseDialog';
|
import { DetachDatabaseDialog } from './ui/detachDatabaseDialog';
|
||||||
import { DropDatabaseDialog as DropDatabaseDialog } from './ui/dropDatabaseDialog';
|
import { DropDatabaseDialog } from './ui/dropDatabaseDialog';
|
||||||
|
import { AttachDatabaseDialog } from './ui/attachDatabaseDialog';
|
||||||
|
|
||||||
export function registerObjectManagementCommands(appContext: AppContext) {
|
export function registerObjectManagementCommands(appContext: AppContext) {
|
||||||
// Notes: Change the second parameter to false to use the actual object management service.
|
// Notes: Change the second parameter to false to use the actual object management service.
|
||||||
@@ -47,6 +48,9 @@ export function registerObjectManagementCommands(appContext: AppContext) {
|
|||||||
appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.detachDatabase', async (context: azdata.ObjectExplorerContext) => {
|
appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.detachDatabase', async (context: azdata.ObjectExplorerContext) => {
|
||||||
await handleDetachDatabase(context, service);
|
await handleDetachDatabase(context, service);
|
||||||
}));
|
}));
|
||||||
|
appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.attachDatabase', async (context: azdata.ObjectExplorerContext) => {
|
||||||
|
await handleAttachDatabase(context, service);
|
||||||
|
}));
|
||||||
appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.dropDatabase', async (context: azdata.ObjectExplorerContext) => {
|
appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.dropDatabase', async (context: azdata.ObjectExplorerContext) => {
|
||||||
await handleDropDatabase(context, service);
|
await handleDropDatabase(context, service);
|
||||||
}));
|
}));
|
||||||
@@ -290,6 +294,34 @@ async function handleDetachDatabase(context: azdata.ObjectExplorerContext, servi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleAttachDatabase(context: azdata.ObjectExplorerContext, service: IObjectManagementService): Promise<void> {
|
||||||
|
const connectionUri = await getConnectionUri(context);
|
||||||
|
if (!connectionUri) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const parentUrn = await getParentUrn(context);
|
||||||
|
const options: ObjectManagementDialogOptions = {
|
||||||
|
connectionUri: connectionUri,
|
||||||
|
isNewObject: true,
|
||||||
|
database: context.connectionProfile!.databaseName!,
|
||||||
|
objectType: ObjectManagement.NodeType.Database,
|
||||||
|
objectName: '',
|
||||||
|
parentUrn: parentUrn,
|
||||||
|
objectExplorerContext: context
|
||||||
|
};
|
||||||
|
const dialog = new AttachDatabaseDialog(service, options);
|
||||||
|
await dialog.open();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
TelemetryReporter.createErrorEvent2(ObjectManagementViewName, TelemetryActions.OpenAttachDatabaseDialog, err).withAdditionalProperties({
|
||||||
|
objectType: context.nodeInfo!.nodeType
|
||||||
|
}).send();
|
||||||
|
console.error(err);
|
||||||
|
await vscode.window.showErrorMessage(objectManagementLoc.OpenAttachDatabaseDialogError(getErrorMessage(err)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleDropDatabase(context: azdata.ObjectExplorerContext, service: IObjectManagementService): Promise<void> {
|
async function handleDropDatabase(context: azdata.ObjectExplorerContext, service: IObjectManagementService): Promise<void> {
|
||||||
const connectionUri = await getConnectionUri(context);
|
const connectionUri = await getConnectionUri(context);
|
||||||
if (!connectionUri) {
|
if (!connectionUri) {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export const ViewMemoryServerPropertiesDocUrl = 'https://learn.microsoft.com/sql
|
|||||||
export const ViewProcessorsServerPropertiesDocUrl = 'https://learn.microsoft.com/sql/database-engine/configure-windows/server-properties-processors-page';
|
export const ViewProcessorsServerPropertiesDocUrl = 'https://learn.microsoft.com/sql/database-engine/configure-windows/server-properties-processors-page';
|
||||||
export const ViewSecurityServerPropertiesDocUrl = 'https://learn.microsoft.com/sql/database-engine/configure-windows/server-properties-security-page';
|
export const ViewSecurityServerPropertiesDocUrl = 'https://learn.microsoft.com/sql/database-engine/configure-windows/server-properties-security-page';
|
||||||
export const DetachDatabaseDocUrl = 'https://go.microsoft.com/fwlink/?linkid=2240322';
|
export const DetachDatabaseDocUrl = 'https://go.microsoft.com/fwlink/?linkid=2240322';
|
||||||
|
export const AttachDatabaseDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/attach-a-database#to-attach-a-database';
|
||||||
export const DatabaseGeneralPropertiesDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/database-properties-general-page';
|
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 DatabaseOptionsPropertiesDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/database-properties-options-page'
|
||||||
export const DropDatabaseDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/drop-database-transact-sql';
|
export const DropDatabaseDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/drop-database-transact-sql';
|
||||||
@@ -47,6 +48,7 @@ export const enum TelemetryActions {
|
|||||||
RenameObject = 'RenameObject',
|
RenameObject = 'RenameObject',
|
||||||
UpdateObject = 'UpdateObject',
|
UpdateObject = 'UpdateObject',
|
||||||
OpenDetachDatabaseDialog = 'OpenDetachDatabaseDialog',
|
OpenDetachDatabaseDialog = 'OpenDetachDatabaseDialog',
|
||||||
|
OpenAttachDatabaseDialog = 'OpenAttachDatabaseDialog',
|
||||||
OpenDropDatabaseDialog = 'OpenDropDatabaseDialog'
|
OpenDropDatabaseDialog = 'OpenDropDatabaseDialog'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,8 +47,12 @@ export const GrantColumnHeader = localize('objectManagement.grantColumnHeader',
|
|||||||
export const WithGrantColumnHeader = localize('objectManagement.withGrantColumnHeader', "With Grant");
|
export const WithGrantColumnHeader = localize('objectManagement.withGrantColumnHeader', "With Grant");
|
||||||
export const DenyColumnHeader = localize('objectManagement.denyColumnHeader', "Deny");
|
export const DenyColumnHeader = localize('objectManagement.denyColumnHeader', "Deny");
|
||||||
export const SelectSecurablesDialogTitle = localize('objectManagement.selectSecurablesDialogTitle', "Select Securables");
|
export const SelectSecurablesDialogTitle = localize('objectManagement.selectSecurablesDialogTitle', "Select Securables");
|
||||||
|
export const AddFileAriaLabel = localize('objectManagement.addFileText', "Add database files");
|
||||||
|
export const RemoveFileAriaLabel = localize('objectManagement.removeFileText', "Remove database file");
|
||||||
export const CreateObjectLabel = localize('objectManagement.createObjectLabel', "Create");
|
export const CreateObjectLabel = localize('objectManagement.createObjectLabel', "Create");
|
||||||
export const ApplyUpdatesLabel = localize('objectManagement.applyUpdatesLabel', "Apply");
|
export const ApplyUpdatesLabel = localize('objectManagement.applyUpdatesLabel', "Apply");
|
||||||
|
export const DataFileLabel = localize('objectManagement.dataFileLabel', "Data");
|
||||||
|
export const LogFileLabel = localize('objectManagement.logFileLabel', "Log");
|
||||||
|
|
||||||
export function ExplicitPermissionsTableLabelSelected(name: string): string { return localize('objectManagement.explicitPermissionsTableLabelSelected', "Explicit permissions for: {0}", name); }
|
export function ExplicitPermissionsTableLabelSelected(name: string): string { return localize('objectManagement.explicitPermissionsTableLabelSelected', "Explicit permissions for: {0}", name); }
|
||||||
export function EffectivePermissionsTableLabelSelected(name: string): string { return localize('objectManagement.effectivePermissionsTableLabelSelected', "Effective permissions for: {0}", name); }
|
export function EffectivePermissionsTableLabelSelected(name: string): string { return localize('objectManagement.effectivePermissionsTableLabelSelected', "Effective permissions for: {0}", name); }
|
||||||
@@ -109,6 +113,13 @@ export function OpenDropDatabaseDialogError(error: string): string {
|
|||||||
}, "An error occurred while opening the drop database dialog. {0}", error);
|
}, "An error occurred while opening the drop database dialog. {0}", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function OpenAttachDatabaseDialogError(error: string): string {
|
||||||
|
return localize({
|
||||||
|
key: 'objectManagement.openAttachDatabaseDialogError',
|
||||||
|
comment: ['{0}: error message.']
|
||||||
|
}, "An error occurred while opening the attach database dialog. {0}", error);
|
||||||
|
}
|
||||||
|
|
||||||
export function OpenObjectPropertiesDialogError(objectType: string, objectName: string, error: string): string {
|
export function OpenObjectPropertiesDialogError(objectType: string, objectName: string, error: string): string {
|
||||||
return localize({
|
return localize({
|
||||||
key: 'objectManagement.openObjectPropertiesDialogError',
|
key: 'objectManagement.openObjectPropertiesDialogError',
|
||||||
@@ -190,6 +201,15 @@ export const DatabaseFilePathLabel = localize('objectManagement.databaseFilePath
|
|||||||
export const DatabaseFileGroupLabel = localize('objectManagement.databaseFileGroup', "File Group");
|
export const DatabaseFileGroupLabel = localize('objectManagement.databaseFileGroup', "File Group");
|
||||||
export const DetachDatabaseOptions = localize('objectManagement.detachDatabaseOptions', "Detach Database Options");
|
export const DetachDatabaseOptions = localize('objectManagement.detachDatabaseOptions', "Detach Database Options");
|
||||||
export const DetachButtonLabel = localize('objectManagement.detachButtonLabel', "Detach");
|
export const DetachButtonLabel = localize('objectManagement.detachButtonLabel', "Detach");
|
||||||
|
export const AttachDatabaseDialogTitle = localize('objectManagement.attachDatabaseDialogTitle', "Attach Database (Preview)");
|
||||||
|
export const NoDatabaseFilesError = localize('objectManagement.doDatabaseFilesError', "No database files were specified to attach to the server.");
|
||||||
|
export const DatabasesToAttachLabel = localize('objectManagement.databasesToAttach', "Databases to Attach");
|
||||||
|
export const AssociatedFilesLabel = localize('objectManagement.associatedDatabaseFiles', "Associated Database Files");
|
||||||
|
export const MdfFileLocation = localize('objectManagement.mdfFileLocation', "MDF File Location");
|
||||||
|
export const DatabaseFilesFilterLabel = localize('objectManagement.databaseFilesFilterLabel', "Database Data Files")
|
||||||
|
export const DatabaseName = localize('objectManagement.databaseName', "DB Name");
|
||||||
|
export const AttachAsText = localize('objectManagement.attachAsText', "Attach As");
|
||||||
|
export const AttachButtonLabel = localize('objectManagement.attachButtonLabel', "Attach");
|
||||||
export const DropDatabaseDialogTitle = (dbName: string) => localize('objectManagement.dropDatabaseDialogTitle', "Drop Database - {0} (Preview)", dbName);
|
export const DropDatabaseDialogTitle = (dbName: string) => localize('objectManagement.dropDatabaseDialogTitle', "Drop Database - {0} (Preview)", dbName);
|
||||||
export const DropButtonLabel = localize('objectManagement.dropButtonLabel', "Drop");
|
export const DropButtonLabel = localize('objectManagement.dropButtonLabel', "Drop");
|
||||||
export const DropDatabaseOptions = localize('objectManagement.dropDatabaseOptions', "Drop Database Options");
|
export const DropDatabaseOptions = localize('objectManagement.dropDatabaseOptions', "Drop Database Options");
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import * as constants from '../constants';
|
|||||||
import * as contracts from '../contracts';
|
import * as contracts from '../contracts';
|
||||||
|
|
||||||
import { BaseService, ISqlOpsFeature, SqlOpsDataClient } from 'dataprotocol-client';
|
import { BaseService, ISqlOpsFeature, SqlOpsDataClient } from 'dataprotocol-client';
|
||||||
import { ObjectManagement, IObjectManagementService } from 'mssql';
|
import { ObjectManagement, IObjectManagementService, DatabaseFileData } from 'mssql';
|
||||||
import { ClientCapabilities } from 'vscode-languageclient';
|
import { ClientCapabilities } from 'vscode-languageclient';
|
||||||
import { AppContext } from '../appContext';
|
import { AppContext } from '../appContext';
|
||||||
|
|
||||||
@@ -75,6 +75,21 @@ export class ObjectManagementService extends BaseService implements IObjectManag
|
|||||||
const params: contracts.DropDatabaseRequestParams = { connectionUri, objectUrn, dropConnections, deleteBackupHistory, generateScript };
|
const params: contracts.DropDatabaseRequestParams = { connectionUri, objectUrn, dropConnections, deleteBackupHistory, generateScript };
|
||||||
return this.runWithErrorHandling(contracts.DropDatabaseRequest.type, params);
|
return this.runWithErrorHandling(contracts.DropDatabaseRequest.type, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async attachDatabases(connectionUri: string, databases: DatabaseFileData[], generateScript: boolean): Promise<string> {
|
||||||
|
const params: contracts.AttachDatabaseRequestParams = { connectionUri, databases, generateScript };
|
||||||
|
return this.runWithErrorHandling(contracts.AttachDatabaseRequest.type, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDataFolder(connectionUri: string): Promise<string> {
|
||||||
|
const params: contracts.GetDataFolderRequestParams = { connectionUri };
|
||||||
|
return this.runWithErrorHandling(contracts.GetDataFolderRequest.type, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAssociatedFiles(connectionUri: string, primaryFilePath: string): Promise<string[]> {
|
||||||
|
const params: contracts.GetAssociatedFilesRequestParams = { connectionUri, primaryFilePath };
|
||||||
|
return this.runWithErrorHandling(contracts.GetAssociatedFilesRequest.type, params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ServerLevelSecurableTypes: SecurableTypeMetadata[] = [
|
const ServerLevelSecurableTypes: SecurableTypeMetadata[] = [
|
||||||
@@ -246,10 +261,22 @@ export class TestObjectManagementService implements IObjectManagementService {
|
|||||||
return this.delayAndResolve('');
|
return this.delayAndResolve('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async attachDatabases(connectionUri: string, databases: DatabaseFileData[], generateScript: boolean): Promise<string> {
|
||||||
|
return this.delayAndResolve('');
|
||||||
|
}
|
||||||
|
|
||||||
dropDatabase(connectionUri: string, objectUrn: string, dropConnections: boolean, deleteBackupHistory: boolean, generateScript: boolean): Thenable<string> {
|
dropDatabase(connectionUri: string, objectUrn: string, dropConnections: boolean, deleteBackupHistory: boolean, generateScript: boolean): Thenable<string> {
|
||||||
return this.delayAndResolve('');
|
return this.delayAndResolve('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getDataFolder(connectionUri: string): Promise<string> {
|
||||||
|
return this.delayAndResolve('');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAssociatedFiles(connectionUri: string, primaryFilePath: string): Promise<string[]> {
|
||||||
|
return this.delayAndResolve([]);
|
||||||
|
}
|
||||||
|
|
||||||
private generateSearchResult(objectType: ObjectManagement.NodeType, schema: string | undefined, count: number): ObjectManagement.SearchResultItem[] {
|
private generateSearchResult(objectType: ObjectManagement.NodeType, schema: string | undefined, count: number): ObjectManagement.SearchResultItem[] {
|
||||||
let items: ObjectManagement.SearchResultItem[] = [];
|
let items: ObjectManagement.SearchResultItem[] = [];
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
|
|||||||
188
extensions/mssql/src/objectManagement/ui/attachDatabaseDialog.ts
Normal file
188
extensions/mssql/src/objectManagement/ui/attachDatabaseDialog.ts
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 { DatabaseFileData, IObjectManagementService, ObjectManagement } from 'mssql';
|
||||||
|
import { Database, DatabaseViewInfo } from '../interfaces';
|
||||||
|
import { AttachDatabaseDocUrl } from '../constants';
|
||||||
|
import * as loc from '../localizedConstants';
|
||||||
|
import { RemoveText } from '../../ui/localizedConstants';
|
||||||
|
import { DefaultMinTableRowCount, DialogButton, getTableHeight } from '../../ui/dialogBase';
|
||||||
|
import path = require('path');
|
||||||
|
import { getErrorMessage } from '../../utils';
|
||||||
|
|
||||||
|
export class AttachDatabaseDialog extends ObjectManagementDialogBase<Database, DatabaseViewInfo> {
|
||||||
|
private _databasesToAttach: DatabaseFileData[] = [];
|
||||||
|
private _databasesTable: azdata.TableComponent;
|
||||||
|
private _associatedFilesTable: azdata.TableComponent;
|
||||||
|
private _databaseFiles: string[][] = [];
|
||||||
|
private readonly _fileFilters: azdata.window.FileFilters[] = [{ label: loc.DatabaseFilesFilterLabel, filters: ['*.mdf'] }];
|
||||||
|
|
||||||
|
private _nameField: azdata.InputBoxComponent;
|
||||||
|
private _nameContainer: azdata.FlexContainer;
|
||||||
|
|
||||||
|
private _ownerDropdown: azdata.DropDownComponent;
|
||||||
|
private _ownerContainer: azdata.FlexContainer;
|
||||||
|
|
||||||
|
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
|
||||||
|
super(objectManagementService, options, loc.AttachDatabaseDialogTitle, 'AttachDatabase');
|
||||||
|
this.dialogObject.okButton.label = loc.AttachButtonLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override get isDirty(): boolean {
|
||||||
|
return this._databasesToAttach.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async initializeUI(): Promise<void> {
|
||||||
|
let filesSection = this.initializeAttachSection();
|
||||||
|
let associatedSection = this.initializeAssociatedFilesSection();
|
||||||
|
this.formContainer.addItems([filesSection, associatedSection]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeAttachSection(): azdata.GroupContainer {
|
||||||
|
const columns = [loc.MdfFileLocation, loc.DatabaseName];
|
||||||
|
this._databasesTable = this.createTable(loc.DatabasesToAttachLabel, columns, []);
|
||||||
|
this.disposables.push(this._databasesTable.onRowSelected(() => this.onFileRowSelected()))
|
||||||
|
|
||||||
|
let addButton: DialogButton = {
|
||||||
|
buttonAriaLabel: loc.AddFileAriaLabel,
|
||||||
|
buttonHandler: async () => await this.onAddFilesButtonClicked()
|
||||||
|
};
|
||||||
|
let removeButton: DialogButton = {
|
||||||
|
buttonAriaLabel: RemoveText,
|
||||||
|
buttonHandler: async () => await this.onRemoveFilesButtonClicked()
|
||||||
|
};
|
||||||
|
const buttonContainer = this.addButtonsForTable(this._databasesTable, addButton, removeButton);
|
||||||
|
|
||||||
|
this._nameField = this.createInputBox(async newValue => {
|
||||||
|
let selectedRow = this._databasesTable.selectedRows[0];
|
||||||
|
let dbFile = this._databasesToAttach[selectedRow];
|
||||||
|
dbFile.databaseName = newValue;
|
||||||
|
}, {});
|
||||||
|
this._nameContainer = this.createLabelInputContainer(loc.AttachAsText, this._nameField);
|
||||||
|
|
||||||
|
this._ownerDropdown = this.createDropdown(loc.OwnerText, async newValue => {
|
||||||
|
let selectedRow = this._databasesTable.selectedRows[0];
|
||||||
|
let dbFile = this._databasesToAttach[selectedRow];
|
||||||
|
dbFile.owner = newValue;
|
||||||
|
}, this.viewInfo.loginNames.options, this.viewInfo.loginNames.options[this.viewInfo.loginNames.defaultValueIndex]);
|
||||||
|
this._ownerContainer = this.createLabelInputContainer(loc.OwnerText, this._ownerDropdown);
|
||||||
|
|
||||||
|
// Hide input controls until we have files in the table
|
||||||
|
this._nameContainer.display = 'none';
|
||||||
|
this._ownerContainer.display = 'none';
|
||||||
|
|
||||||
|
return this.createGroup(loc.DatabasesToAttachLabel, [this._databasesTable, buttonContainer, this._nameContainer, this._ownerContainer], false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeAssociatedFilesSection(): azdata.GroupContainer {
|
||||||
|
const columns = [loc.DatabaseFileNameLabel, loc.DatabaseFileTypeLabel, loc.DatabaseFilePathLabel];
|
||||||
|
this._associatedFilesTable = this.createTable(loc.DatabaseFilesLabel, columns, []);
|
||||||
|
return this.createGroup(loc.AssociatedFilesLabel, [this._associatedFilesTable], false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onFileRowSelected(): Promise<void> {
|
||||||
|
if (this._databasesTable.selectedRows?.length > 0) {
|
||||||
|
let selectedRow = this._databasesTable.selectedRows[0];
|
||||||
|
let dbFile = this._databasesToAttach[selectedRow];
|
||||||
|
|
||||||
|
this._nameField.value = dbFile.databaseName;
|
||||||
|
this._ownerDropdown.value = dbFile.owner;
|
||||||
|
|
||||||
|
await this.updateAssociatedFilesTable(dbFile.databaseFilePaths);
|
||||||
|
} else {
|
||||||
|
await this.updateAssociatedFilesTable([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateAssociatedFilesTable(filePaths: string[]): Promise<void> {
|
||||||
|
let tableRows = filePaths.map(filePath => {
|
||||||
|
let ext = path.extname(filePath);
|
||||||
|
let fileType = ext === '.ldf' ? loc.LogFileLabel : loc.DataFileLabel;
|
||||||
|
let fileName = path.basename(filePath, ext);
|
||||||
|
return [fileName, fileType, filePath];
|
||||||
|
});
|
||||||
|
await this._associatedFilesTable.updateProperties({
|
||||||
|
data: tableRows,
|
||||||
|
height: getTableHeight(tableRows.length, DefaultMinTableRowCount)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onAddFilesButtonClicked(): Promise<void> {
|
||||||
|
try {
|
||||||
|
let dataFolder = await this.objectManagementService.getDataFolder(this.options.connectionUri);
|
||||||
|
let filePath = await azdata.window.openServerFileBrowserDialog(this.options.connectionUri, dataFolder, this._fileFilters);
|
||||||
|
if (filePath) {
|
||||||
|
let owner = this.viewInfo.loginNames?.options[this.viewInfo.loginNames.defaultValueIndex];
|
||||||
|
let fileName = path.basename(filePath, path.extname(filePath));
|
||||||
|
let tableRow = [filePath, fileName];
|
||||||
|
|
||||||
|
// Associated files will also include the primary file, so we don't need to add it to the array again
|
||||||
|
let associatedFiles = await this.objectManagementService.getAssociatedFiles(this.options.connectionUri, filePath) ?? [];
|
||||||
|
|
||||||
|
this._databaseFiles.push(tableRow);
|
||||||
|
this._databasesToAttach.push({ databaseName: fileName, databaseFilePaths: associatedFiles, owner });
|
||||||
|
|
||||||
|
this._nameContainer.display = 'block';
|
||||||
|
this._ownerContainer.display = 'block';
|
||||||
|
|
||||||
|
await this.updateTableData();
|
||||||
|
this._databasesTable.setActiveCell(this._databasesToAttach.length - 1, 0);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.dialogObject.message = {
|
||||||
|
text: getErrorMessage(error),
|
||||||
|
level: azdata.window.MessageLevel.Error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onRemoveFilesButtonClicked(): Promise<void> {
|
||||||
|
let selectedRows = this._databasesTable.selectedRows;
|
||||||
|
let deletedRowCount = 0;
|
||||||
|
for (let row of selectedRows) {
|
||||||
|
let index = row - deletedRowCount;
|
||||||
|
this._databaseFiles.splice(index, 1);
|
||||||
|
this._databasesToAttach.splice(index, 1);
|
||||||
|
deletedRowCount++;
|
||||||
|
}
|
||||||
|
if (this._databasesToAttach.length === 0) {
|
||||||
|
this._nameContainer.display = 'none';
|
||||||
|
this._ownerContainer.display = 'none';
|
||||||
|
} else {
|
||||||
|
this._databasesTable.setActiveCell(0, 0);
|
||||||
|
}
|
||||||
|
await this.updateTableData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateTableData(): Promise<void> {
|
||||||
|
await this._databasesTable.updateProperties({
|
||||||
|
data: this._databaseFiles,
|
||||||
|
height: getTableHeight(this._databaseFiles.length, DefaultMinTableRowCount)
|
||||||
|
});
|
||||||
|
this.onFormFieldChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override get helpUrl(): string {
|
||||||
|
return AttachDatabaseDocUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async validateInput(): Promise<string[]> {
|
||||||
|
let errors = [];
|
||||||
|
if (this._databasesToAttach.length === 0) {
|
||||||
|
errors.push(loc.NoDatabaseFilesError);
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async saveChanges(contextId: string, object: ObjectManagement.SqlObject): Promise<void> {
|
||||||
|
await this.objectManagementService.attachDatabases(this.options.connectionUri, this._databasesToAttach, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async generateScript(): Promise<string> {
|
||||||
|
return await this.objectManagementService.attachDatabases(this.options.connectionUri, this._databasesToAttach, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user