diff --git a/extensions/mssql/config.json b/extensions/mssql/config.json index 66ba6a218693..86dda65dd530 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.25", + "version": "4.9.0.26", "downloadFileNames": { "Windows_86": "win-x86-net7.0.zip", "Windows_64": "win-x64-net7.0.zip", diff --git a/extensions/mssql/src/objectManagement/constants.ts b/extensions/mssql/src/objectManagement/constants.ts index 8536ed4fc565..0bb351fb552c 100644 --- a/extensions/mssql/src/objectManagement/constants.ts +++ b/extensions/mssql/src/objectManagement/constants.ts @@ -39,6 +39,7 @@ export const DatabaseOptionsPropertiesDocUrl = 'https://learn.microsoft.com/sql/ export const DropDatabaseDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/drop-database-transact-sql'; export const DatabaseScopedConfigurationPropertiesDocUrl = 'https://learn.microsoft.com/sql/t-sql/statements/alter-database-scoped-configuration-transact-sql' export const DatabaseFilesPropertiesDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/database-properties-files-page' +export const DatabaseFileGroupsPropertiesDocUrl = 'https://learn.microsoft.com/sql/relational-databases/databases/database-properties-filegroups-page' export const enum TelemetryActions { CreateObject = 'CreateObject', diff --git a/extensions/mssql/src/objectManagement/interfaces.ts b/extensions/mssql/src/objectManagement/interfaces.ts index 43d04e954fd0..a515d5c1acf7 100644 --- a/extensions/mssql/src/objectManagement/interfaces.ts +++ b/extensions/mssql/src/objectManagement/interfaces.ts @@ -457,6 +457,7 @@ export interface Database extends ObjectManagement.SqlObject { databaseScopedConfigurations: DatabaseScopedConfigurationsInfo[]; isFilesTabSupported?: boolean; files?: DatabaseFile[]; + filegroups?: FileGroup[]; } export interface DatabaseViewInfo extends ObjectManagement.ObjectViewInfo { @@ -575,6 +576,12 @@ export const enum FileGrowthType { None = 99 } +export const enum FileGroupType { + RowsFileGroup = 0, + FileStreamDataFileGroup = 2, + MemoryOptimizedDataFileGroup = 3 +} + export interface DatabaseFile { id: number; name: string; @@ -588,3 +595,12 @@ export interface DatabaseFile { autoFileGrowthType: FileGrowthType; maxSizeLimitInMb: number } + +export interface FileGroup { + id?: number; + name: string; + type: FileGroupType; + isReadOnly: boolean; + isDefault: boolean; + autogrowAllFiles: boolean; +} diff --git a/extensions/mssql/src/objectManagement/localizedConstants.ts b/extensions/mssql/src/objectManagement/localizedConstants.ts index 0391a5bba722..619e93cb5bd2 100644 --- a/extensions/mssql/src/objectManagement/localizedConstants.ts +++ b/extensions/mssql/src/objectManagement/localizedConstants.ts @@ -167,6 +167,7 @@ export const GeneralSectionHeader = localize('objectManagement.generalSectionHea export const AdvancedSectionHeader = localize('objectManagement.advancedSectionHeader', "Advanced"); export const OptionsSectionHeader = localize('objectManagement.optionsSectionHeader', "Options"); export const FilesSectionHeader = localize('objectManagement.optionsSectionHeader', "Files"); +export const FileGroupsSectionHeader = localize('objectManagement.filegroupsSectionHeader', "Filegroups"); export const PasswordText = localize('objectManagement.passwordLabel', "Password"); export const ConfirmPasswordText = localize('objectManagement.confirmPasswordLabel', "Confirm password"); export const EnabledText = localize('objectManagement.enabledLabel', "Enabled"); @@ -394,6 +395,17 @@ export const FilegrowthLimitError = localize('objectManagement.databasePropertie export const RowsDataFileType = localize('objectManagement.databaseProperties.rowsDataFileType', "ROWS Data"); export const LogFiletype = localize('objectManagement.databaseProperties.logfiletype', "LOG"); export const FilestreamFileType = localize('objectManagement.databaseProperties.filestreamFileType', "FILESTREAM Data"); +export const RowsFileGroupsSectionText = localize('objectManagement.databaseProperties.rowsFileGroupsSectionText', "Rows"); +export const FileStreamFileGroupsSectionText = localize('objectManagement.databaseProperties.fileStreamFileGroupsSectionText', "FileStream"); +export const MemoryOptimizedFileGroupsSectionText = localize('objectManagement.databaseProperties.memoryOptimizedFileGroupsSectionText', "Memory Optimized Data"); +export const FilesText = localize('objectManagement.databaseProperties.filesText', "Files"); +export const ReadOnlyText = localize('objectManagement.databaseProperties.readOnlyText', "Read-Only"); +export const DefaultText = localize('objectManagement.databaseProperties.defaultText', "Default"); +export const AutogrowAllFilesText = localize('objectManagement.databaseProperties.autogrowAllFilesText', "Autogrow All Files"); +export const FilestreamFilesText = localize('objectManagement.databaseProperties.filestreamFilesText', "Filestream Files"); +export const AddFilegroupText = localize('objectManagement.databaseProperties.addFilegroupButtonText', "Add Filegroup"); +export const FilegroupExistsError = (name: string) => localize('objectManagement.databaseProperties.FilegroupExistsError', "File group '{0}' could not be added to the collection, because it already exists.", name); +export const EmptyFilegroupNameError = localize('objectManagement.databaseProperties.emptyFilegroupNameError', "Cannot use empty object names for filegroups."); // Util functions export function getNodeTypeDisplayName(type: string, inTitle: boolean = false): string { diff --git a/extensions/mssql/src/objectManagement/ui/databaseDialog.ts b/extensions/mssql/src/objectManagement/ui/databaseDialog.ts index bd0a58689db8..7353bde5ee2e 100644 --- a/extensions/mssql/src/objectManagement/ui/databaseDialog.ts +++ b/extensions/mssql/src/objectManagement/ui/databaseDialog.ts @@ -8,8 +8,8 @@ import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './obj import { DefaultInputWidth, DefaultTableWidth, DefaultMinTableRowCount, DefaultMaxTableRowCount, getTableHeight, DialogButton } from '../../ui/dialogBase'; import { IObjectManagementService } from 'mssql'; import * as localizedConstants from '../localizedConstants'; -import { CreateDatabaseDocUrl, DatabaseGeneralPropertiesDocUrl, DatabaseFilesPropertiesDocUrl, DatabaseOptionsPropertiesDocUrl, DatabaseScopedConfigurationPropertiesDocUrl } from '../constants'; -import { Database, DatabaseFile, DatabaseScopedConfigurationsInfo, DatabaseViewInfo, FileGrowthType } from '../interfaces'; +import { CreateDatabaseDocUrl, DatabaseGeneralPropertiesDocUrl, DatabaseFilesPropertiesDocUrl, DatabaseOptionsPropertiesDocUrl, DatabaseScopedConfigurationPropertiesDocUrl, DatabaseFileGroupsPropertiesDocUrl } from '../constants'; +import { Database, DatabaseFile, DatabaseScopedConfigurationsInfo, DatabaseViewInfo, FileGrowthType, FileGroup, FileGroupType } from '../interfaces'; import { convertNumToTwoDecimalStringInMB } from '../utils'; import { isUndefinedOrNull } from '../../types'; import { deepClone } from '../../util/objects'; @@ -24,6 +24,7 @@ export class DatabaseDialog extends ObjectManagementDialogBase 0 && !collationNames.some(name => name.toLowerCase() === this.objectInfo.collationName?.toLowerCase())) { errors.push(localizedConstants.CollationNotValidError(this.objectInfo.collationName ?? '')); } + + // Validate Rows Filegroup names + if (this.objectInfo.filegroups?.length > 0) { + let seenFilegroups = new Set; + this.objectInfo.filegroups.map(function (item) { + if (item.name === '') { + errors.push(localizedConstants.EmptyFilegroupNameError); + } else if (seenFilegroups.has(item.name)) { + errors.push(localizedConstants.FilegroupExistsError(item.name)); + } else { + seenFilegroups.add(item.name) + } + }); + } return errors; } @@ -527,7 +577,8 @@ export class DatabaseDialog extends ObjectManagementDialogBase { return this.convertToDataView(file); }); - await this.setTableData(this.databaseFilesTable, newData, DefaultMaxTableRowCount) + await this.setTableData(this.databaseFilesTable, newData, DefaultMaxTableRowCount); + await this.updateFileGroupsTablesfileCount(result.type); } } @@ -539,7 +590,8 @@ export class DatabaseDialog extends ObjectManagementDialogBase { return this.convertToDataView(file); }); - await this.setTableData(this.databaseFilesTable, newData, DefaultMaxTableRowCount) + await this.setTableData(this.databaseFilesTable, newData, DefaultMaxTableRowCount); + await this.updateFileGroupsTablesfileCount(result.type); } } } @@ -549,11 +601,29 @@ export class DatabaseDialog extends ObjectManagementDialogBase { if (this.databaseFilesTable.selectedRows.length === 1) { + await this.updateFileGroupsTablesfileCount(this.objectInfo.files[this.databaseFilesTable.selectedRows[0]].type); this.objectInfo.files?.splice(this.databaseFilesTable.selectedRows[0], 1); var newData = this.objectInfo.files?.map(file => { return this.convertToDataView(file); }); - await this.setTableData(this.databaseFilesTable, newData, DefaultMaxTableRowCount) + await this.setTableData(this.databaseFilesTable, newData, DefaultMaxTableRowCount); + } + } + + /** + * Updating the filegroups tables number of files count for each action of adding/editing/removing a database file + * @param fileType type of the file to get the data for the table + */ + private async updateFileGroupsTablesfileCount(fileType: string): Promise { + if (fileType === localizedConstants.RowsDataFileType) { + let data = this.getTableData(FileGroupType.RowsFileGroup); + await this.setTableData(this.rowsFilegroupsTable, data); + } + else if (fileType === localizedConstants.FilestreamFileType) { + let data = this.getTableData(FileGroupType.FileStreamDataFileGroup); + await this.setTableData(this.filestreamFilegroupsTable, data); + data = this.getTableData(FileGroupType.MemoryOptimizedDataFileGroup); + await this.setTableData(this.memoryOptimizedFilegroupsTable, data); } } @@ -561,9 +631,9 @@ export class DatabaseDialog extends ObjectManagementDialogBase 0) { + isEnabled = false; + } + } return isEnabled; } @@ -598,7 +675,7 @@ export class DatabaseDialog extends ObjectManagementDialogBase option === 'PRIMARY'), fileNameWithExtension: '', sizeInMb: defaultFileSizeInMb, isAutoGrowthEnabled: true, @@ -611,6 +688,8 @@ export class DatabaseDialog extends ObjectManagementDialogBase { + const data = this.getTableData(FileGroupType.RowsFileGroup); + this.rowsFilegroupsTable = this.modelView.modelBuilder.table().withProps({ + columns: [{ + type: azdata.ColumnType.text, + value: localizedConstants.NameText, + width: 120 + }, { + type: azdata.ColumnType.text, + value: localizedConstants.FilesText, + width: 60 + }, { + type: azdata.ColumnType.checkBox, + value: localizedConstants.ReadOnlyText, + width: 80 + }, { + type: azdata.ColumnType.checkBox, + value: localizedConstants.DefaultText, + width: 80 + }, { + type: azdata.ColumnType.checkBox, + value: localizedConstants.AutogrowAllFilesText, + width: 110 + }], + data: data, + height: getTableHeight(data.length, DefaultMinTableRowCount, DefaultMaxTableRowCount), + width: DefaultTableWidth, + forceFitColumns: azdata.ColumnSizingMode.DataFit, + CSSStyles: { + 'margin-left': '10px' + } + }).component(); + this.rowsFilegroupNameInput = this.getFilegroupNameInput(this.rowsFilegroupsTable, FileGroupType.RowsFileGroup); + const addButtonComponent: DialogButton = { + buttonAriaLabel: localizedConstants.AddFilegroupText, + buttonHandler: () => this.onAddDatabaseFileGroupsButtonClicked(this.rowsFilegroupsTable) + }; + const removeButtonComponent: DialogButton = { + buttonAriaLabel: localizedConstants.RemoveButton, + buttonHandler: () => this.onRemoveDatabaseFileGroupsButtonClicked(this.rowsFilegroupsTable) + }; + const rowsFileGroupButtonContainer = this.addButtonsForTable(this.rowsFilegroupsTable, addButtonComponent, removeButtonComponent); + + this.disposables.push( + this.rowsFilegroupsTable.onCellAction(async (arg: azdata.ICheckboxCellActionEventArgs) => { + let filegroup = this.rowDataFileGroupsTableRows[arg.row]; + // Read-Only column + if (arg.column === 2) { + filegroup.isReadOnly = arg.checked; + } + // Default column + if (arg.column === 3) { + this.updateFilegroupsDefaultColumnValues(arg, filegroup, FileGroupType.RowsFileGroup); + } + // Autogrow all files column + if (arg.column === 4) { + filegroup.autogrowAllFiles = arg.checked; + } + + // Refresh the table with updated data + let data = this.getTableData(FileGroupType.RowsFileGroup); + await this.setTableData(this.rowsFilegroupsTable, data); + this.onFormFieldChange(); + }), + this.rowsFilegroupsTable.onRowSelected( + async () => { + if (this.rowsFilegroupsTable.selectedRows.length === 1) { + const fileGroup = this.rowDataFileGroupsTableRows[this.rowsFilegroupsTable.selectedRows[0]]; + await this.rowsFilegroupNameInput.updateCssStyles({ 'visibility': fileGroup.id < 0 ? 'visible' : 'hidden' }); + this.rowsFilegroupNameInput.value = fileGroup.name; + this.onFormFieldChange(); + } + } + ) + ); + + const rowContainer = this.modelView.modelBuilder.flexContainer().withItems([this.rowsFilegroupNameInput]).component(); + rowContainer.addItems([rowsFileGroupButtonContainer], { flex: '0 0 auto' }); + return this.createGroup(localizedConstants.RowsFileGroupsSectionText, [this.rowsFilegroupsTable, rowContainer], true); + } + + /** + * Initializes the filestream filegroups section and updates the table data + * @returns filestream data filegroups container + */ + private initializeFileStreamFileGroupSection(): azdata.GroupContainer { + const data = this.getTableData(FileGroupType.FileStreamDataFileGroup); + this.filestreamFilegroupsTable = this.modelView.modelBuilder.table().withProps({ + columns: [{ + type: azdata.ColumnType.text, + value: localizedConstants.NameText + }, { + type: azdata.ColumnType.text, + value: localizedConstants.FilestreamFilesText + }, { + type: azdata.ColumnType.checkBox, + value: localizedConstants.ReadOnlyText + }, { + type: azdata.ColumnType.checkBox, + value: localizedConstants.DefaultText + }], + data: data, + height: getTableHeight(data.length, DefaultMinTableRowCount, DefaultMaxTableRowCount), + width: DefaultTableWidth, + forceFitColumns: azdata.ColumnSizingMode.DataFit, + CSSStyles: { + 'margin-left': '10px' + } + }).component(); + this.filestreamFilegroupNameInput = this.getFilegroupNameInput(this.filestreamFilegroupsTable, FileGroupType.FileStreamDataFileGroup); + const addButtonComponent: DialogButton = { + buttonAriaLabel: localizedConstants.AddFilegroupText, + buttonHandler: () => this.onAddDatabaseFileGroupsButtonClicked(this.filestreamFilegroupsTable) + }; + const removeButtonComponent: DialogButton = { + buttonAriaLabel: localizedConstants.RemoveButton, + buttonHandler: () => this.onRemoveDatabaseFileGroupsButtonClicked(this.filestreamFilegroupsTable) + }; + const filestreamFileGroupButtonContainer = this.addButtonsForTable(this.filestreamFilegroupsTable, addButtonComponent, removeButtonComponent); + + this.disposables.push( + this.filestreamFilegroupsTable.onCellAction(async (arg: azdata.ICheckboxCellActionEventArgs) => { + let filegroup = this.filestreamDataFileGroupsTableRows[arg.row]; + // Read-Only column + if (arg.column === 2) { + filegroup.isReadOnly = arg.checked; + } + // Default column + else if (arg.column === 3) { + this.updateFilegroupsDefaultColumnValues(arg, filegroup, FileGroupType.FileStreamDataFileGroup); + } + + // Refresh the table with updated data + let data = this.getTableData(FileGroupType.FileStreamDataFileGroup); + await this.setTableData(this.filestreamFilegroupsTable, data); + this.onFormFieldChange(); + }), + this.filestreamFilegroupsTable.onRowSelected( + async () => { + if (this.filestreamFilegroupsTable.selectedRows.length === 1) { + const fileGroup = this.filestreamDataFileGroupsTableRows[this.filestreamFilegroupsTable.selectedRows[0]]; + await this.filestreamFilegroupNameInput.updateCssStyles({ 'visibility': fileGroup.id < 0 ? 'visible' : 'hidden' }); + this.filestreamFilegroupNameInput.value = fileGroup.name; + this.onFormFieldChange(); + } + } + ) + ); + + const filestreamContainer = this.modelView.modelBuilder.flexContainer().withItems([this.filestreamFilegroupNameInput]).component(); + filestreamContainer.addItems([filestreamFileGroupButtonContainer], { flex: '0 0 auto' }); + return this.createGroup(localizedConstants.FileStreamFileGroupsSectionText, [this.filestreamFilegroupsTable, filestreamContainer], true); + } + + /** + * Initializes the memory optimized filegroups section and updates the table data + * @returns Memory optimized filegroups container + */ + private initializeMemoryOptimizedFileGroupSection(): azdata.GroupContainer { + const data = this.getTableData(FileGroupType.MemoryOptimizedDataFileGroup); + this.memoryOptimizedFilegroupsTable = this.modelView.modelBuilder.table().withProps({ + columns: [{ + type: azdata.ColumnType.text, + value: localizedConstants.NameText + }, { + type: azdata.ColumnType.text, + value: localizedConstants.FilestreamFilesText + }], + data: data, + height: getTableHeight(data.length, DefaultMinTableRowCount, DefaultMaxTableRowCount), + width: DefaultTableWidth, + forceFitColumns: azdata.ColumnSizingMode.DataFit, + CSSStyles: { + 'margin-left': '10px' + } + }).component(); + this.memoryOptimizedFilegroupNameInput = this.getFilegroupNameInput(this.memoryOptimizedFilegroupsTable, FileGroupType.MemoryOptimizedDataFileGroup); + const addButtonComponent: DialogButton = { + buttonAriaLabel: localizedConstants.AddFilegroupText, + buttonHandler: () => this.onAddDatabaseFileGroupsButtonClicked(this.memoryOptimizedFilegroupsTable) + }; + const removeButtonComponent: DialogButton = { + buttonAriaLabel: localizedConstants.RemoveButton, + buttonHandler: () => this.onRemoveDatabaseFileGroupsButtonClicked(this.memoryOptimizedFilegroupsTable) + }; + const memoryOptimizedFileGroupButtonContainer = this.addButtonsForTable(this.memoryOptimizedFilegroupsTable, addButtonComponent, removeButtonComponent); + + this.disposables.push( + this.memoryOptimizedFilegroupsTable.onRowSelected( + async () => { + if (this.memoryOptimizedFilegroupsTable.selectedRows.length === 1) { + const fileGroup = this.memoryoptimizedFileGroupsTableRows[this.memoryOptimizedFilegroupsTable.selectedRows[0]]; + await this.memoryOptimizedFilegroupNameInput.updateCssStyles({ 'visibility': fileGroup.id < 0 ? 'visible' : 'hidden' }); + this.memoryOptimizedFilegroupNameInput.value = fileGroup.name; + this.onFormFieldChange(); + } + } + ) + ); + + const memoryOptimizedContainer = this.modelView.modelBuilder.flexContainer().withItems([this.memoryOptimizedFilegroupNameInput]).component(); + memoryOptimizedContainer.addItems([memoryOptimizedFileGroupButtonContainer], { flex: '0 0 auto' }); + return this.createGroup(localizedConstants.MemoryOptimizedFileGroupsSectionText, [this.memoryOptimizedFilegroupsTable, memoryOptimizedContainer], true); + } + + /** + * Update the default value for the filegroup + * @param arg selected checkbox event + * @param filegroup filegroup object + * @param filegroupType filegroup type + */ + private updateFilegroupsDefaultColumnValues(arg: azdata.ICheckboxCellActionEventArgs, filegroup: FileGroup, filegroupType: FileGroupType): void { + if (arg.checked) { + this.objectInfo.filegroups.forEach(fg => { + if (fg.type === filegroupType) { + fg.isDefault = fg.name === filegroup.name && fg.id === filegroup.id ? arg.checked : !arg.checked; + } + }); + } else { + filegroup.isDefault = arg.checked; + } + } + + /** + * Adding new row to the respective table on its add button click + * @param table table component + */ + private async onAddDatabaseFileGroupsButtonClicked(table: azdata.TableComponent): Promise { + let newData: any[] | undefined; + let newRow: FileGroup = { + id: --this.newFileGroupTemporaryId, + name: '', + type: undefined, + isReadOnly: false, + isDefault: false, + autogrowAllFiles: false + }; + if (table === this.rowsFilegroupsTable) { + newRow.type = FileGroupType.RowsFileGroup; + newRow.isReadOnly = false; + newRow.isDefault = false; + newRow.autogrowAllFiles = false + this.objectInfo.filegroups?.push(newRow); + newData = this.getTableData(FileGroupType.RowsFileGroup); + } + else if (table === this.filestreamFilegroupsTable) { + newRow.type = FileGroupType.FileStreamDataFileGroup; + newRow.isReadOnly = false; + newRow.isDefault = false; + this.objectInfo.filegroups?.push(newRow); + newData = this.getTableData(FileGroupType.FileStreamDataFileGroup); + } + else if (table === this.memoryOptimizedFilegroupsTable && this.memoryoptimizedFileGroupsTableRows.length < 1) { + newRow.type = FileGroupType.MemoryOptimizedDataFileGroup; + this.objectInfo.filegroups?.push(newRow); + newData = this.getTableData(FileGroupType.MemoryOptimizedDataFileGroup); + } + + if (newData !== undefined) { + // Refresh the table with new row data + this.updateFileGroupsOptionsAndTableRows(); + await this.setTableData(table, newData, DefaultMaxTableRowCount); + } + } + + /** + * Prepares the individual table rows for each filegroup type and list of filegroups options + * This will be useful to get the selected row data from the table to get the filegroup property details, helps when have duplicate rows added + */ + private updateFileGroupsOptionsAndTableRows(): void { + // Filegroups rows for filegroups tab + this.rowDataFileGroupsTableRows = this.objectInfo.filegroups?.filter(filegroup => filegroup.type === FileGroupType.RowsFileGroup); + this.filestreamDataFileGroupsTableRows = this.objectInfo.filegroups?.filter(filegroup => filegroup.type === FileGroupType.FileStreamDataFileGroup); + this.memoryoptimizedFileGroupsTableRows = this.objectInfo.filegroups?.filter(filegroup => filegroup.type === FileGroupType.MemoryOptimizedDataFileGroup); + + // Filegroups options for files tab + this.filestreamDatafileGroupsOptions = this.objectInfo.filegroups?.filter(filegroup => filegroup.type === FileGroupType.FileStreamDataFileGroup || filegroup.type === FileGroupType.MemoryOptimizedDataFileGroup).map(filegroup => filegroup.name); + this.rowDatafileGroupsOptions = this.objectInfo.filegroups?.filter(filegroup => filegroup.type === FileGroupType.RowsFileGroup).map(filegroup => filegroup.name); + let index: number; + if ((index = this.rowDatafileGroupsOptions.indexOf('PRIMARY')) !== -1) { + this.rowDatafileGroupsOptions.unshift(this.rowDatafileGroupsOptions.splice(index, 1)[0]); + } + } + + /** + * Removed the selected row from the respective table on its remove button click + * @param table table component + */ + private async onRemoveDatabaseFileGroupsButtonClicked(table: azdata.TableComponent): Promise { + if (table === this.rowsFilegroupsTable) { + if (this.rowsFilegroupsTable.selectedRows.length === 1) { + const removeFilegroupIndex = this.objectInfo.filegroups.indexOf(this.rowDataFileGroupsTableRows[this.rowsFilegroupsTable.selectedRows[0]]); + this.objectInfo.filegroups?.splice(removeFilegroupIndex, 1); + var newData = this.getTableData(FileGroupType.RowsFileGroup); + await this.rowsFilegroupNameInput.updateCssStyles({ 'visibility': 'hidden' }); + } + } + else if (table === this.filestreamFilegroupsTable) { + if (this.filestreamFilegroupsTable.selectedRows.length === 1) { + const removeFilegroupIndex = this.objectInfo.filegroups.indexOf(this.filestreamDataFileGroupsTableRows[this.filestreamFilegroupsTable.selectedRows[0]]); + this.objectInfo.filegroups?.splice(removeFilegroupIndex, 1); + var newData = this.getTableData(FileGroupType.FileStreamDataFileGroup); + await this.filestreamFilegroupNameInput.updateCssStyles({ 'visibility': 'hidden' }); + } + } + else if (table === this.memoryOptimizedFilegroupsTable) { + if (this.memoryOptimizedFilegroupsTable.selectedRows.length === 1) { + const removeFilegroupIndex = this.objectInfo.filegroups.indexOf(this.memoryoptimizedFileGroupsTableRows[this.memoryOptimizedFilegroupsTable.selectedRows[0]]); + this.objectInfo.filegroups?.splice(removeFilegroupIndex, 1); + var newData = this.getTableData(FileGroupType.MemoryOptimizedDataFileGroup); + await this.memoryOptimizedFilegroupNameInput.updateCssStyles({ 'visibility': 'hidden' }); + } + } + + // Refresh the individual table rows object and table with updated data + this.updateFileGroupsOptionsAndTableRows(); + await this.setTableData(table, newData) + } + + /** + * Creates input box for filegroup name + * @param table table component + * @param filegroupType filegroup type + * @returns Input component + */ + private getFilegroupNameInput(table: azdata.TableComponent, filegroupType: FileGroupType): azdata.InputBoxComponent { + return this.createInputBox(async (value) => { + if (table.selectedRows.length === 1) { + let fg = null; + if (table === this.rowsFilegroupsTable) { + fg = this.rowDataFileGroupsTableRows[table.selectedRows[0]]; + } else if (table === this.filestreamFilegroupsTable) { + fg = this.filestreamDataFileGroupsTableRows[table.selectedRows[0]]; + } else if (table === this.memoryOptimizedFilegroupsTable) { + fg = this.memoryoptimizedFileGroupsTableRows[table.selectedRows[0]]; + } + if (fg !== null && fg.id < 0) { + fg.name = value; + let data = this.getTableData(filegroupType); + await this.setTableData(table, data); + this.updateFileGroupsOptionsAndTableRows(); + } + } + }, { + ariaLabel: '', + inputType: 'text', + enabled: true, + value: '', + width: 200, + CSSStyles: { 'margin': '5px 0px 0px 10px', 'visibility': 'hidden' } + }) + } + + /** + * Converts the filegroup object to a data view object + * Note: Cannot change properties(Read-only, Default, Autogrow All files) of empty Rows data filegroups, the filegroup must contain at least one file + * Note: Cannot change properties(Read-only) of empty Filestream data filegroups, the filegroup must contain at least one file + * @param filegroupType filegroup type + * @returns data view object + */ + private getTableData(filegroupType: FileGroupType): any[] { + let data: any[] = []; + this.objectInfo.filegroups?.map(fileGroup => { + const filesCount = this.objectInfo.files?.filter(file => file.fileGroup === fileGroup.name).length; + if (filegroupType === FileGroupType.RowsFileGroup && fileGroup.type === filegroupType) { + data.push([ + fileGroup.name, + filesCount, + { checked: fileGroup.isReadOnly, enabled: (fileGroup.name !== 'PRIMARY' && filesCount > 0) }, + { checked: fileGroup.isDefault, enabled: filesCount > 0 }, + { checked: fileGroup.autogrowAllFiles, enabled: filesCount > 0 } + ]); + } else if (fileGroup.type === FileGroupType.FileStreamDataFileGroup && fileGroup.type === filegroupType) { + data.push([ + fileGroup.name, + filesCount, + { checked: fileGroup.isReadOnly, enabled: filesCount > 0 }, + fileGroup.isDefault + ]); + } else if (fileGroup.type === FileGroupType.MemoryOptimizedDataFileGroup && fileGroup.type === filegroupType) { + data.push([ + fileGroup.name, + filesCount + ]); + } + }); + + return data; + } + //#endregion + //#region Database Properties - Options Tab private initializeOptionsGeneralSection(): void { let containers: azdata.Component[] = []; diff --git a/extensions/mssql/src/objectManagement/ui/databaseFileDialog.ts b/extensions/mssql/src/objectManagement/ui/databaseFileDialog.ts index 200b84b7e823..e27ae84905e2 100644 --- a/extensions/mssql/src/objectManagement/ui/databaseFileDialog.ts +++ b/extensions/mssql/src/objectManagement/ui/databaseFileDialog.ts @@ -16,6 +16,8 @@ export interface NewDatabaseFileDialogOptions { title: string; viewInfo: DatabaseViewInfo; files: DatabaseFile[]; + rowFilegroups: string[]; + filestreamFilegroups: string[]; isNewFile: boolean; isEditingNewFile: boolean; databaseFile: DatabaseFile; @@ -144,7 +146,7 @@ export class DatabaseFileDialog extends DialogBase { // Filegroup this.fileGroupDropdown = this.createDropdown(localizedConstants.FilegroupText, async (newValue) => { this.result.fileGroup = newValue; - }, this.options.viewInfo.rowDataFileGroupsOptions, this.options.databaseFile.fileGroup, this.isEditingFile, DefaultInputWidth); + }, this.options.rowFilegroups, this.options.databaseFile.fileGroup, this.isEditingFile, DefaultInputWidth); const sizeContainer = this.createLabelInputContainer(localizedConstants.FilegroupText, this.fileGroupDropdown); containers.push(sizeContainer); @@ -315,7 +317,7 @@ export class DatabaseFileDialog extends DialogBase { */ private async updateOptionsForSelectedFileType(selectedOption: string): Promise { // Row Data defaults - let fileGroupDdOptions = this.options.viewInfo.rowDataFileGroupsOptions; + let fileGroupDdOptions = this.options.rowFilegroups; let fileGroupDdValue = this.result.fileGroup; let visibility = 'visible'; let maxSizeGroupMarginTop = '0px'; @@ -330,7 +332,7 @@ export class DatabaseFileDialog extends DialogBase { } // File Stream else if (selectedOption === localizedConstants.FilestreamFileType) { - fileGroupDdOptions = this.options.viewInfo.fileStreamFileGroupsOptions; + fileGroupDdOptions = this.options.filestreamFilegroups; fileGroupDdValue = this.result.fileGroup; visibility = 'hidden'; maxSizeGroupMarginTop = '-130px'; diff --git a/extensions/mssql/src/ui/dialogBase.ts b/extensions/mssql/src/ui/dialogBase.ts index a2ab263f8544..f7605d445c5c 100644 --- a/extensions/mssql/src/ui/dialogBase.ts +++ b/extensions/mssql/src/ui/dialogBase.ts @@ -77,7 +77,7 @@ export abstract class DialogBase { protected onFormFieldChange(): void { } - protected get removeButtonEnabled(): boolean { return true; } + protected removeButtonEnabled(table: azdata.TableComponent): boolean { return true; } protected validateInput(): Promise { return Promise.resolve([]); } @@ -244,7 +244,7 @@ export abstract class DialogBase { return table; } - protected async setTableData(table: azdata.TableComponent, data: any[][], maxRowCount: number = DefaultMaxTableRowCount) { + protected async setTableData(table: azdata.TableComponent, data: any[][], maxRowCount: number = DefaultMaxTableRowCount): Promise { await table.updateProperties({ data: data, height: getTableHeight(data.length, DefaultMinTableRowCount, maxRowCount) @@ -314,7 +314,7 @@ export abstract class DialogBase { buttonComponents.push(removeButtonComponent); this.disposables.push(table.onRowSelected(() => { - const isRemoveButtonEnabled = this.removeButtonEnabled; + const isRemoveButtonEnabled = this.removeButtonEnabled(table); updateButtons(isRemoveButtonEnabled); }));