Skip to content

Commit

Permalink
Fixes #1259 Create.UniqueConstraint API should be as feature complete…
Browse files Browse the repository at this point in the history
… as Create.Index for creating unique indexes
  • Loading branch information
jzabroski committed Dec 19, 2023
1 parent 218aee1 commit 99926dd
Show file tree
Hide file tree
Showing 9 changed files with 559 additions and 34 deletions.
7 changes: 5 additions & 2 deletions FluentMigrator.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FRESOURCE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BETWEEN_USING_GROUPS/@EntryValue">1</s:Int64>
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Copyright (c) $CURRENT_YEAR$, FluentMigrator Project&#xD;
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Copyright (c) ${CurrentDate.Year}, FluentMigrator Project&#xD;
&#xD;
Licensed under the Apache License, Version 2.0 (the "License");&#xD;
you may not use this file except in compliance with the License.&#xD;
Expand All @@ -100,6 +100,7 @@ limitations under the License.&#xD;
<s:String x:Key="/Default/Environment/PerformanceGuide/SwitchBehaviour/=VCS/@EntryIndexedValue">DO_NOTHING</s:String>
<s:String x:Key="/Default/Environment/PerformanceGuide/SwitchBehaviour/=VsBulb/@EntryIndexedValue">DO_NOTHING</s:String>
<s:String x:Key="/Default/Environment/PerformanceGuide/SwitchBehaviour/=XAML_0020Designer/@EntryIndexedValue">DO_NOTHING</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECodeCleanup_002EFileHeader_002EFileHeaderSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
Expand All @@ -125,4 +126,6 @@ limitations under the License.&#xD;
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=D2493F411116E746BE25897408D6C410/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=D2493F411116E746BE25897408D6C410/Text/@EntryValue">Initializes a new instance of the &lt;see cref="$className$"/&gt; class.</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Maintenances/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Migrator/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Migrator/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=TESTPRIMARYKEY/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=TESTUNIQUECONSTRAINT/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,28 @@ namespace FluentMigrator.Infrastructure.Extensions
/// </summary>
public static class AdditionalFeaturesExtensions
{
/// <summary>
/// Gets an additional feature value
/// </summary>
/// <typeparam name="T">The value type</typeparam>
/// <param name="additionalFeatures">The additional feature values</param>
/// <param name="key">The key into the <see cref="ISupportAdditionalFeatures.AdditionalFeatures"/> dictionary</param>
/// <param name="value">The value found if successful/</param>
/// <returns>Whether the <paramref name="key"/> was found in the <paramref name="additionalFeatures"/>. <value>false</value> means the feature was not set by the client.</returns>
public static bool TryGetAdditionalFeature<T>(this ISupportAdditionalFeatures additionalFeatures, string key, out T value)
{
var dict = additionalFeatures.AdditionalFeatures;
if (!dict.TryGetValue(key, out var val))
{
value = default;
return false;
}

value = (T)val;

return true;
}

/// <summary>
/// Gets an additional feature value
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public CreateIndexExpressionNonKeyBuilder(ICreateIndexOnColumnSyntax expression,

public ICreateIndexNonKeyColumnSyntax Include(string columnName)
{
SupportAdditionalFeatures.Include(columnName);
SupportAdditionalFeatures.CreateIndexInclude(columnName);
return this;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// limitations under the License.
#endregion

using FluentMigrator.Builders.Create.Constraint;
using FluentMigrator.Builders.Create.Index;
using FluentMigrator.Infrastructure;
using FluentMigrator.Infrastructure.Extensions;
Expand All @@ -22,13 +23,20 @@ namespace FluentMigrator.SqlServer
{
public static partial class SqlServerExtensions
{
public static readonly string IndexFilter = "SqlServerIndexFilter";

public static ICreateIndexOptionsSyntax Filter(this ICreateIndexOptionsSyntax expression, string filter)
{
var additionalFeatures = expression as ISupportAdditionalFeatures;
additionalFeatures.SetAdditionalFeature(IndexFilter, filter);
return expression;
}

public static ICreateConstraintOptionsSyntax Filter(
this ICreateConstraintOptionsSyntax expression,
string filter)
{
var additionalFeatures = expression as ISupportAdditionalFeatures;
additionalFeatures.SetAdditionalFeature(UniqueConstraintFilter, filter);
return expression;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using System;
using System.Collections.Generic;

using FluentMigrator.Builders.Create.Constraint;
using FluentMigrator.Builders.Create.Index;
using FluentMigrator.Infrastructure;
using FluentMigrator.Infrastructure.Extensions;
Expand All @@ -29,21 +30,36 @@ public static partial class SqlServerExtensions
public static ICreateIndexOptionsSyntax Include(this ICreateIndexOptionsSyntax expression, string columnName)
{
var additionalFeatures = expression as ISupportAdditionalFeatures;
additionalFeatures.Include(columnName);
additionalFeatures.CreateIndexInclude(columnName);
return expression;
}

public static ICreateIndexNonKeyColumnSyntax Include(this ICreateIndexOnColumnSyntax expression, string columnName)
{
var additionalFeatures = expression as ISupportAdditionalFeatures;
additionalFeatures.Include(columnName);
additionalFeatures.CreateIndexInclude(columnName);
return new CreateIndexExpressionNonKeyBuilder(expression, additionalFeatures);
}

internal static void Include(this ISupportAdditionalFeatures additionalFeatures, string columnName)
internal static void CreateIndexInclude(this ISupportAdditionalFeatures additionalFeatures, string columnName)
{
if (additionalFeatures == null)
throw new InvalidOperationException(UnsupportedMethodMessage(nameof(Include), nameof(ISupportAdditionalFeatures)));
throw new InvalidOperationException(UnsupportedMethodMessage(nameof(CreateIndexInclude), nameof(ISupportAdditionalFeatures)));
var includes = additionalFeatures.GetAdditionalFeature<IList<IndexIncludeDefinition>>(IncludesList, () => new List<IndexIncludeDefinition>());
includes.Add(new IndexIncludeDefinition { Name = columnName });
}

public static ICreateConstraintOptionsSyntax Include(this ICreateConstraintOptionsSyntax expression, string columnName)
{
var additionalFeatures = expression as ISupportAdditionalFeatures;
additionalFeatures.CreateUniqueConstraintInclude(columnName);
return expression;
}

internal static void CreateUniqueConstraintInclude(this ISupportAdditionalFeatures additionalFeatures, string columnName)
{
if (additionalFeatures == null)
throw new InvalidOperationException(UnsupportedMethodMessage(nameof(CreateUniqueConstraintInclude), nameof(ISupportAdditionalFeatures)));
var includes = additionalFeatures.GetAdditionalFeature<IList<IndexIncludeDefinition>>(IncludesList, () => new List<IndexIncludeDefinition>());
includes.Add(new IndexIncludeDefinition { Name = columnName });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,13 @@ public static partial class SqlServerExtensions
public static readonly string ConstraintType = "SqlServerConstraintType";
public static readonly string IncludesList = "SqlServerIncludes";
public static readonly string OnlineIndex = "SqlServerOnlineIndex";
public static readonly string IndexFilter = "SqlServerIndexFilter";
public static readonly string RowGuidColumn = "SqlServerRowGuidColumn";
public static readonly string IndexColumnNullsDistinct = "SqlServerIndexColumnNullsDistinct";
public static readonly string SchemaAuthorization = "SqlServerSchemaAuthorization";
public static readonly string SparseColumn = "SqlServerSparseColumn";
public static readonly string UniqueConstraintFilter = "SqlServerUniqueConstraintFilter";
public static readonly string UniqueConstraintIncludesList = "SqlServerUniqueConstraintIncludes";

/// <summary>
/// Inserts data using Sql Server's IDENTITY INSERT feature.
Expand All @@ -58,14 +61,16 @@ private static void SetConstraintType(ICreateConstraintOptionsSyntax expression,
additionalFeatures.AdditionalFeatures[ConstraintType] = type;
}

public static void Clustered(this ICreateConstraintOptionsSyntax expression)
public static ICreateConstraintOptionsSyntax Clustered(this ICreateConstraintOptionsSyntax expression)
{
SetConstraintType(expression, SqlServerConstraintType.Clustered);
return expression;
}

public static void NonClustered(this ICreateConstraintOptionsSyntax expression)
public static ICreateConstraintOptionsSyntax NonClustered(this ICreateConstraintOptionsSyntax expression)
{
SetConstraintType(expression, SqlServerConstraintType.NonClustered);
return expression;
}

public static ICreateTableColumnOptionOrWithColumnSyntax RowGuid(this ICreateTableColumnOptionOrWithColumnSyntax expression)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,19 @@ namespace FluentMigrator.Runner.Generators.SqlServer
{
public class SqlServer2005Generator : SqlServer2000Generator
{
private const string ErrorMessageFilteredIndexesAreNonClusteredIndexes =
"Filtered indexes are non-clustered indexes that have the addition of a WHERE clause. "
+ "SQL Server does not support clustered filtered indexes. "
+ "Create a non-clustered index with include columns instead to create a non-clustered covering index.";
private static readonly HashSet<string> _supportedAdditionalFeatures = new HashSet<string>
{
SqlServerExtensions.IncludesList,
SqlServerExtensions.OnlineIndex,
SqlServerExtensions.RowGuidColumn,
SqlServerExtensions.SchemaAuthorization,
SqlServerExtensions.ConstraintType,
SqlServerExtensions.UniqueConstraintFilter,
SqlServerExtensions.UniqueConstraintIncludesList
};

public SqlServer2005Generator()
Expand Down Expand Up @@ -83,17 +90,30 @@ public SqlServer2005Generator()

public override string IdentityInsert { get { return "SET IDENTITY_INSERT {0} {1}"; } }

public virtual string CreateUniqueConstraint { get { return "CREATE UNIQUE INDEX {1} ON {0} ({2}){3}{4}"; } }

public override string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}){5}{6}"; } }

public virtual string GetIncludeString(CreateIndexExpression column)
public virtual string GetIncludeString(CreateIndexExpression expression)
{
return GetIncludeStringFor(expression, SqlServerExtensions.IncludesList);
}

public virtual string GetIncludeString(CreateConstraintExpression expression)
{
var includes = column.GetAdditionalFeature<IList<IndexIncludeDefinition>>(SqlServerExtensions.IncludesList);
return GetIncludeStringFor(expression, SqlServerExtensions.UniqueConstraintIncludesList);
}

string[] indexIncludes = new string[includes?.Count ?? 0];
protected string GetIncludeStringFor(ISupportAdditionalFeatures expression, string featureKey)
{
if (!expression.TryGetAdditionalFeature<IList<IndexIncludeDefinition>>(featureKey, out var includes))
return string.Empty;

var indexIncludes = new string[includes?.Count ?? 0];

if (includes != null)
{
for (int i = 0; i != includes.Count; i++)
for (var i = 0; i != includes.Count; i++)
{
var includeDef = includes[i];
indexIncludes[i] = Quoter.QuoteColumnName(includeDef.Name);
Expand All @@ -105,6 +125,38 @@ public virtual string GetIncludeString(CreateIndexExpression column)

public virtual string GetFilterString(CreateIndexExpression createIndexExpression)
{
if (createIndexExpression.Index.TryGetAdditionalFeature<string>(SqlServerExtensions.IndexFilter, out var filter))
{
if (createIndexExpression.Index.IsClustered)
throw new Exception(ErrorMessageFilteredIndexesAreNonClusteredIndexes);

return " WHERE " + filter;
}

return string.Empty;
}

public virtual string GetFilterString(CreateConstraintExpression createConstraintExpression)
{
if (createConstraintExpression.Constraint.TryGetAdditionalFeature<string>(SqlServerExtensions.UniqueConstraintFilter, out var filter))
{
if (createConstraintExpression.Constraint.IsUniqueConstraint)
{
if (!createConstraintExpression.Constraint.TryGetAdditionalFeature<SqlServerConstraintType>(
SqlServerExtensions.ConstraintType,
out var indexType)
|| indexType == SqlServerConstraintType.NonClustered)
{
return " WHERE " + filter;
}

throw new Exception(ErrorMessageFilteredIndexesAreNonClusteredIndexes);
}

throw new InvalidOperationException(
"Create Constraint Expressions with Filters are only supported for Unique Indexes.");
}

return string.Empty;
}

Expand Down Expand Up @@ -265,12 +317,29 @@ public override string Generate(DeleteIndexExpression expression)

public override string Generate(CreateConstraintExpression expression)
{
var filterParts = GetFilterString(expression);
var includeParts = GetIncludeString(expression);
var columns = string.Join(", ", expression.Constraint.Columns.Select(x => Quoter.QuoteColumnName(x)).ToArray());

if (expression.Constraint.IsUniqueConstraint)
if (expression.Constraint.TryGetAdditionalFeature<string>(SqlServerExtensions.UniqueConstraintFilter, out _) ||
expression.Constraint.TryGetAdditionalFeature<IList<IndexIncludeDefinition>>(SqlServerExtensions.UniqueConstraintIncludesList, out _))
return
string.Format(
CreateUniqueConstraint,
Quoter.QuoteTableName(expression.Constraint.TableName, expression.Constraint.SchemaName),
Quoter.QuoteConstraintName(expression.Constraint.ConstraintName),
columns,
includeParts,
filterParts
);

var withParts = GetWithOptions(expression);
var withPart = !string.IsNullOrEmpty(withParts)
? $" WITH ({withParts})"
: string.Empty;

return $"{base.Generate(expression)}{withPart}";
return $"{base.Generate(expression)}{filterParts}{withPart}";
}

public override string Generate(DeleteDefaultConstraintExpression expression)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,24 +113,6 @@ public override string Generate(CreateIndexExpression expression)
return sql;
}

public override string GetFilterString(CreateIndexExpression createIndexExpression)
{
var filter = createIndexExpression.Index.GetAdditionalFeature<string>(SqlServerExtensions.IndexFilter);


if (!string.IsNullOrWhiteSpace(filter))
{
if (createIndexExpression.Index.IsClustered)
throw new System.Exception("Filtered indexes are nonclustered indexes that have the additon of a WHERE clause. " +
"SQL Server does not support clustered filtered indexes. " +
"Create a non-clustered index with include columns instead to create a non-clustered covering index.");

return " WHERE " + filter;
}

return string.Empty;
}

/// <inheritdoc />
public override string GetWithOptions(ISupportAdditionalFeatures expression)
{
Expand Down

0 comments on commit 99926dd

Please sign in to comment.