support issue severity (#1430)

This commit is contained in:
Alan Ren
2022-03-17 10:16:42 -07:00
committed by GitHub
parent 271f5f881a
commit 92cb4beca0
11 changed files with 215 additions and 112 deletions

View File

@@ -5,7 +5,7 @@
using System.Collections.Generic;
using Microsoft.Data.Tools.Sql.DesignServices.TableDesigner;
using ValidationError = Microsoft.SqlTools.ServiceLayer.TableDesigner.Contracts.TableDesignerValidationError;
using TableDesignerIssue = Microsoft.SqlTools.ServiceLayer.TableDesigner.Contracts.TableDesignerIssue;
using System.Linq;
namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
{
@@ -28,15 +28,16 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
new OnlyDurableMemoryOptimizedTableCanBeSystemVersionedRule(),
new TemporalTableMustHavePrimaryKeyRule(),
new TableMustHaveAtLeastOneColumnRule(),
new MemoryOptimizedTableIdentityColumnRule()
new MemoryOptimizedTableIdentityColumnRule(),
new TableShouldAvoidHavingMultipleEdgeConstraintsRule()
};
/// <summary>
/// Validate the table and return the validation errors.
/// </summary>
public static List<ValidationError> Validate(TableViewModel table)
public static List<TableDesignerIssue> Validate(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
foreach (var rule in Rules)
{
errors.AddRange(rule.Run(table));
@@ -47,22 +48,22 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public interface ITableDesignerValidationRule
{
List<ValidationError> Run(TableViewModel table);
List<TableDesignerIssue> Run(TableViewModel table);
}
public class IndexMustHaveColumnsRule : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
for (int i = 0; i < table.Indexes.Items.Count; i++)
{
var index = table.Indexes.Items[i];
if (index.Columns.Count == 0)
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = string.Format("Index '{0}' does not have any columns associated with it.", index.Name),
Description = string.Format("Index '{0}' does not have any columns associated with it.", index.Name),
PropertyPath = new object[] { TablePropertyNames.Indexes, i }
});
}
@@ -73,17 +74,17 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class ForeignKeyMustHaveColumnsRule : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
for (int i = 0; i < table.ForeignKeys.Items.Count; i++)
{
var foreignKey = table.ForeignKeys.Items[i];
if (foreignKey.Columns.Count == 0)
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = string.Format("Foreign key '{0}' does not have any columns specified.", foreignKey.Name),
Description = string.Format("Foreign key '{0}' does not have any columns specified.", foreignKey.Name),
PropertyPath = new object[] { TablePropertyNames.ForeignKeys, i }
});
}
@@ -94,9 +95,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class ColumnCanOnlyAppearOnceInIndexRule : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
for (int i = 0; i < table.Indexes.Items.Count; i++)
{
var index = table.Indexes.Items[i];
@@ -106,9 +107,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
var columnSpec = index.Columns[j];
if (existingColumns.Contains(columnSpec.Column))
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = string.Format("Column with name '{0}' has already been added to the index '{1}'. Row number: {2}.", columnSpec.Column, index.Name, j + 1),
Description = string.Format("Column with name '{0}' has already been added to the index '{1}'. Row number: {2}.", columnSpec.Column, index.Name, j + 1),
PropertyPath = new object[] { TablePropertyNames.Indexes, i, IndexPropertyNames.Columns, j }
});
}
@@ -124,9 +125,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class ColumnCanOnlyAppearOnceInForeignKeyRule : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
for (int i = 0; i < table.ForeignKeys.Items.Count; i++)
{
var foreignKey = table.ForeignKeys.Items[i];
@@ -136,9 +137,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
var column = foreignKey.Columns[j];
if (existingColumns.Contains(column))
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = string.Format("Column with name '{0}' has already been added to the foreign key '{1}'. Row number: {2}.", column, foreignKey.Name, j + 1),
Description = string.Format("Column with name '{0}' has already been added to the foreign key '{1}'. Row number: {2}.", column, foreignKey.Name, j + 1),
PropertyPath = new object[] { TablePropertyNames.ForeignKeys, i, ForeignKeyPropertyNames.ColumnMapping, j, ForeignKeyColumnMappingPropertyNames.Column }
});
}
@@ -154,9 +155,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
var foreignColumn = foreignKey.ForeignColumns[j];
if (existingForeignColumns.Contains(foreignColumn))
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = string.Format("Foreign column with name '{0}' has already been added to the foreign key '{1}'. Row number: {2}.", foreignColumn, foreignKey.Name, j + 1),
Description = string.Format("Foreign column with name '{0}' has already been added to the foreign key '{1}'. Row number: {2}.", foreignColumn, foreignKey.Name, j + 1),
PropertyPath = new object[] { TablePropertyNames.ForeignKeys, i, ForeignKeyPropertyNames.ColumnMapping, j, ForeignKeyColumnMappingPropertyNames.ForeignColumn }
});
}
@@ -172,18 +173,18 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class NoDuplicateConstraintNameRule : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
var existingNames = new HashSet<string>();
for (int i = 0; i < table.ForeignKeys.Items.Count; i++)
{
var foreignKey = table.ForeignKeys.Items[i];
if (existingNames.Contains(foreignKey.Name))
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = string.Format("The name '{0}' is already used by another constraint. Row number: {1}.", foreignKey.Name, i + 1),
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 }
});
}
@@ -198,9 +199,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
var checkConstraint = table.CheckConstraints.Items[i];
if (existingNames.Contains(checkConstraint.Name))
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = string.Format("The name '{0}' is already used by another constraint. Row number: {1}.", checkConstraint.Name, i + 1),
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 }
});
}
@@ -215,9 +216,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
var edgeConstraint = table.EdgeConstraints.Items[i];
if (existingNames.Contains(edgeConstraint.Name))
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = string.Format("The name '{0}' is already used by another constraint. Row number: {1}.", edgeConstraint.Name, i + 1),
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 }
});
}
@@ -232,18 +233,18 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class NoDuplicateColumnNameRule : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
var existingNames = new HashSet<string>();
for (int i = 0; i < table.Columns.Items.Count; i++)
{
var column = table.Columns.Items[i];
if (existingNames.Contains(column.Name))
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = string.Format("The name '{0}' is already used by another column. Row number: {1}.", column.Name, i + 1),
Description = string.Format("The name '{0}' is already used by another column. Row number: {1}.", column.Name, i + 1),
PropertyPath = new object[] { TablePropertyNames.Columns, i, TableColumnPropertyNames.Name }
});
}
@@ -258,18 +259,18 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class NoDuplicateIndexNameRule : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
var existingNames = new HashSet<string>();
for (int i = 0; i < table.Indexes.Items.Count; i++)
{
var index = table.Indexes.Items[i];
if (existingNames.Contains(index.Name))
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = string.Format("The name '{0}' is already used by another index. Row number: {1}.", index.Name, i + 1),
Description = string.Format("The name '{0}' is already used by another index. Row number: {1}.", index.Name, i + 1),
PropertyPath = new object[] { TablePropertyNames.Indexes, i, IndexPropertyNames.Name }
});
}
@@ -284,17 +285,17 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class EdgeConstraintMustHaveClausesRule : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
for (int i = 0; i < table.EdgeConstraints.Items.Count; i++)
{
var edgeConstraint = table.EdgeConstraints.Items[i];
if (edgeConstraint.Clauses.Count == 0)
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = string.Format("Edge constraint '{0}' does not have any clauses specified.", edgeConstraint.Name),
Description = string.Format("Edge constraint '{0}' does not have any clauses specified.", edgeConstraint.Name),
PropertyPath = new object[] { TablePropertyNames.EdgeConstraints, i }
});
}
@@ -305,9 +306,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class EdgeConstraintNoRepeatingClausesRule : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
for (int i = 0; i < table.EdgeConstraints.Items.Count; i++)
{
var edgeConstraint = table.EdgeConstraints.Items[i];
@@ -318,9 +319,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
var pair = string.Format("{0} - {1}", clause.FromTable, clause.ToTable);
if (existingPairs.Contains(pair))
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = string.Format("The pair '{0}' is already defined by another clause in the edge constraint. Row number: {1}.", pair, j + 1),
Description = string.Format("The pair '{0}' is already defined by another clause in the edge constraint. Row number: {1}.", pair, j + 1),
PropertyPath = new object[] { TablePropertyNames.EdgeConstraints, i, EdgeConstraintPropertyNames.Clauses, j, EdgeConstraintClausePropertyNames.FromTable }
});
}
@@ -336,14 +337,14 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class MemoryOptimizedTableMustHaveNonClusteredPrimaryKeyRule : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
if (table.IsMemoryOptimized && (table.PrimaryKey == null || table.PrimaryKey.IsClustered))
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = "Memory-optimized table must have non-clustered primary key.",
Description = "Memory-optimized table must have non-clustered primary key.",
PropertyPath = new object[] { TablePropertyNames.PrimaryKeyIsClustered }
});
}
@@ -353,14 +354,14 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class TemporalTableMustHavePrimaryKeyRule : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
if (table.SystemVersioningHistoryTable != null && table.PrimaryKey == null)
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = "System versioned table must have primary key."
Description = "System versioned table must have primary key."
});
}
return errors;
@@ -369,14 +370,14 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class TemporalTableMustHavePeriodColumns : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
if (table.SystemVersioningHistoryTable != null && !table.PeriodColumnsDefined)
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = "System versioned table must have the period columns defined."
Description = "System versioned table must have the period columns defined."
});
}
return errors;
@@ -385,23 +386,23 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class PeriodColumnsRule : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
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);
if (rowStart.Count() > 1 || rowEnd.Count() > 1)
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = "Period columns (Generated Always As Row Start/End) can only be defined once."
Description = "Period columns (Generated Always As Row Start/End) can only be defined once."
});
}
else if (rowEnd.Count() != rowStart.Count())
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = "Period columns (Generated Always As Row Start/End) must be defined as pair. If one is defined, the other must also be defined"
Description = "Period columns (Generated Always As Row Start/End) must be defined as pair. If one is defined, the other must also be defined"
});
}
return errors;
@@ -410,17 +411,17 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class ColumnsInPrimaryKeyCannotBeNullableRule : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
for (int i = 0; i < table.Columns.Items.Count; i++)
{
var column = table.Columns.Items[i];
if (column.IsPrimaryKey && column.IsNullable)
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = "Columns in primary key cannot be nullable.",
Description = "Columns in primary key cannot be nullable.",
PropertyPath = new object[] { TablePropertyNames.Columns, i }
});
}
@@ -431,14 +432,14 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class OnlyDurableMemoryOptimizedTableCanBeSystemVersionedRule : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
if (table.Durability == TableDurability.SchemaOnly && table.IsMemoryOptimized && table.IsSystemVersioningEnabled)
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = "Only durable (DURABILITY = SCHEMA_AND_DATA) memory-optimized tables can be system-versioned."
Description = "Only durable (DURABILITY = SCHEMA_AND_DATA) memory-optimized tables can be system-versioned."
});
}
return errors;
@@ -447,14 +448,14 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class TableMustHaveAtLeastOneColumnRule : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
if (!table.IsEdge && table.Columns.Items.Count == 0)
{
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = "A table must have at least one column defined."
Description = "A table must have at least one column defined."
});
}
return errors;
@@ -463,9 +464,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
public class MemoryOptimizedTableIdentityColumnRule : ITableDesignerValidationRule
{
public List<ValidationError> Run(TableViewModel table)
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<ValidationError>();
var errors = new List<TableDesignerIssue>();
if (table.IsMemoryOptimized)
{
for (int i = 0; i < table.Columns.Items.Count; i++)
@@ -474,9 +475,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
if (column.IsIdentity && (column.IdentitySeed != 1 || column.IdentityIncrement != 1))
{
var propertyName = column.IdentitySeed != 1 ? TableColumnPropertyNames.IdentitySeed : TableColumnPropertyNames.IdentityIncrement;
errors.Add(new ValidationError()
errors.Add(new TableDesignerIssue()
{
Message = "The use of seed and increment values other than 1 is not supported with memory optimized tables.",
Description = "The use of seed and increment values other than 1 is not supported with memory optimized tables.",
PropertyPath = new object[] { TablePropertyNames.Columns, i, propertyName }
});
}
@@ -485,4 +486,21 @@ namespace Microsoft.SqlTools.ServiceLayer.TableDesigner
return errors;
}
}
public class TableShouldAvoidHavingMultipleEdgeConstraintsRule : ITableDesignerValidationRule
{
public List<TableDesignerIssue> Run(TableViewModel table)
{
var errors = new List<TableDesignerIssue>();
if (table.EdgeConstraints.Items.Count > 1)
{
errors.Add(new TableDesignerIssue()
{
Description = "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. Please refer to https://docs.microsoft.com/sql/relational-databases/tables/graph-edge-constraints for more details.",
Severity = Contracts.IssueSeverity.Warning
});
}
return errors;
}
}
}