From 8f26a209500c773d4723b3234ef33908dcc1b359 Mon Sep 17 00:00:00 2001 From: Hai Cao Date: Tue, 6 Sep 2022 13:32:43 -0700 Subject: [PATCH] [Table Designer] support hash index and column store index (#1672) --- .../Localization/sr.cs | 252 ++++++++++++++++++ .../Localization/sr.resx | 100 +++++++ .../Localization/sr.strings | 24 ++ .../Localization/sr.xlf | 124 +++++++++ .../TableDesigner/Constants.cs | 18 ++ .../DesignerDataPropertyWithTabInfo.cs | 18 ++ .../Contracts/TableDesignerView.cs | 1 + .../ViewModel/ColumnStoreIndexViewModel.cs | 28 ++ .../Contracts/ViewModel/IndexViewModel.cs | 2 + .../Contracts/ViewModel/TableViewModel.cs | 1 + .../TableDesigner/TableDesignerService.cs | 231 ++++++++++++++++ .../TableDesigner/TableDesignerValidator.cs | 109 +++++++- 12 files changed, 907 insertions(+), 1 deletion(-) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/DesignerDataPropertyWithTabInfo.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/ViewModel/ColumnStoreIndexViewModel.cs diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs index 70450df7..b5873445 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs @@ -8861,6 +8861,46 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string IndexIsHashPropertyDescription + { + get + { + return Keys.GetString(Keys.IndexIsHashPropertyDescription); + } + } + + public static string IndexIsHashPropertyTitle + { + get + { + return Keys.GetString(Keys.IndexIsHashPropertyTitle); + } + } + + public static string IndexBucketCountPropertyDescription + { + get + { + return Keys.GetString(Keys.IndexBucketCountPropertyDescription); + } + } + + public static string IndexBucketCountPropertyTitle + { + get + { + return Keys.GetString(Keys.IndexBucketCountPropertyTitle); + } + } + + public static string HashIndexGroupTitle + { + get + { + return Keys.GetString(Keys.HashIndexGroupTitle); + } + } + public static string TableDesignerColumnsDisplayValueTitle { get @@ -8869,6 +8909,102 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string ColumnStoreIndexNamePropertyTitle + { + get + { + return Keys.GetString(Keys.ColumnStoreIndexNamePropertyTitle); + } + } + + public static string ColumnStoreIndexNamePropertyDescription + { + get + { + return Keys.GetString(Keys.ColumnStoreIndexNamePropertyDescription); + } + } + + public static string ColumnStoreIndexDescriptionPropertyTitle + { + get + { + return Keys.GetString(Keys.ColumnStoreIndexDescriptionPropertyTitle); + } + } + + public static string ColumnStoreIndexDescriptionPropertyDescription + { + get + { + return Keys.GetString(Keys.ColumnStoreIndexDescriptionPropertyDescription); + } + } + + public static string ColumnStoreIndexIsClusteredPropertyDescription + { + get + { + return Keys.GetString(Keys.ColumnStoreIndexIsClusteredPropertyDescription); + } + } + + public static string ColumnStoreIndexIsClusteredPropertyTitle + { + get + { + return Keys.GetString(Keys.ColumnStoreIndexIsClusteredPropertyTitle); + } + } + + public static string ColumnStoreIndexFilterPredicatePropertyDescription + { + get + { + return Keys.GetString(Keys.ColumnStoreIndexFilterPredicatePropertyDescription); + } + } + + public static string ColumnStoreIndexFilterPredicatePropertyTitle + { + get + { + return Keys.GetString(Keys.ColumnStoreIndexFilterPredicatePropertyTitle); + } + } + + public static string ColumnStoreIndexColumnsPropertyDescription + { + get + { + return Keys.GetString(Keys.ColumnStoreIndexColumnsPropertyDescription); + } + } + + public static string ColumnStoreIndexColumnsGroupTitle + { + get + { + return Keys.GetString(Keys.ColumnStoreIndexColumnsGroupTitle); + } + } + + public static string ColumnStoreIndexAddColumn + { + get + { + return Keys.GetString(Keys.ColumnStoreIndexAddColumn); + } + } + + public static string ColumnStoreIndexColumnPropertyName + { + get + { + return Keys.GetString(Keys.ColumnStoreIndexColumnPropertyName); + } + } + public static string TableDesignerDeleteColumnConfirmationMessage { get @@ -8925,6 +9061,30 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string TableDesignerColumnStoreIndexesTableTitle + { + get + { + return Keys.GetString(Keys.TableDesignerColumnStoreIndexesTableTitle); + } + } + + public static string TableDesignerColumnStoreIndexObjectType + { + get + { + return Keys.GetString(Keys.TableDesignerColumnStoreIndexObjectType); + } + } + + public static string AddNewColumnStoreIndexLabel + { + get + { + return Keys.GetString(Keys.AddNewColumnStoreIndexLabel); + } + } + public static string TableDesignerEdgeConstraintsTabTitle { get @@ -9830,6 +9990,26 @@ namespace Microsoft.SqlTools.ServiceLayer return Keys.GetString(Keys.ComputedColumnNeedToBePersistedInForeignKeyRuleDescription, columnName, foreignKeyName); } + public static string HashIndexNotSupportedInNonMemoryOptimizedTableRuleDescription(string indexName) + { + return Keys.GetString(Keys.HashIndexNotSupportedInNonMemoryOptimizedTableRuleDescription, indexName); + } + + public static string HashIndexMustHaveBucketCountRuleDescription(string indexName) + { + return Keys.GetString(Keys.HashIndexMustHaveBucketCountRuleDescription, indexName); + } + + public static string ColumnCanOnlyAppearOnceInNonClusteredColumnStoreIndexRuleDescription(string columnName, string indexName, int rowNumber) + { + return Keys.GetString(Keys.ColumnCanOnlyAppearOnceInNonClusteredColumnStoreIndexRuleDescription, columnName, indexName, rowNumber); + } + + public static string NonClusteredColumnStoreIndexMustHaveColumnsRuleDescription(string indexName) + { + return Keys.GetString(Keys.NonClusteredColumnStoreIndexMustHaveColumnsRuleDescription, indexName); + } + public static string SqlProjectModelNotFound(string projectUri) { return Keys.GetString(Keys.SqlProjectModelNotFound, projectUri); @@ -13346,9 +13526,60 @@ namespace Microsoft.SqlTools.ServiceLayer public const string IndexFilterPredicatePropertyTitle = "IndexFilterPredicatePropertyTitle"; + public const string IndexIsHashPropertyDescription = "IndexIsHashPropertyDescription"; + + + public const string IndexIsHashPropertyTitle = "IndexIsHashPropertyTitle"; + + + public const string IndexBucketCountPropertyDescription = "IndexBucketCountPropertyDescription"; + + + public const string IndexBucketCountPropertyTitle = "IndexBucketCountPropertyTitle"; + + + public const string HashIndexGroupTitle = "HashIndexGroupTitle"; + + public const string TableDesignerColumnsDisplayValueTitle = "TableDesignerColumnsDisplayValueTitle"; + public const string ColumnStoreIndexNamePropertyTitle = "ColumnStoreIndexNamePropertyTitle"; + + + public const string ColumnStoreIndexNamePropertyDescription = "ColumnStoreIndexNamePropertyDescription"; + + + public const string ColumnStoreIndexDescriptionPropertyTitle = "ColumnStoreIndexDescriptionPropertyTitle"; + + + public const string ColumnStoreIndexDescriptionPropertyDescription = "ColumnStoreIndexDescriptionPropertyDescription"; + + + public const string ColumnStoreIndexIsClusteredPropertyDescription = "ColumnStoreIndexIsClusteredPropertyDescription"; + + + public const string ColumnStoreIndexIsClusteredPropertyTitle = "ColumnStoreIndexIsClusteredPropertyTitle"; + + + public const string ColumnStoreIndexFilterPredicatePropertyDescription = "ColumnStoreIndexFilterPredicatePropertyDescription"; + + + public const string ColumnStoreIndexFilterPredicatePropertyTitle = "ColumnStoreIndexFilterPredicatePropertyTitle"; + + + public const string ColumnStoreIndexColumnsPropertyDescription = "ColumnStoreIndexColumnsPropertyDescription"; + + + public const string ColumnStoreIndexColumnsGroupTitle = "ColumnStoreIndexColumnsGroupTitle"; + + + public const string ColumnStoreIndexAddColumn = "ColumnStoreIndexAddColumn"; + + + public const string ColumnStoreIndexColumnPropertyName = "ColumnStoreIndexColumnPropertyName"; + + public const string TableDesignerDeleteColumnConfirmationMessage = "TableDesignerDeleteColumnConfirmationMessage"; @@ -13370,6 +13601,15 @@ namespace Microsoft.SqlTools.ServiceLayer public const string TableDesignerGraphTableTypeNone = "TableDesignerGraphTableTypeNone"; + public const string TableDesignerColumnStoreIndexesTableTitle = "TableDesignerColumnStoreIndexesTableTitle"; + + + public const string TableDesignerColumnStoreIndexObjectType = "TableDesignerColumnStoreIndexObjectType"; + + + public const string AddNewColumnStoreIndexLabel = "AddNewColumnStoreIndexLabel"; + + public const string TableDesignerEdgeConstraintsTabTitle = "TableDesignerEdgeConstraintsTabTitle"; @@ -13604,6 +13844,18 @@ namespace Microsoft.SqlTools.ServiceLayer public const string ComputedColumnNeedToBePersistedInForeignKeyRuleDescription = "ComputedColumnNeedToBePersistedInForeignKeyRuleDescription"; + public const string HashIndexNotSupportedInNonMemoryOptimizedTableRuleDescription = "HashIndexNotSupportedInNonMemoryOptimizedTableRuleDescription"; + + + public const string HashIndexMustHaveBucketCountRuleDescription = "HashIndexMustHaveBucketCountRuleDescription"; + + + public const string ColumnCanOnlyAppearOnceInNonClusteredColumnStoreIndexRuleDescription = "ColumnCanOnlyAppearOnceInNonClusteredColumnStoreIndexRuleDescription"; + + + public const string NonClusteredColumnStoreIndexMustHaveColumnsRuleDescription = "NonClusteredColumnStoreIndexMustHaveColumnsRuleDescription"; + + public const string TableDesignerConfirmationText = "TableDesignerConfirmationText"; diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx index dbf6716d..3787f1f8 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx @@ -4847,10 +4847,78 @@ The Query Processor estimates that implementing the following index could improv Filter Predicate + + Whether the index is a hash index + + + + Is Hash + + + + Bucket count of the hash index, note the value will always automatically round up to the next power of 2. + + + + Bucket Count + + + + Hash Index + + Columns + + Name + + + + Name of the column store index + + + + Description + + + + Description of the column store index + + + + Whether the column store index is clustered. + + + + Is Clustered + + + + Filter predicate of the column store index + + + + Filter Predicate + + + + Columns in this column store index + + + + Columns + + + + Add Column + + + + Column + + Removing a column will also remove it from the indexes and foreign keys. Are you sure you want to continue? @@ -4879,6 +4947,18 @@ The Query Processor estimates that implementing the following index could improv None + + Column Store Indexes + + + + Column Store Index + + + + New Column Store Index + + Edge Constraints @@ -5205,6 +5285,26 @@ The Query Processor estimates that implementing the following index could improv The computed column with name '{0}' has to be persisted to be part of the foreign key '{1}'. . Parameters: 0 - columnName (string), 1 - foreignKeyName (string) + + + Hash index with name '{0}' is not supported on a non memory-optimized table. + . + Parameters: 0 - indexName (string) + + + Hash index '{0}' does not have a bucket count. + . + Parameters: 0 - indexName (string) + + + Column with name '{0}' has already been added to the non-clustered column store index '{1}'. Row number: {2}. + . + Parameters: 0 - columnName (string), 1 - indexName (string), 2 - rowNumber (int) + + + Non-clustered column store index '{0}' does not have any columns associated with it. + . + Parameters: 0 - indexName (string) I have read the summary and understand the potential risks. diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index a2932e61..f0663eb9 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -2296,7 +2296,24 @@ IndexIncludedColumnsAddColumn = Add Column IndexIncludedColumnsColumnPropertyName = Column IndexFilterPredicatePropertyDescription = Filter predicate of the index IndexFilterPredicatePropertyTitle = Filter Predicate +IndexIsHashPropertyDescription = Whether the index is a hash index +IndexIsHashPropertyTitle = Is Hash +IndexBucketCountPropertyDescription = Bucket count of the hash index, note the value will always automatically round up to the next power of 2. +IndexBucketCountPropertyTitle = Bucket Count +HashIndexGroupTitle = Hash Index TableDesignerColumnsDisplayValueTitle = Columns +ColumnStoreIndexNamePropertyTitle = Name +ColumnStoreIndexNamePropertyDescription = Name of the column store index +ColumnStoreIndexDescriptionPropertyTitle = Description +ColumnStoreIndexDescriptionPropertyDescription = Description of the column store index +ColumnStoreIndexIsClusteredPropertyDescription = Whether the column store index is clustered. +ColumnStoreIndexIsClusteredPropertyTitle = Is Clustered +ColumnStoreIndexFilterPredicatePropertyDescription = Filter predicate of the column store index +ColumnStoreIndexFilterPredicatePropertyTitle = Filter Predicate +ColumnStoreIndexColumnsPropertyDescription = Columns in this column store index +ColumnStoreIndexColumnsGroupTitle = Columns +ColumnStoreIndexAddColumn = Add Column +ColumnStoreIndexColumnPropertyName = Column TableDesignerDeleteColumnConfirmationMessage = Removing a column will also remove it from the indexes and foreign keys. Are you sure you want to continue? TableDesignerGraphTableGroupTitle = Graph Table TableDesignerGraphTableTypeTitle = Type @@ -2304,6 +2321,9 @@ TableDesignerGraphTableTypeDescription = Specifies the table type. TableDesignerGraphTableTypeEdge = Edge TableDesignerGraphTableTypeNode = Node TableDesignerGraphTableTypeNone = None +TableDesignerColumnStoreIndexesTableTitle = Column Store Indexes +TableDesignerColumnStoreIndexObjectType = Column Store Index +AddNewColumnStoreIndexLabel = New Column Store Index TableDesignerEdgeConstraintsTabTitle = Edge Constraints TableDesignerEdgeConstraintObjectType = Edge Constraint TableDesignerEdgeConstraintNamePropertyDescription = Name of the constraint. @@ -2382,6 +2402,10 @@ ColumnCanOnlyAppearOnceInIndexIncludedColumnsRuleDescription(string columnName, 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}. ComputedColumnNeedToBePersistedAndNotNullInPrimaryKeyRuleDescription(string columnName) = The computed column with name '{0}' has to be persisted and not nullable to be part of a primary key. ComputedColumnNeedToBePersistedInForeignKeyRuleDescription(string columnName, string foreignKeyName) = The computed column with name '{0}' has to be persisted to be part of the foreign key '{1}'. +HashIndexNotSupportedInNonMemoryOptimizedTableRuleDescription(string indexName) = Hash index with name '{0}' is not supported on a non memory-optimized table. +HashIndexMustHaveBucketCountRuleDescription(string indexName) = Hash index '{0}' does not have a bucket count. +ColumnCanOnlyAppearOnceInNonClusteredColumnStoreIndexRuleDescription(string columnName, string indexName, int rowNumber) = Column with name '{0}' has already been added to the non-clustered column store index '{1}'. Row number: {2}. +NonClusteredColumnStoreIndexMustHaveColumnsRuleDescription(string indexName) = Non-clustered column store index '{0}' does not have any columns associated with it. TableDesignerConfirmationText = I have read the summary and understand the potential risks. ############################################################################ diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf index 89dee44f..90d71ea0 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf @@ -6359,6 +6359,130 @@ The Query Processor estimates that implementing the following index could improv The computed column with name '{0}' has to be persisted to be part of the foreign key '{1}'. . Parameters: 0 - columnName (string), 1 - foreignKeyName (string) + + + Hash index with name '{0}' is not supported on a non memory-optimized table. + Hash index with name '{0}' is not supported on a non memory-optimized table. + . + Parameters: 0 - indexName (string) + + + Hash index '{0}' does not have a bucket count. + Hash index '{0}' does not have a bucket count. + . + Parameters: 0 - indexName (string) + + + Whether the index is a hash index + Whether the index is a hash index + + + + Is Hash + Is Hash + + + + Bucket count of the hash index, note the value will always automatically round up to the next power of 2. + Bucket count of the hash index, note the value will always automatically round up to the next power of 2. + + + + Bucket Count + Bucket Count + + + + Hash Index + Hash Index + + + + Name + Name + + + + Name of the column store index + Name of the column store index + + + + Description + Description + + + + Description of the column store index + Description of the column store index + + + + Whether the column store index is clustered. + Whether the column store index is clustered. + + + + Is Clustered + Is Clustered + + + + Filter predicate of the column store index + Filter predicate of the column store index + + + + Filter Predicate + Filter Predicate + + + + Columns in this column store index + Columns in this column store index + + + + Columns + Columns + + + + Add Column + Add Column + + + + Column + Column + + + + Column with name '{0}' has already been added to the non-clustered column store index '{1}'. Row number: {2}. + Column with name '{0}' has already been added to the non-clustered column store index '{1}'. Row number: {2}. + . + Parameters: 0 - columnName (string), 1 - indexName (string), 2 - rowNumber (int) + + + Column Store Indexes + Column Store Indexes + + + + Column Store Index + Column Store Index + + + + New Column Store Index + New Column Store Index + + + + Non-clustered column store index '{0}' does not have any columns associated with it. + Non-clustered column store index '{0}' does not have any columns associated with it. + . + Parameters: 0 - indexName (string) I have read the summary and understand the potential risks. diff --git a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Constants.cs b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Constants.cs index 310991c4..62096cf7 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Constants.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Constants.cs @@ -14,6 +14,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner public const string ForeignKeys = "foreignKeys"; public const string CheckConstraints = "checkConstraints"; public const string Indexes = "indexes"; + public const string ColumnStoreIndexes = "columnStoreIndexes"; public const string EdgeConstraints = "edgeConstraints"; public const string GraphTableType = "graphTableType"; public const string IsSystemVersioningEnabled = "isSystemVersioningEnabled"; @@ -86,6 +87,8 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner public const string Enabled = "enabled"; public const string IsUnique = "isUnique"; public const string IsClustered = "isClustered"; + public const string IsHash = "isHash"; + public const string BucketCount = "bucketCount"; public const string Columns = "columns"; public const string IncludedColumns = "includedColumns"; public const string FilterPredicate = "filterPredicate"; @@ -103,6 +106,21 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner public const string Column = "column"; } + public static class ColumnStoreIndexPropertyNames + { + public const string Name = "name"; + public const string Description = "description"; + public const string IsClustered = "isClustered"; + public const string Columns = "columns"; + public const string FilterPredicate = "filterPredicate"; + public const string ColumnsDisplayValue = "columnsDisplayValue"; + } + + public static class ColumnStoreIndexColumnSpecificationPropertyNames + { + public const string Column = "column"; + } + public static class EdgeConstraintPropertyNames { public const string Name = "name"; diff --git a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/DesignerDataPropertyWithTabInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/DesignerDataPropertyWithTabInfo.cs new file mode 100644 index 00000000..2c01d490 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/DesignerDataPropertyWithTabInfo.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.SqlTools.ServiceLayer.TableDesigner.Contracts +{ + /// + /// Represents a component/property with tab info in the table designer + /// + public class DesignerDataPropertyWithTabInfo : DesignerDataPropertyInfo + { + /// + /// The tab of the property + /// + public string Tab { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/TableDesignerView.cs b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/TableDesignerView.cs index bb6b8eba..67246591 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/TableDesignerView.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/TableDesignerView.cs @@ -21,6 +21,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner.Contracts public BuiltinTableOptions IndexTableOptions { get; set; } = new BuiltinTableOptions(); public BuiltinTableOptions IndexColumnSpecificationTableOptions { get; set; } = new BuiltinTableOptions(); public List AdditionalPrimaryKeyProperties { get; set; } = new List(); + public List AdditionalComponents { get; set; } = new List(); public BuiltinTableOptions PrimaryKeyColumnSpecificationTableOptions = new BuiltinTableOptions(); public List AdditionalTabs { get; } = new List(); public bool UseAdvancedSaveMode { get; set; } diff --git a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/ViewModel/ColumnStoreIndexViewModel.cs b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/ViewModel/ColumnStoreIndexViewModel.cs new file mode 100644 index 00000000..b5aff70a --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/ViewModel/ColumnStoreIndexViewModel.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.SqlTools.ServiceLayer.TableDesigner.Contracts +{ + /// + /// The view model of column store index. + /// + public class ColumnStoreIndexViewModel : ObjectViewModelBase + { + + public CheckBoxProperties IsClustered { 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 class ColumnStoreIndexedColumnSpecification + { + public DropdownProperties Column { get; set; } = new DropdownProperties(); + + public CheckBoxProperties Ascending { get; set; } = new CheckBoxProperties(); + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/ViewModel/IndexViewModel.cs b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/ViewModel/IndexViewModel.cs index 394d1271..61da7717 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/ViewModel/IndexViewModel.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/ViewModel/IndexViewModel.cs @@ -15,7 +15,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner.Contracts public CheckBoxProperties IsClustered { get; set; } = new CheckBoxProperties(); public CheckBoxProperties IsUnique { get; set; } = new CheckBoxProperties(); + public CheckBoxProperties IsHash { get; set; } = new CheckBoxProperties(); + public InputBoxProperties BucketCount { get; set; } = new InputBoxProperties(); public InputBoxProperties ColumnsDisplayValue { get; set; } = new InputBoxProperties(); public InputBoxProperties FilterPredicate { get; set; } = new InputBoxProperties(); diff --git a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/ViewModel/TableViewModel.cs b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/ViewModel/TableViewModel.cs index 8bfc6558..279ef7a1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/ViewModel/TableViewModel.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/Contracts/ViewModel/TableViewModel.cs @@ -42,6 +42,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner.Contracts public TableComponentProperties EdgeConstraints { get; set; } = new TableComponentProperties(); public TableComponentProperties Indexes { get; set; } = new TableComponentProperties(); + public TableComponentProperties ColumnStoreIndexes { get; set; } = new TableComponentProperties(); public InputBoxProperties Script { get; set; } = new InputBoxProperties(); } diff --git a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/TableDesignerService.cs b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/TableDesignerService.cs index 9a641e76..8ee9b1ef 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/TableDesignerService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/TableDesignerService.cs @@ -245,6 +245,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner case TablePropertyNames.Indexes: table.Indexes.AddNew(); break; + case TablePropertyNames.ColumnStoreIndexes: + table.ColumnStoreIndexes.AddNew(); + break; case TablePropertyNames.EdgeConstraints: table.EdgeConstraints.AddNew(); break; @@ -290,6 +293,16 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner break; } break; + case TablePropertyNames.ColumnStoreIndexes: + switch (propertyNameL2) + { + case ColumnStoreIndexPropertyNames.Columns: + table.ColumnStoreIndexes.Items[indexL1].AddNewColumnSpecification(); + break; + default: + break; + } + break; case TablePropertyNames.EdgeConstraints: switch (propertyNameL2) { @@ -329,6 +342,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner case TablePropertyNames.Indexes: table.Indexes.RemoveAt(objIndex); break; + case TablePropertyNames.ColumnStoreIndexes: + table.ColumnStoreIndexes.RemoveAt(objIndex); + break; case TablePropertyNames.EdgeConstraints: table.EdgeConstraints.RemoveAt(objIndex); break; @@ -370,6 +386,16 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner break; } break; + case TablePropertyNames.ColumnStoreIndexes: + switch (propertyNameL2) + { + case ColumnStoreIndexPropertyNames.Columns: + table.ColumnStoreIndexes.Items[indexL1].RemoveColumnSpecification(indexL2); + break; + default: + break; + } + break; case TablePropertyNames.EdgeConstraints: switch (propertyNameL2) { @@ -612,6 +638,32 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner case IndexPropertyNames.FilterPredicate: sqlIndex.FilterPredicate = GetStringValue(newValue); break; + case IndexPropertyNames.IsHash: + sqlIndex.IsHash = GetBooleanValue(newValue); + break; + case IndexPropertyNames.BucketCount: + sqlIndex.BucketCount = GetInt32Value(newValue); + break; + default: + break; + } + break; + case TablePropertyNames.ColumnStoreIndexes: + var csIndex = table.ColumnStoreIndexes.Items[indexL1]; + switch (propertyNameL2) + { + case ColumnStoreIndexPropertyNames.IsClustered: + csIndex.IsClustered = GetBooleanValue(newValue); + break; + case ColumnStoreIndexPropertyNames.Name: + csIndex.Name = GetStringValue(newValue); + break; + case ColumnStoreIndexPropertyNames.Description: + csIndex.Description = GetStringValue(newValue); + break; + case ColumnStoreIndexPropertyNames.FilterPredicate: + csIndex.FilterPredicate = GetStringValue(newValue); + break; default: break; } @@ -711,6 +763,24 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner break; } break; + case TablePropertyNames.ColumnStoreIndexes: + var csIndex = table.ColumnStoreIndexes.Items[indexL1]; + switch (propertyNameL2) + { + case ColumnStoreIndexPropertyNames.Columns: + switch (propertyNameL3) + { + case ColumnStoreIndexColumnSpecificationPropertyNames.Column: + csIndex.UpdateColumnName(indexL2, GetStringValue(newValue)); + break; + default: + break; + } + break; + default: + break; + } + break; case TablePropertyNames.EdgeConstraints: var constraint = table.EdgeConstraints.Items[indexL1]; switch (propertyNameL2) @@ -930,10 +1000,15 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner indexVM.IsClustered.Checked = index.IsClustered; indexVM.Enabled.Checked = index.Enabled; indexVM.IsUnique.Checked = index.IsUnique; + indexVM.IsHash.Checked = index.IsHash; + indexVM.IsHash.Enabled = table.IsMemoryOptimized || index.IsHash; + indexVM.BucketCount.Value = index.BucketCount?.ToString(); + indexVM.BucketCount.Enabled = table.IsMemoryOptimized || index.IsHash; foreach (var columnSpec in index.Columns) { var columnSpecVM = new IndexedColumnSpecification(); columnSpecVM.Ascending.Checked = columnSpec.IsAscending; + columnSpecVM.Ascending.Enabled = index.CanEditIsAscending; columnSpecVM.Column.Value = columnSpec.Column; columnSpecVM.Column.Values = tableDesigner.GetColumnsForTable(table.FullName).ToList(); indexVM.Columns.Data.Add(columnSpecVM); @@ -957,6 +1032,35 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner tableViewModel.Indexes.Data.Add(indexVM); } + foreach (var index in table.ColumnStoreIndexes.Items) + { + var indexVM = new ColumnStoreIndexViewModel(); + indexVM.Name.Value = index.Name; + indexVM.Description.Value = index.Description; + indexVM.Description.Enabled = index.CanEditDescription; + indexVM.IsClustered.Checked = index.IsClustered; + indexVM.FilterPredicate.Value = index.FilterPredicate; + indexVM.FilterPredicate.Enabled = !index.IsClustered || index.FilterPredicate != null; + indexVM.Columns.Enabled = !index.IsClustered || index.Columns.Count() > 0; + indexVM.Columns.CanAddRows = !index.IsClustered; + indexVM.ColumnsDisplayValue.Enabled = false; + + // avoid populating columns for CLUSTERED column store index + if (!index.IsClustered) + { + indexVM.ColumnsDisplayValue.Value = index.ColumnsDisplayValue; + + foreach (var columnSpec in index.Columns) + { + var columnSpecVM = new ColumnStoreIndexedColumnSpecification(); + columnSpecVM.Column.Value = columnSpec.Column; + columnSpecVM.Column.Values = tableDesigner.GetColumnsForTable(table.FullName).ToList(); + indexVM.Columns.Data.Add(columnSpecVM); + } + } + tableViewModel.ColumnStoreIndexes.Data.Add(indexVM); + } + foreach (var constraint in table.EdgeConstraints.Items) { var constraintVM = new EdgeConstraintViewModel(); @@ -993,6 +1097,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner this.SetForeignKeysViewInfo(view); this.SetCheckConstraintsViewInfo(view); this.SetIndexesViewInfo(view); + this.SetColumnStoreIndexesViewInfo(view); this.SetGraphTableViewInfo(view, tableDesigner); this.SetEdgeConstraintsViewInfo(view, tableDesigner); this.SetTemporalTableViewInfo(view, tableDesigner); @@ -1233,6 +1338,29 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner } }, new DesignerDataPropertyInfo() + { + PropertyName = IndexPropertyNames.IsHash, + Description = SR.IndexIsHashPropertyDescription, + ComponentType = DesignerComponentType.Checkbox, + Group = SR.HashIndexGroupTitle, + ComponentProperties = new CheckBoxProperties() + { + Title = SR.IndexIsHashPropertyTitle + } + }, + new DesignerDataPropertyInfo() + { + PropertyName = IndexPropertyNames.BucketCount, + Description = SR.IndexBucketCountPropertyDescription, + ComponentType = DesignerComponentType.Input, + Group = SR.HashIndexGroupTitle, + ComponentProperties = new InputBoxProperties() + { + Title = SR.IndexBucketCountPropertyTitle, + Width = 200 + } + }, + new DesignerDataPropertyInfo() { PropertyName = IndexPropertyNames.IncludedColumns, Description = SR.IndexIncludedColumnsPropertyDescription, @@ -1283,6 +1411,109 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner view.IndexColumnSpecificationTableOptions.CanInsertRows = false; } + private void SetColumnStoreIndexesViewInfo(TableDesignerView view) + { + var columnStoreIndexesTableProperties = new TableComponentProperties() + { + Title = SR.TableDesignerColumnStoreIndexesTableTitle, + ObjectTypeDisplayName = SR.TableDesignerColumnStoreIndexObjectType, + LabelForAddNewButton = SR.AddNewColumnStoreIndexLabel + }; + columnStoreIndexesTableProperties.Columns.AddRange(new string[] { ColumnStoreIndexPropertyNames.Name, ColumnStoreIndexPropertyNames.ColumnsDisplayValue, ColumnStoreIndexPropertyNames.IsClustered }); + columnStoreIndexesTableProperties.ItemProperties.AddRange(new DesignerDataPropertyInfo[] { + new DesignerDataPropertyInfo() + { + PropertyName = ColumnStoreIndexPropertyNames.Name, + Description = SR.ColumnStoreIndexNamePropertyDescription, + ComponentType = DesignerComponentType.Input, + ComponentProperties = new InputBoxProperties() + { + Title = SR.ColumnStoreIndexNamePropertyTitle, + Width = 200 + } + }, + new DesignerDataPropertyInfo() + { + PropertyName = ColumnStoreIndexPropertyNames.Description, + Description = SR.ColumnStoreIndexDescriptionPropertyDescription, + ComponentType = DesignerComponentType.Input, + ComponentProperties = new InputBoxProperties() + { + Title = SR.ColumnStoreIndexDescriptionPropertyTitle, + Width = 200 + } + }, + new DesignerDataPropertyInfo() + { + PropertyName = ColumnStoreIndexPropertyNames.IsClustered, + Description = SR.ColumnStoreIndexIsClusteredPropertyDescription, + ComponentType = DesignerComponentType.Checkbox, + ComponentProperties = new CheckBoxProperties() + { + Title = SR.ColumnStoreIndexIsClusteredPropertyTitle + } + }, + new DesignerDataPropertyInfo() + { + PropertyName = ColumnStoreIndexPropertyNames.FilterPredicate, + Description = SR.ColumnStoreIndexFilterPredicatePropertyDescription, + ComponentType = DesignerComponentType.Input, + ComponentProperties = new InputBoxProperties() + { + Title = SR.ColumnStoreIndexFilterPredicatePropertyTitle, + Width = 200 + } + }, + new DesignerDataPropertyInfo() + { + PropertyName = ColumnStoreIndexPropertyNames.ColumnsDisplayValue, + ShowInPropertiesView = false, + ComponentType = DesignerComponentType.Input, + ComponentProperties = new InputBoxProperties() + { + Title = SR.TableDesignerColumnsDisplayValueTitle, + Width = 200 + } + }, + new DesignerDataPropertyInfo() + { + PropertyName = ColumnStoreIndexPropertyNames.Columns, + Description = SR.ColumnStoreIndexColumnsPropertyDescription, + ComponentType = DesignerComponentType.Table, + Group = SR.ColumnStoreIndexColumnsGroupTitle, + ComponentProperties = new TableComponentProperties() + { + AriaLabel = SR.ColumnStoreIndexColumnsGroupTitle, + Columns = new List () { ColumnStoreIndexColumnSpecificationPropertyNames.Column}, + LabelForAddNewButton = SR.ColumnStoreIndexAddColumn, + ItemProperties = new List() + { + new DesignerDataPropertyInfo() + { + PropertyName = ColumnStoreIndexColumnSpecificationPropertyNames.Column, + ComponentType = DesignerComponentType.Dropdown, + ComponentProperties = new DropdownProperties() + { + Title = SR.ColumnStoreIndexColumnPropertyName, + Width = 150 + } + }, + } + } + } + }); + + var columnStoreIndexesComponent = new DesignerDataPropertyWithTabInfo() + { + PropertyName = TablePropertyNames.ColumnStoreIndexes, + Tab = TablePropertyNames.Indexes, + ComponentType = DesignerComponentType.Table, + ComponentProperties = columnStoreIndexesTableProperties, + ShowInPropertiesView = false + }; + view.AdditionalComponents.Add(columnStoreIndexesComponent); + } + private void SetGraphTableViewInfo(TableDesignerView view, Dac.TableDesigner tableDesigner) { if (tableDesigner.IsGraphTableSupported && (tableDesigner.IsNewTable || tableDesigner.TableViewModel.IsEdge || tableDesigner.TableViewModel.IsNode)) diff --git a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/TableDesignerValidator.cs b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/TableDesignerValidator.cs index 7facb18e..5e986a0d 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/TableDesignerValidator.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/TableDesigner/TableDesignerValidator.cs @@ -39,7 +39,11 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner new ColumnCanOnlyAppearOnceInIndexIncludedColumnsRule(), new ColumnCannotDuplicateWitIndexKeyColumnsRule(), new ComputedColumnNeedToBePersistedAndNotNullInPrimaryKeyRule(), - new ComputedColumnNeedToBePersistedInForeignKeyRule() + new ComputedColumnNeedToBePersistedInForeignKeyRule(), + new HashIndexNotSupportedInNonMemoryOptimizedTableRule(), + new HashIndexMustHaveBucketCountRule(), + new ColumnCanOnlyAppearOnceInNonClusteredColumnStoreIndexRule(), + new NonClusteredColumnStoreIndexMustHaveColumnsRule() }; /// @@ -754,4 +758,107 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner return errors; } } + + public class HashIndexNotSupportedInNonMemoryOptimizedTableRule : ITableDesignerValidationRule + { + public List Run(Dac.TableDesigner designer) + { + var table = designer.TableViewModel; + var errors = new List(); + if (!table.IsMemoryOptimized) + { + for (int i = 0; i < table.Indexes.Items.Count; i++) + { + var index = table.Indexes.Items[i]; + if (index.IsHash) + { + errors.Add(new TableDesignerIssue() + { + Description = SR.HashIndexNotSupportedInNonMemoryOptimizedTableRuleDescription(index.Name), + PropertyPath = new object[] { TablePropertyNames.Indexes, i, IndexPropertyNames.IsHash } + }); + } + } + } + return errors; + } + } + + public class HashIndexMustHaveBucketCountRule : 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.IsHash && !index.BucketCount.HasValue) + { + errors.Add(new TableDesignerIssue() + { + Description = SR.HashIndexMustHaveBucketCountRuleDescription(index.Name), + PropertyPath = new object[] { TablePropertyNames.Indexes, i, IndexPropertyNames.BucketCount } + }); + } + } + return errors; + } + } + + public class ColumnCanOnlyAppearOnceInNonClusteredColumnStoreIndexRule : ITableDesignerValidationRule + { + public List Run(Dac.TableDesigner designer) + { + var table = designer.TableViewModel; + var errors = new List(); + for (int i = 0; i < table.ColumnStoreIndexes.Items.Count; i++) + { + var index = table.ColumnStoreIndexes.Items[i]; + if (!index.IsClustered) + { + var existingColumns = new HashSet(); + for (int j = 0; j < index.Columns.Count; j++) + { + var columnSpec = index.Columns[j]; + if (existingColumns.Contains(columnSpec.Column)) + { + errors.Add(new TableDesignerIssue() + { + Description = SR.ColumnCanOnlyAppearOnceInNonClusteredColumnStoreIndexRuleDescription(columnSpec.Column, index.Name, j + 1), + PropertyPath = new object[] { TablePropertyNames.ColumnStoreIndexes, i, ColumnStoreIndexPropertyNames.Columns, j } + }); + } + else + { + existingColumns.Add(columnSpec.Column); + } + } + } + } + return errors; + } + } + + public class NonClusteredColumnStoreIndexMustHaveColumnsRule : ITableDesignerValidationRule + { + public List Run(Dac.TableDesigner designer) + { + var table = designer.TableViewModel; + var errors = new List(); + for (int i = 0; i < table.ColumnStoreIndexes.Items.Count; i++) + { + var index = table.ColumnStoreIndexes.Items[i]; + if (!index.IsClustered && index.Columns.Count == 0) + { + errors.Add(new TableDesignerIssue() + { + Description = SR.NonClusteredColumnStoreIndexMustHaveColumnsRuleDescription(index.Name), + PropertyPath = new object[] { TablePropertyNames.ColumnStoreIndexes, i } + }); + } + } + return errors; + } + } } \ No newline at end of file