From a9fe77589dad58fba317e6c3d58477c863380174 Mon Sep 17 00:00:00 2001 From: Hai Cao Date: Fri, 5 Aug 2022 15:04:44 -0700 Subject: [PATCH] [Table Designer] Support filter predicate and included columns for index (#1619) --- .../Localization/sr.cs | 104 +++++++++++++++++ .../Localization/sr.resx | 42 +++++++ .../Localization/sr.strings | 12 +- .../Localization/sr.xlf | 52 +++++++++ .../TableDesigner/Constants.cs | 7 ++ .../Contracts/ViewModel/IndexViewModel.cs | 7 ++ .../TableDesigner/TableDesignerService.cs | 70 ++++++++++++ .../TableDesigner/TableDesignerValidator.cs | 108 +++++++++++++++++- 8 files changed, 400 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs index 31c6f8b2..9adff211 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs @@ -8797,6 +8797,54 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string IndexIncludedColumnsGroupTitle + { + get + { + return Keys.GetString(Keys.IndexIncludedColumnsGroupTitle); + } + } + + public static string IndexIncludedColumnsPropertyDescription + { + get + { + return Keys.GetString(Keys.IndexIncludedColumnsPropertyDescription); + } + } + + public static string IndexIncludedColumnsAddColumn + { + get + { + return Keys.GetString(Keys.IndexIncludedColumnsAddColumn); + } + } + + public static string IndexIncludedColumnsColumnPropertyName + { + get + { + return Keys.GetString(Keys.IndexIncludedColumnsColumnPropertyName); + } + } + + public static string IndexFilterPredicatePropertyDescription + { + get + { + return Keys.GetString(Keys.IndexFilterPredicatePropertyDescription); + } + } + + public static string IndexFilterPredicatePropertyTitle + { + get + { + return Keys.GetString(Keys.IndexFilterPredicatePropertyTitle); + } + } + public static string TableDesignerColumnsDisplayValueTitle { get @@ -9349,6 +9397,22 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string ClusteredIndexCannotHaveIncludedColumnsRuleDescription + { + get + { + return Keys.GetString(Keys.ClusteredIndexCannotHaveIncludedColumnsRuleDescription); + } + } + + public static string ClusteredIndexCannotHaveFilterPredicateRuleDescription + { + get + { + return Keys.GetString(Keys.ClusteredIndexCannotHaveFilterPredicateRuleDescription); + } + } + public static string ConnectionServiceListDbErrorNotConnected(string uri) { return Keys.GetString(Keys.ConnectionServiceListDbErrorNotConnected, uri); @@ -9714,6 +9778,16 @@ namespace Microsoft.SqlTools.ServiceLayer return Keys.GetString(Keys.ColumnCannotBeListedMoreThanOnceInPrimaryKeyRuleDescription, columnName); } + public static string ColumnCanOnlyAppearOnceInIndexIncludedColumnsRuleDescription(string columnName, string indexName, int rowNumber) + { + return Keys.GetString(Keys.ColumnCanOnlyAppearOnceInIndexIncludedColumnsRuleDescription, columnName, indexName, rowNumber); + } + + public static string ColumnCannotDuplicateWitIndexKeyColumnsRuleDescription(string columnName, string indexName, int rowNumber) + { + return Keys.GetString(Keys.ColumnCannotDuplicateWitIndexKeyColumnsRuleDescription, columnName, indexName, rowNumber); + } + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Keys { @@ -13196,6 +13270,24 @@ namespace Microsoft.SqlTools.ServiceLayer public const string IndexColumnIsAscendingPropertyTitle = "IndexColumnIsAscendingPropertyTitle"; + public const string IndexIncludedColumnsGroupTitle = "IndexIncludedColumnsGroupTitle"; + + + public const string IndexIncludedColumnsPropertyDescription = "IndexIncludedColumnsPropertyDescription"; + + + public const string IndexIncludedColumnsAddColumn = "IndexIncludedColumnsAddColumn"; + + + public const string IndexIncludedColumnsColumnPropertyName = "IndexIncludedColumnsColumnPropertyName"; + + + public const string IndexFilterPredicatePropertyDescription = "IndexFilterPredicatePropertyDescription"; + + + public const string IndexFilterPredicatePropertyTitle = "IndexFilterPredicatePropertyTitle"; + + public const string TableDesignerColumnsDisplayValueTitle = "TableDesignerColumnsDisplayValueTitle"; @@ -13436,6 +13528,18 @@ namespace Microsoft.SqlTools.ServiceLayer public const string MutipleCreateTableStatementsInScriptRuleDescription = "MutipleCreateTableStatementsInScriptRuleDescription"; + public const string ClusteredIndexCannotHaveIncludedColumnsRuleDescription = "ClusteredIndexCannotHaveIncludedColumnsRuleDescription"; + + + public const string ClusteredIndexCannotHaveFilterPredicateRuleDescription = "ClusteredIndexCannotHaveFilterPredicateRuleDescription"; + + + public const string ColumnCanOnlyAppearOnceInIndexIncludedColumnsRuleDescription = "ColumnCanOnlyAppearOnceInIndexIncludedColumnsRuleDescription"; + + + public const string ColumnCannotDuplicateWitIndexKeyColumnsRuleDescription = "ColumnCannotDuplicateWitIndexKeyColumnsRuleDescription"; + + private Keys() { } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx index 118bcc61..30a0d870 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx @@ -4815,6 +4815,30 @@ The Query Processor estimates that implementing the following index could improv Is Ascending + + Included Columns + + + + The included columns of the index + + + + Add Column + + + + Column + + + + Filter predicate of the index + + + + Filter Predicate + + Columns @@ -5146,4 +5170,22 @@ The Query Processor estimates that implementing the following index could improv There are multiple table definitions in the script, only the first table can be edited in the designer. + + Included columns are not supported for a clustered index. + + + + Filter predicate is not supported for a clustered index. + + + + Column with name '{0}' has already been included to the index '{1}'. Row number: {2}. + . + Parameters: 0 - columnName (string), 1 - indexName (string), 2 - rowNumber (int) + + + Included column with name '{0}' has already been part of the index '{1}' and it cannot be included. Row number: {2}. + . + Parameters: 0 - columnName (string), 1 - indexName (string), 2 - rowNumber (int) + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index 1ac60e70..6ce2b1d0 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -2286,6 +2286,12 @@ IndexIsUniquePropertyDescription = Whether the data entered into this index must TableDesignerIsUniquePropertyTitle = Is Unique IndexColumnIsAscendingPropertyDescription = Specifies the sort order of the column. IndexColumnIsAscendingPropertyTitle = Is Ascending +IndexIncludedColumnsGroupTitle = Included Columns +IndexIncludedColumnsPropertyDescription = The included columns of the index +IndexIncludedColumnsAddColumn = Add Column +IndexIncludedColumnsColumnPropertyName = Column +IndexFilterPredicatePropertyDescription = Filter predicate of the index +IndexFilterPredicatePropertyTitle = Filter Predicate TableDesignerColumnsDisplayValueTitle = Columns TableDesignerDeleteColumnConfirmationMessage = Removing a column will also remove it from the indexes and foreign keys. Are you sure you want to continue? TableDesignerGraphTableGroupTitle = Graph Table @@ -2365,4 +2371,8 @@ MemoryOptimizedTableIdentityColumnRuleDescription = The use of seed and incremen TableShouldAvoidHavingMultipleEdgeConstraintsRuleDescription = The table has more than one edge constraint on it. This is only useful as a temporary state when modifying existing edge constraints, and should not be used in other cases. ColumnCannotBeListedMoreThanOnceInPrimaryKeyRuleDescription(string columnName) = Cannot use duplicate column names in primary key, column name: {0} MemoryOptimizedCannotBeEnabledWhenNotSupportedRuleDescription = Memory-optimized table is not supported for this database. -MutipleCreateTableStatementsInScriptRuleDescription = There are multiple table definitions in the script, only the first table can be edited in the designer. \ No newline at end of file +MutipleCreateTableStatementsInScriptRuleDescription = There are multiple table definitions in the script, only the first table can be edited in the designer. +ClusteredIndexCannotHaveIncludedColumnsRuleDescription = Included columns are not supported for a clustered index. +ClusteredIndexCannotHaveFilterPredicateRuleDescription = Filter predicate is not supported for a clustered index. +ColumnCanOnlyAppearOnceInIndexIncludedColumnsRuleDescription(string columnName, string indexName, int rowNumber) = Column with name '{0}' has already been included to the index '{1}'. Row number: {2}. +ColumnCannotDuplicateWitIndexKeyColumnsRuleDescription(string columnName, string indexName, int rowNumber) = Included column with name '{0}' has already been part of the index '{1}' and it cannot be included. Row number: {2}. \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf index 48338fe5..7e0720a3 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf @@ -6254,6 +6254,58 @@ The Query Processor estimates that implementing the following index could improv Average Row Size + + Included Columns + Included Columns + + + + The included columns of the index + The included columns of the index + + + + Add Column + Add Column + + + + Column + Column + + + + Filter predicate of the index + Filter predicate of the index + + + + Filter Predicate + Filter Predicate + + + + Included columns are not supported for a clustered index. + Included columns are not supported for a clustered index. + + + + Filter predicate is not supported for a clustered index. + Filter predicate is not supported for a clustered index. + + + + Column with name '{0}' has already been included to the index '{1}'. Row number: {2}. + Column with name '{0}' has already been included to the index '{1}'. Row number: {2}. + . + Parameters: 0 - columnName (string), 1 - indexName (string), 2 - rowNumber (int) + + + Included column with name '{0}' has already been part of the index '{1}' and it cannot be included. Row number: {2}. + Included column with name '{0}' has already been part of the index '{1}' and it cannot be included. Row number: {2}. + . + Parameters: 0 - columnName (string), 1 - indexName (string), 2 - rowNumber (int) + Updatable Ledger Updatable Ledger diff --git a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Constants.cs b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Constants.cs index b20d8276..310991c4 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Constants.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Constants.cs @@ -87,6 +87,8 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner public const string IsUnique = "isUnique"; public const string IsClustered = "isClustered"; public const string Columns = "columns"; + public const string IncludedColumns = "includedColumns"; + public const string FilterPredicate = "filterPredicate"; public const string ColumnsDisplayValue = "columnsDisplayValue"; } @@ -96,6 +98,11 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner public const string Ascending = "ascending"; } + public static class IndexIncludedColumnSpecificationPropertyNames + { + public const string Column = "column"; + } + public static class EdgeConstraintPropertyNames { public const string Name = "name"; diff --git a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/ViewModel/IndexViewModel.cs b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/ViewModel/IndexViewModel.cs index 8b9a5844..394d1271 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/ViewModel/IndexViewModel.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/ViewModel/IndexViewModel.cs @@ -17,8 +17,10 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner.Contracts public CheckBoxProperties IsUnique { get; set; } = new CheckBoxProperties(); public InputBoxProperties ColumnsDisplayValue { get; set; } = new InputBoxProperties(); + public InputBoxProperties FilterPredicate { get; set; } = new InputBoxProperties(); public TableComponentProperties Columns { get; set; } = new TableComponentProperties(); + public TableComponentProperties IncludedColumns { get; set; } = new TableComponentProperties(); } public class IndexedColumnSpecification @@ -27,4 +29,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner.Contracts public CheckBoxProperties Ascending { get; set; } = new CheckBoxProperties(); } + + public class IndexIncludedColumnSpecification + { + public DropdownProperties Column { get; set; } = new DropdownProperties(); + } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/TableDesignerService.cs b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/TableDesignerService.cs index aaab396c..f1925714 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/TableDesignerService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/TableDesignerService.cs @@ -281,6 +281,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner case IndexPropertyNames.Columns: table.Indexes.Items[indexL1].AddNewColumnSpecification(); break; + case IndexPropertyNames.IncludedColumns: + table.Indexes.Items[indexL1].AddNewIncludedColumn(); + break; default: break; } @@ -358,6 +361,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner case IndexPropertyNames.Columns: table.Indexes.Items[indexL1].RemoveColumnSpecification(indexL2); break; + case IndexPropertyNames.IncludedColumns: + table.Indexes.Items[indexL1].RemoveIncludedColumn(indexL2); + break; default: break; } @@ -601,6 +607,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner case IndexPropertyNames.Description: sqlIndex.Description = GetStringValue(newValue); break; + case IndexPropertyNames.FilterPredicate: + sqlIndex.FilterPredicate = GetStringValue(newValue); + break; default: break; } @@ -686,6 +695,16 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner break; } break; + case IndexPropertyNames.IncludedColumns: + switch (propertyNameL3) + { + case IndexIncludedColumnSpecificationPropertyNames.Column: + sqlIndex.UpdateIncludedColumn(indexL2, GetStringValue(newValue)); + break; + default: + break; + } + break; default: break; } @@ -917,6 +936,20 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner columnSpecVM.Column.Values = tableDesigner.GetColumnsForTable(table.FullName).ToList(); indexVM.Columns.Data.Add(columnSpecVM); } + + indexVM.FilterPredicate.Value = index.FilterPredicate; + indexVM.FilterPredicate.Enabled = !index.IsClustered || index.FilterPredicate != null; + indexVM.IncludedColumns.Enabled = !index.IsClustered || index.IncludedColumns.Count() > 0; + indexVM.IncludedColumns.CanAddRows = !index.IsClustered; + + foreach (var column in index.IncludedColumns) + { + var includedColumnsVM = new IndexIncludedColumnSpecification(); + includedColumnsVM.Column.Value = column; + includedColumnsVM.Column.Values = tableDesigner.GetColumnsForTable(table.FullName).ToList(); + indexVM.IncludedColumns.Data.Add(includedColumnsVM); + } + indexVM.ColumnsDisplayValue.Value = index.ColumnsDisplayValue; indexVM.ColumnsDisplayValue.Enabled = false; tableViewModel.Indexes.Data.Add(indexVM); @@ -1185,6 +1218,43 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner { Title = SR.TableDesignerIsUniquePropertyTitle } + }, + new DesignerDataPropertyInfo() + { + PropertyName = IndexPropertyNames.FilterPredicate, + Description = SR.IndexFilterPredicatePropertyDescription, + ComponentType = DesignerComponentType.Input, + ComponentProperties = new InputBoxProperties() + { + Title = SR.IndexFilterPredicatePropertyTitle, + Width = 200 + } + }, + new DesignerDataPropertyInfo() + { + PropertyName = IndexPropertyNames.IncludedColumns, + Description = SR.IndexIncludedColumnsPropertyDescription, + ComponentType = DesignerComponentType.Table, + Group = SR.IndexIncludedColumnsGroupTitle, + ComponentProperties = new TableComponentProperties() + { + AriaLabel = SR.IndexIncludedColumnsGroupTitle, + Columns = new List () { IndexIncludedColumnSpecificationPropertyNames.Column}, + LabelForAddNewButton = SR.IndexIncludedColumnsAddColumn, + ItemProperties = new List() + { + new DesignerDataPropertyInfo() + { + PropertyName = IndexIncludedColumnSpecificationPropertyNames.Column, + ComponentType = DesignerComponentType.Dropdown, + ComponentProperties = new DropdownProperties() + { + Title = SR.IndexIncludedColumnsColumnPropertyName, + Width = 150 + } + }, + } + } } }); view.IndexTableOptions.PropertiesToDisplay = new List() { IndexPropertyNames.Name, IndexPropertyNames.ColumnsDisplayValue, IndexPropertyNames.IsClustered, IndexPropertyNames.IsUnique }; diff --git a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/TableDesignerValidator.cs b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/TableDesignerValidator.cs index 65f5bef0..767538b7 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/TableDesignerValidator.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/TableDesignerValidator.cs @@ -33,7 +33,11 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner new MemoryOptimizedTableIdentityColumnRule(), new TableShouldAvoidHavingMultipleEdgeConstraintsRule(), new ColumnCannotBeListedMoreThanOnceInPrimaryKeyRule(), - new MutipleCreateTableStatementsInScriptRule() + new MutipleCreateTableStatementsInScriptRule(), + new ClusteredIndexCannotHaveFilterPredicate(), + new ClusteredIndexCannotHaveIncludedColumnsRule(), + new ColumnCanOnlyAppearOnceInIndexIncludedColumnsRule(), + new ColumnCannotDuplicateWitIndexKeyColumnsRule() }; /// @@ -77,6 +81,50 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner } } + public class ClusteredIndexCannotHaveIncludedColumnsRule : ITableDesignerValidationRule + { + public List Run(Dac.TableDesigner designer) + { + var table = designer.TableViewModel; + var errors = new List(); + for (int i = 0; i < table.Indexes.Items.Count; i++) + { + var index = table.Indexes.Items[i]; + if (index.IsClustered && index.IncludedColumns.Count != 0) + { + errors.Add(new TableDesignerIssue() + { + Description = SR.ClusteredIndexCannotHaveIncludedColumnsRuleDescription, + PropertyPath = new object[] { TablePropertyNames.Indexes, i, IndexPropertyNames.IncludedColumns, 0 } + }); + } + } + return errors; + } + } + + public class ClusteredIndexCannotHaveFilterPredicate : ITableDesignerValidationRule + { + public List Run(Dac.TableDesigner designer) + { + var table = designer.TableViewModel; + var errors = new List(); + for (int i = 0; i < table.Indexes.Items.Count; i++) + { + var index = table.Indexes.Items[i]; + if (index.IsClustered && index.FilterPredicate != null) + { + errors.Add(new TableDesignerIssue() + { + Description = SR.ClusteredIndexCannotHaveFilterPredicateRuleDescription, + PropertyPath = new object[] { TablePropertyNames.Indexes, i, IndexPropertyNames.FilterPredicate } + }); + } + } + return errors; + } + } + public class ForeignKeyMustHaveColumnsRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) @@ -130,6 +178,64 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner } } + public class ColumnCanOnlyAppearOnceInIndexIncludedColumnsRule : ITableDesignerValidationRule + { + public List Run(Dac.TableDesigner designer) + { + var table = designer.TableViewModel; + var errors = new List(); + for (int i = 0; i < table.Indexes.Items.Count; i++) + { + var index = table.Indexes.Items[i]; + var existingColumns = new HashSet(); + for (int j = 0; j < index.IncludedColumns.Count; j++) + { + var col = index.IncludedColumns[j]; + if (existingColumns.Contains(col)) + { + errors.Add(new TableDesignerIssue() + { + Description = SR.ColumnCanOnlyAppearOnceInIndexIncludedColumnsRuleDescription(col, index.Name, j + 1), + PropertyPath = new object[] { TablePropertyNames.Indexes, i, IndexPropertyNames.IncludedColumns, j } + }); + } + else + { + existingColumns.Add(col); + } + } + } + return errors; + } + } + + public class ColumnCannotDuplicateWitIndexKeyColumnsRule : ITableDesignerValidationRule + { + public List Run(Dac.TableDesigner designer) + { + var table = designer.TableViewModel; + var errors = new List(); + for (int i = 0; i < table.Indexes.Items.Count; i++) + { + var index = table.Indexes.Items[i]; + var keyCols = new HashSet(index.Columns.Select(s => s.Column)); + for (int j = 0; j < index.IncludedColumns.Count; j++) + { + var col = index.IncludedColumns[j]; + if (keyCols.Contains(col)) + { + errors.Add(new TableDesignerIssue() + { + Description = SR.ColumnCannotDuplicateWitIndexKeyColumnsRuleDescription(col, index.Name, j + 1), + PropertyPath = new object[] { TablePropertyNames.Indexes, i, IndexPropertyNames.IncludedColumns, j } + }); + } + } + } + return errors; + } + } + public class ColumnCanOnlyAppearOnceInForeignKeyRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer)