Skip to content

Commit

Permalink
Add support for index sort order (descending) (#27210)
Browse files Browse the repository at this point in the history
Closes #4150
  • Loading branch information
roji committed Jan 22, 2022
1 parent a482bd1 commit 786798e
Show file tree
Hide file tree
Showing 58 changed files with 1,187 additions and 109 deletions.
21 changes: 20 additions & 1 deletion src/EFCore.Abstractions/IndexAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ namespace Microsoft.EntityFrameworkCore;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class IndexAttribute : Attribute
{
private bool? _isUnique;
private string? _name;
private bool? _isUnique;
private bool[]? _isDescending;

/// <summary>
/// Initializes a new instance of the <see cref="IndexAttribute" /> class.
Expand Down Expand Up @@ -54,6 +55,24 @@ public bool IsUnique
set => _isUnique = value;
}

/// <summary>
/// A set of values indicating whether each corresponding index column has descending sort order.
/// </summary>
public bool[]? IsDescending
{
get => _isDescending;
set
{
if (value is not null && value.Length != PropertyNames.Count)
{
throw new ArgumentException(
AbstractionsStrings.InvalidNumberOfIndexSortOrderValues(value.Length, PropertyNames.Count), nameof(IsDescending));
}

_isDescending = value;
}
}

/// <summary>
/// Checks whether <see cref="IsUnique" /> has been explicitly set to a value.
/// </summary>
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore.Abstractions/Properties/AbstractionsStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,7 @@
<data name="CollectionArgumentIsEmpty" xml:space="preserve">
<value>The collection argument '{argumentName}' must contain at least one element.</value>
</data>
<data name="InvalidNumberOfIndexSortOrderValues" xml:space="preserve">
<value>Invalid number of index sort order values: {numValues} values were provided, but the index has {numProperties} properties.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,14 @@ protected virtual void Generate(CreateIndexOperation operation, IndentedStringBu
.Append("unique: true");
}

if (operation.IsDescending is not null)
{
builder
.AppendLine(",")
.Append("descending: ")
.Append(Code.Literal(operation.IsDescending));
}

if (operation.Filter != null)
{
builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,15 @@ protected virtual void GenerateIndex(
.Append(".IsUnique()");
}

if (index.IsDescending is not null)
{
stringBuilder
.AppendLine()
.Append(".IsDescending(")
.Append(string.Join(", ", index.IsDescending.Select(Code.Literal)))
.Append(')');
}

GenerateIndexAnnotations(indexBuilderName, index, stringBuilder);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,11 @@ private void GenerateIndex(IIndex index)
lines.Add($".{nameof(IndexBuilder.IsUnique)}()");
}

if (index.IsDescending is not null)
{
lines.Add($".{nameof(IndexBuilder.IsDescending)}({string.Join(", ", index.IsDescending.Select(d => _code.Literal(d)))})");
}

GenerateAnnotations(index, annotations, lines);

AppendMultiLineFluentApi(index.DeclaringEntityType, lines);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ private void GenerateIndexAttributes(IEntityType entityType)
indexAttribute.AddParameter($"{nameof(IndexAttribute.IsUnique)} = {_code.Literal(index.IsUnique)}");
}

if (index.IsDescending is not null)
{
indexAttribute.AddParameter($"{nameof(IndexAttribute.IsDescending)} = {_code.UnknownLiteral(index.IsDescending)}");
}

_sb.AppendLine(indexAttribute.ToString());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -627,9 +627,14 @@ protected virtual EntityTypeBuilder VisitIndexes(EntityTypeBuilder builder, ICol

indexBuilder = indexBuilder.IsUnique(index.IsUnique);

if (index.IsDescending.Any(desc => desc))
{
indexBuilder = indexBuilder.IsDescending(index.IsDescending.ToArray());
}

if (index.Filter != null)
{
indexBuilder.HasFilter(index.Filter);
indexBuilder = indexBuilder.HasFilter(index.Filter);
}

indexBuilder.Metadata.AddAnnotations(index.GetAnnotations());
Expand Down
22 changes: 20 additions & 2 deletions src/EFCore.Relational/Metadata/ITableIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public interface ITableIndex : IAnnotatable
/// </summary>
bool IsUnique { get; }

/// <summary>
/// A set of values indicating whether each corresponding index column has descending sort order.
/// </summary>
IReadOnlyList<bool>? IsDescending { get; }

/// <summary>
/// Gets the expression used as the index filter.
/// </summary>
Expand Down Expand Up @@ -70,8 +75,21 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt

builder
.Append(Name)
.Append(' ')
.Append(ColumnBase.Format(Columns));
.Append(" {")
.AppendJoin(
", ",
Enumerable.Range(0, Columns.Count)
.Select(
i =>
$@"'{Columns[i].Name}'{(
MappedIndexes.First() is not RuntimeIndex
&& IsDescending is not null
&& i < IsDescending.Count
&& IsDescending[i]
? " Desc"
: ""
)}"))
.Append('}');

if (IsUnique)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ public static bool AreCompatible(
{
throw new InvalidOperationException(
RelationalStrings.DuplicateIndexTableMismatch(
index.Properties.Format(),
index.DisplayName(),
index.DeclaringEntityType.DisplayName(),
duplicateIndex.Properties.Format(),
duplicateIndex.DisplayName(),
duplicateIndex.DeclaringEntityType.DisplayName(),
index.GetDatabaseName(storeObject),
index.DeclaringEntityType.GetSchemaQualifiedTableName(),
Expand All @@ -50,9 +50,9 @@ public static bool AreCompatible(
{
throw new InvalidOperationException(
RelationalStrings.DuplicateIndexColumnMismatch(
index.Properties.Format(),
index.DisplayName(),
index.DeclaringEntityType.DisplayName(),
duplicateIndex.Properties.Format(),
duplicateIndex.DisplayName(),
duplicateIndex.DeclaringEntityType.DisplayName(),
index.DeclaringEntityType.GetSchemaQualifiedTableName(),
index.GetDatabaseName(storeObject),
Expand All @@ -69,9 +69,29 @@ public static bool AreCompatible(
{
throw new InvalidOperationException(
RelationalStrings.DuplicateIndexUniquenessMismatch(
index.Properties.Format(),
index.DisplayName(),
index.DeclaringEntityType.DisplayName(),
duplicateIndex.Properties.Format(),
duplicateIndex.DisplayName(),
duplicateIndex.DeclaringEntityType.DisplayName(),
index.DeclaringEntityType.GetSchemaQualifiedTableName(),
index.GetDatabaseName(storeObject)));
}

return false;
}

if (index.IsDescending is null != duplicateIndex.IsDescending is null
|| (index.IsDescending is not null
&& duplicateIndex.IsDescending is not null
&& !index.IsDescending.SequenceEqual(duplicateIndex.IsDescending)))
{
if (shouldThrow)
{
throw new InvalidOperationException(
RelationalStrings.DuplicateIndexSortOrdersMismatch(
index.DisplayName(),
index.DeclaringEntityType.DisplayName(),
duplicateIndex.DisplayName(),
duplicateIndex.DeclaringEntityType.DisplayName(),
index.DeclaringEntityType.GetSchemaQualifiedTableName(),
index.GetDatabaseName(storeObject)));
Expand All @@ -82,16 +102,21 @@ public static bool AreCompatible(

if (index.GetFilter(storeObject) != duplicateIndex.GetFilter(storeObject))
{
throw new InvalidOperationException(
RelationalStrings.DuplicateIndexFiltersMismatch(
index.Properties.Format(),
index.DeclaringEntityType.DisplayName(),
duplicateIndex.Properties.Format(),
duplicateIndex.DeclaringEntityType.DisplayName(),
index.DeclaringEntityType.GetSchemaQualifiedTableName(),
index.GetDatabaseName(storeObject),
index.GetFilter(),
duplicateIndex.GetFilter()));
if (shouldThrow)
{
throw new InvalidOperationException(
RelationalStrings.DuplicateIndexFiltersMismatch(
index.DisplayName(),
index.DeclaringEntityType.DisplayName(),
duplicateIndex.DisplayName(),
duplicateIndex.DeclaringEntityType.DisplayName(),
index.DeclaringEntityType.GetSchemaQualifiedTableName(),
index.GetDatabaseName(storeObject),
index.GetFilter(),
duplicateIndex.GetFilter()));
}

return false;
}

return true;
Expand Down
4 changes: 4 additions & 0 deletions src/EFCore.Relational/Metadata/Internal/TableIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ public override bool IsReadOnly
/// <inheritdoc />
public virtual bool IsUnique { get; }

/// <inheritdoc />
public virtual IReadOnlyList<bool>? IsDescending
=> MappedIndexes.First().IsDescending;

/// <inheritdoc />
public virtual string? Filter
=> MappedIndexes.First().GetFilter(StoreObjectIdentifier.Table(Table.Name, Table.Schema));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1393,6 +1393,10 @@ protected virtual IEnumerable<MigrationOperation> Diff(

private bool IndexStructureEquals(ITableIndex source, ITableIndex target, DiffContext diffContext)
=> source.IsUnique == target.IsUnique
&& ((source.IsDescending is null && target.IsDescending is null)
|| (source.IsDescending is not null
&& target.IsDescending is not null
&& source.IsDescending.SequenceEqual(target.IsDescending)))
&& source.Filter == target.Filter
&& !HasDifferences(source.GetAnnotations(), target.GetAnnotations())
&& source.Columns.Select(p => p.Name).SequenceEqual(
Expand Down
19 changes: 16 additions & 3 deletions src/EFCore.Relational/Migrations/MigrationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -601,21 +601,27 @@ public virtual AlterOperationBuilder<AlterTableOperation> AlterTable(
/// <param name="schema">The schema that contains the table, or <see langword="null" /> to use the default schema.</param>
/// <param name="unique">Indicates whether or not the index enforces uniqueness.</param>
/// <param name="filter">The filter to apply to the index, or <see langword="null" /> for no filter.</param>
/// <param name="descending">
/// A set of values indicating whether each corresponding index column has descending sort order.
/// If <see langword="null" />, all columns will have ascending order.
/// </param>
/// <returns>A builder to allow annotations to be added to the operation.</returns>
public virtual OperationBuilder<CreateIndexOperation> CreateIndex(
string name,
string table,
string column,
string? schema = null,
bool unique = false,
string? filter = null)
string? filter = null,
bool[]? descending = null)
=> CreateIndex(
name,
table,
new[] { Check.NotEmpty(column, nameof(column)) },
schema,
unique,
filter);
filter,
descending);

/// <summary>
/// Builds a <see cref="CreateIndexOperation" /> to create a new composite (multi-column) index.
Expand All @@ -629,14 +635,19 @@ public virtual OperationBuilder<CreateIndexOperation> CreateIndex(
/// <param name="schema">The schema that contains the table, or <see langword="null" /> to use the default schema.</param>
/// <param name="unique">Indicates whether or not the index enforces uniqueness.</param>
/// <param name="filter">The filter to apply to the index, or <see langword="null" /> for no filter.</param>
/// <param name="descending">
/// A set of values indicating whether each corresponding index column has descending sort order.
/// If <see langword="null" />, all columns will have ascending order.
/// </param>
/// <returns>A builder to allow annotations to be added to the operation.</returns>
public virtual OperationBuilder<CreateIndexOperation> CreateIndex(
string name,
string table,
string[] columns,
string? schema = null,
bool unique = false,
string? filter = null)
string? filter = null,
bool[]? descending = null)
{
Check.NotEmpty(name, nameof(name));
Check.NotEmpty(table, nameof(table));
Expand All @@ -649,8 +660,10 @@ public virtual OperationBuilder<CreateIndexOperation> CreateIndex(
Name = name,
Columns = columns,
IsUnique = unique,
IsDescending = descending,
Filter = filter
};

Operations.Add(operation);

return new OperationBuilder<CreateIndexOperation>(operation);
Expand Down
Loading

0 comments on commit 786798e

Please sign in to comment.