Added uniqueness check for constraints (#1454)

* added rule for checking constraints

* added constraint validation rule

* merge PR's

* remove unused code

* update comment

* remove unused import
This commit is contained in:
Aditya Bist
2022-04-08 12:52:24 -07:00
committed by GitHub
parent 71830ee5f9
commit b1f494d04d
4 changed files with 143 additions and 76 deletions

View File

@@ -28,6 +28,11 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner.Contracts
/// Metadata about the table
/// </summary>
public Dictionary<string, string> Metadata { get; set; }
/// <summary>
/// The table schema validation error
/// </summary>
public string SchemaValidationError { get; set; }
}
/// <summary>

View File

@@ -27,6 +27,8 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner.Contracts
public TableDesignerIssue[] Issues { get; set; }
public Dictionary<string, string> Metadata { get; set; }
public string InputValidationError { get; set; }
}
/// <summary>

View File

@@ -10,6 +10,7 @@ using System.Linq;
using Microsoft.Data.SqlClient;
using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Hosting;
using Microsoft.Data.Tools.Sql.DesignServices;
using Microsoft.SqlTools.ServiceLayer.TableDesigner.Contracts;
using Dac = Microsoft.Data.Tools.Sql.DesignServices.TableDesigner;
using STSHost = Microsoft.SqlTools.ServiceLayer.Hosting.ServiceHost;
@@ -98,30 +99,44 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
{
var refreshViewRequired = false;
DesignerPathUtils.Validate(requestParams.TableChangeInfo.Path, requestParams.TableChangeInfo.Type);
switch (requestParams.TableChangeInfo.Type)
try
{
case DesignerEditType.Add:
this.HandleAddItemRequest(requestParams);
break;
case DesignerEditType.Remove:
this.HandleRemoveItemRequest(requestParams);
break;
case DesignerEditType.Update:
refreshViewRequired = this.HandleUpdateItemRequest(requestParams);
break;
default:
break;
switch (requestParams.TableChangeInfo.Type)
{
case DesignerEditType.Add:
this.HandleAddItemRequest(requestParams);
break;
case DesignerEditType.Remove:
this.HandleRemoveItemRequest(requestParams);
break;
case DesignerEditType.Update:
refreshViewRequired = this.HandleUpdateItemRequest(requestParams);
break;
default:
break;
}
await SendProcessTableEditResponse(requestParams.TableInfo, requestContext, refreshViewRequired);
}
var designer = this.GetTableDesigner(requestParams.TableInfo);
var issues = TableDesignerValidator.Validate(designer.TableViewModel);
await requestContext.SendResult(new ProcessTableDesignerEditResponse()
catch (DesignerValidationException e)
{
ViewModel = this.GetTableViewModel(requestParams.TableInfo),
IsValid = issues.Where(i => i.Severity == IssueSeverity.Error).Count() == 0,
Issues = issues.ToArray(),
View = refreshViewRequired ? this.GetDesignerViewInfo(requestParams.TableInfo) : null,
Metadata = this.GetMetadata(requestParams.TableInfo)
});
await SendProcessTableEditResponse(requestParams.TableInfo, requestContext, refreshViewRequired, e.Message);
}
});
}
private async Task SendProcessTableEditResponse(TableInfo tableInfo, RequestContext<ProcessTableDesignerEditResponse> requestContext,
bool refreshViewRequired, string inputValidationError = null)
{
var designer = this.GetTableDesigner(tableInfo);
var issues = TableDesignerValidator.Validate(designer);
await requestContext.SendResult(new ProcessTableDesignerEditResponse()
{
ViewModel = this.GetTableViewModel(tableInfo),
IsValid = issues.Where(i => i.Severity == IssueSeverity.Error).Count() == 0,
Issues = issues.ToArray(),
View = refreshViewRequired ? this.GetDesignerViewInfo(tableInfo) : null,
Metadata = this.GetMetadata(tableInfo),
InputValidationError = inputValidationError
});
}
@@ -168,16 +183,30 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
{
return this.HandleRequest<GeneratePreviewReportResult>(requestContext, async () =>
{
var table = this.GetTableDesigner(tableInfo);
var report = table.GenerateReport();
var generatePreviewReportResult = new GeneratePreviewReportResult();
generatePreviewReportResult.Report = report;
generatePreviewReportResult.MimeType = "text/markdown";
generatePreviewReportResult.Metadata = this.GetMetadata(tableInfo);
await requestContext.SendResult(generatePreviewReportResult);
try
{
await SendGeneratePreviewReportResult(tableInfo, requestContext);
}
catch (DesignerValidationException e)
{
await SendGeneratePreviewReportResult(tableInfo, requestContext, e.Message);
}
});
}
private async Task SendGeneratePreviewReportResult(TableInfo tableInfo, RequestContext<GeneratePreviewReportResult> requestContext,
string schemaValidationError = null)
{
var table = this.GetTableDesigner(tableInfo);
var report = table.GenerateReport();
var generatePreviewReportResult = new GeneratePreviewReportResult();
generatePreviewReportResult.Report = report;
generatePreviewReportResult.MimeType = "text/markdown";
generatePreviewReportResult.Metadata = this.GetMetadata(tableInfo);
generatePreviewReportResult.SchemaValidationError = schemaValidationError;
await requestContext.SendResult(generatePreviewReportResult);
}
private Task HandleDisposeTableDesignerRequest(TableInfo tableInfo, RequestContext<DisposeTableDesignerResponse> requestContext)
{
return this.HandleRequest<DisposeTableDesignerResponse>(requestContext, async () =>

View File

@@ -5,6 +5,7 @@
using System.Collections.Generic;
using Microsoft.Data.Tools.Sql.DesignServices.TableDesigner;
using Dac = Microsoft.Data.Tools.Sql.DesignServices.TableDesigner;
using TableDesignerIssue = Microsoft.SqlTools.ServiceLayer.TableDesigner.Contracts.TableDesignerIssue;
using System.Linq;
namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
@@ -35,12 +36,12 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
/// <summary>
/// Validate the table and return the validation errors.
/// </summary>
public static List<TableDesignerIssue> Validate(TableViewModel table)
public static List<TableDesignerIssue> Validate(Dac.TableDesigner designer)
{
var errors = new List<TableDesignerIssue>();
foreach (var rule in Rules)
{
errors.AddRange(rule.Run(table));
errors.AddRange(rule.Run(designer));
}
return errors;
}
@@ -48,13 +49,14 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public interface ITableDesignerValidationRule
{
List<TableDesignerIssue> Run(TableViewModel table);
List<TableDesignerIssue> Run(Dac.TableDesigner designer);
}
public class IndexMustHaveColumnsRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
for (int i = 0; i < table.Indexes.Items.Count; i++)
{
@@ -74,8 +76,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class ForeignKeyMustHaveColumnsRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
for (int i = 0; i < table.ForeignKeys.Items.Count; i++)
{
@@ -95,8 +98,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class ColumnCanOnlyAppearOnceInIndexRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
for (int i = 0; i < table.Indexes.Items.Count; i++)
{
@@ -125,8 +129,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class ColumnCanOnlyAppearOnceInForeignKeyRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
for (int i = 0; i < table.ForeignKeys.Items.Count; i++)
{
@@ -173,58 +178,71 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class NoDuplicateConstraintNameRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var errors = new List<TableDesignerIssue>();
var table = designer.TableViewModel;
var existingNames = new HashSet<string>();
Dictionary<string, int> currentSchemaConstraints = designer.AllConstraintsNamesCounts;
for (int i = 0; i < table.ForeignKeys.Items.Count; i++)
{
var foreignKey = table.ForeignKeys.Items[i];
if (existingNames.Contains(foreignKey.Name))
if (currentSchemaConstraints.ContainsKey(foreignKey.Name) && currentSchemaConstraints[foreignKey.Name] > 1)
{
errors.Add(new TableDesignerIssue()
if (existingNames.Contains(foreignKey.Name))
{
Description = string.Format("The name '{0}' is already used by another constraint. Row number: {1}.", foreignKey.Name, i + 1),
PropertyPath = new object[] { TablePropertyNames.ForeignKeys, i, ForeignKeyPropertyNames.Name }
});
}
else
{
existingNames.Add(foreignKey.Name);
errors.Add(new TableDesignerIssue()
{
Description = string.Format("The name '{0}' is already used by another constraint. Row number: {1}.", foreignKey.Name, i + 1),
PropertyPath = new object[] { TablePropertyNames.ForeignKeys, i, ForeignKeyPropertyNames.Name }
});
}
else
{
existingNames.Add(foreignKey.Name);
}
}
}
for (int i = 0; i < table.CheckConstraints.Items.Count; i++)
{
var checkConstraint = table.CheckConstraints.Items[i];
if (existingNames.Contains(checkConstraint.Name))
if (currentSchemaConstraints.ContainsKey(checkConstraint.Name) && currentSchemaConstraints[checkConstraint.Name] > 1)
{
errors.Add(new TableDesignerIssue()
if (existingNames.Contains(checkConstraint.Name))
{
Description = string.Format("The name '{0}' is already used by another constraint. Row number: {1}.", checkConstraint.Name, i + 1),
PropertyPath = new object[] { TablePropertyNames.CheckConstraints, i, CheckConstraintPropertyNames.Name }
});
}
else
{
existingNames.Add(checkConstraint.Name);
errors.Add(new TableDesignerIssue()
{
Description = string.Format("The name '{0}' is already used by another constraint. Row number: {1}.", checkConstraint.Name, i + 1),
PropertyPath = new object[] { TablePropertyNames.CheckConstraints, i, CheckConstraintPropertyNames.Name }
});
}
else
{
existingNames.Add(checkConstraint.Name);
}
}
}
for (int i = 0; i < table.EdgeConstraints.Items.Count; i++)
{
var edgeConstraint = table.EdgeConstraints.Items[i];
if (existingNames.Contains(edgeConstraint.Name))
if (currentSchemaConstraints.ContainsKey(edgeConstraint.Name) && currentSchemaConstraints[edgeConstraint.Name] > 1)
{
errors.Add(new TableDesignerIssue()
if (existingNames.Contains(edgeConstraint.Name))
{
Description = string.Format("The name '{0}' is already used by another constraint. Row number: {1}.", edgeConstraint.Name, i + 1),
PropertyPath = new object[] { TablePropertyNames.EdgeConstraints, i, EdgeConstraintPropertyNames.Name }
});
}
else
{
existingNames.Add(edgeConstraint.Name);
errors.Add(new TableDesignerIssue()
{
Description = string.Format("The name '{0}' is already used by another constraint. Row number: {1}.", edgeConstraint.Name, i + 1),
PropertyPath = new object[] { TablePropertyNames.EdgeConstraints, i, EdgeConstraintPropertyNames.Name }
});
}
else
{
existingNames.Add(edgeConstraint.Name);
}
}
}
return errors;
@@ -233,8 +251,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class NoDuplicateColumnNameRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
var existingNames = new HashSet<string>();
for (int i = 0; i < table.Columns.Items.Count; i++)
@@ -259,8 +278,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class NoDuplicateIndexNameRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
var existingNames = new HashSet<string>();
for (int i = 0; i < table.Indexes.Items.Count; i++)
@@ -285,8 +305,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class EdgeConstraintMustHaveClausesRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
for (int i = 0; i < table.EdgeConstraints.Items.Count; i++)
{
@@ -306,8 +327,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class EdgeConstraintNoRepeatingClausesRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
for (int i = 0; i < table.EdgeConstraints.Items.Count; i++)
{
@@ -337,8 +359,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class MemoryOptimizedTableMustHaveNonClusteredPrimaryKeyRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
if (table.IsMemoryOptimized && (table.PrimaryKey == null || table.PrimaryKey.IsClustered))
{
@@ -354,8 +377,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class TemporalTableMustHavePrimaryKeyRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
if (table.SystemVersioningHistoryTable != null && table.PrimaryKey == null)
{
@@ -370,8 +394,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class TemporalTableMustHavePeriodColumns : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
if (table.SystemVersioningHistoryTable != null && !table.PeriodColumnsDefined)
{
@@ -386,8 +411,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class PeriodColumnsRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
var rowStart = table.Columns.Items.Where(c => c.GeneratedAlwaysAs == ColumnGeneratedAlwaysAsType.GeneratedAlwaysAsRowStart);
var rowEnd = table.Columns.Items.Where(c => c.GeneratedAlwaysAs == ColumnGeneratedAlwaysAsType.GeneratedAlwaysAsRowEnd);
@@ -411,8 +437,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class ColumnsInPrimaryKeyCannotBeNullableRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
for (int i = 0; i < table.Columns.Items.Count; i++)
{
@@ -432,8 +459,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class OnlyDurableMemoryOptimizedTableCanBeSystemVersionedRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
if (table.Durability == TableDurability.SchemaOnly && table.IsMemoryOptimized && table.IsSystemVersioningEnabled)
{
@@ -448,8 +476,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class TableMustHaveAtLeastOneColumnRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
if (!table.IsEdge && table.Columns.Items.Count == 0)
{
@@ -464,8 +493,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class MemoryOptimizedTableIdentityColumnRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
if (table.IsMemoryOptimized)
{
@@ -489,8 +519,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class TableShouldAvoidHavingMultipleEdgeConstraintsRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
public List<TableDesignerIssue> Run(Dac.TableDesigner designer)
{
var table = designer.TableViewModel;
var errors = new List<TableDesignerIssue>();
if (table.EdgeConstraints.Items.Count > 1)
{