Skip to content

Commit

Permalink
Add support for index sort order (descending)
Browse files Browse the repository at this point in the history
  • Loading branch information
roji committed Jan 19, 2022
1 parent 6523335 commit d03e2d9
Show file tree
Hide file tree
Showing 48 changed files with 883 additions and 54 deletions.
6 changes: 6 additions & 0 deletions src/EFCore.Abstractions/IndexAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ public bool IsUnique
set => _isUnique = value;
}

/// <summary>
/// Gets a set of values indicating whether each corresponding index column has descending sort order.
/// If less sort order values are provided than there are columns, the remaining columns will have ascending order.
/// </summary>
public bool[]? IsDescending { get; set; }

/// <summary>
/// Checks whether <see cref="IsUnique" /> has been explicitly set to a value.
/// </summary>
Expand Down
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.Length > 0)
{
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.Count > 0)
{
stringBuilder
.AppendLine()
.Append(".IsDescending(")
.Append(string.Join(", ", index.IsDescending.Select(Code.Literal)))
.Append(')');
}

GenerateIndexAnnotations(indexBuilderName, index, stringBuilder);
}

Expand Down
42 changes: 42 additions & 0 deletions src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,48 @@ private void GenerateIndex(IIndex index)
lines.Add($".{nameof(IndexBuilder.IsUnique)}()");
}

// Ascending/descending:
// If all ascending (IsDescending.Count == 0), no need to scaffold IsDescending() call (default)
// If all descending (IsDescending is all true), scaffold parameterless IsDescending()
// Otherwise, scaffold IsDescending() with values up until the last true (unspecified values default to false)
if (index.IsDescending.Count > 0)
{
var descendingBuilder = new StringBuilder()
.Append('.')
.Append(nameof(IndexBuilder.IsDescending))
.Append('(');

var isAnyAscending = false;
var lastDescending = -1;
for (var i = 0; i < index.IsDescending.Count; i++)
{
if (index.IsDescending[i])
{
lastDescending = i;
}
else
{
isAnyAscending = true;
}
}

if (isAnyAscending)
{
for (var i = 0; i <= lastDescending; i++)
{
if (i > 0)
{
descendingBuilder.Append(", ");
}

descendingBuilder.Append(_code.Literal(index.IsDescending[i]));
}
}

descendingBuilder.Append(')');
lines.Add(descendingBuilder.ToString());
}

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.Count > 0)
{
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 @@ -625,7 +625,12 @@ protected virtual EntityTypeBuilder VisitIndexes(EntityTypeBuilder builder, ICol
? builder.HasIndex(propertyNames)
: builder.HasIndex(propertyNames, index.Name);

indexBuilder = indexBuilder.IsUnique(index.IsUnique);
indexBuilder.IsUnique(index.IsUnique);

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

if (index.Filter != null)
{
Expand Down
14 changes: 12 additions & 2 deletions src/EFCore.Relational/Metadata/ITableIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ public interface ITableIndex : IAnnotatable
/// </summary>
bool IsUnique { get; }

/// <summary>
/// A set of values indicating whether each corresponding index column has descending sort order.
/// If less sort order values are provided than there are columns, the remaining columns will have ascending order.
/// </summary>
IReadOnlyList<bool> IsDescending { get; }

/// <summary>
/// Gets the expression used as the index filter.
/// </summary>
Expand Down Expand Up @@ -70,8 +76,12 @@ 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}'{(i < IsDescending.Count && IsDescending[i] ? " Desc" : "")}"))
.Append('}');

if (IsUnique)
{
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,7 @@ private static void PopulateConstraints(Table table)
continue;
}

tableIndex = new TableIndex(name, table, columns, index.IsUnique);
tableIndex = new TableIndex(name, table, columns, index.IsUnique, index.IsDescending);

table.Indexes.Add(name, tableIndex);
}
Expand Down
7 changes: 6 additions & 1 deletion src/EFCore.Relational/Metadata/Internal/TableIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ public TableIndex(
string name,
Table table,
IReadOnlyList<Column> columns,
bool unique)
bool unique,
IReadOnlyList<bool> isDescending)
{
Name = name;
Table = table;
Columns = columns;
IsUnique = unique;
IsDescending = isDescending;
}

/// <inheritdoc />
Expand Down Expand Up @@ -68,6 +70,9 @@ public override bool IsReadOnly
/// <inheritdoc />
public virtual bool IsUnique { get; }

/// <inheritdoc />
public virtual IReadOnlyList<bool> IsDescending { get; }

/// <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,7 @@ protected virtual IEnumerable<MigrationOperation> Diff(

private bool IndexStructureEquals(ITableIndex source, ITableIndex target, DiffContext diffContext)
=> source.IsUnique == target.IsUnique
&& source.IsDescending.SequenceEqual(target.IsDescending)
&& source.Filter == target.Filter
&& !HasDifferences(source.GetAnnotations(), target.GetAnnotations())
&& source.Columns.Select(p => p.Name).SequenceEqual(
Expand Down
25 changes: 22 additions & 3 deletions src/EFCore.Relational/Migrations/MigrationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -601,21 +601,28 @@ 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 less sort order values are provided than there are columns, the remaining columns will have ascending 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 +636,20 @@ 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 less sort order values are provided than there are columns, the remaining columns will have ascending 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 @@ -651,6 +664,12 @@ public virtual OperationBuilder<CreateIndexOperation> CreateIndex(
IsUnique = unique,
Filter = filter
};

if (descending is not null)
{
operation.IsDescending = descending;
}

Operations.Add(operation);

return new OperationBuilder<CreateIndexOperation>(operation);
Expand Down
42 changes: 31 additions & 11 deletions src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -428,9 +428,11 @@ protected virtual void Generate(
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name))
.Append(" ON ")
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Table, operation.Schema))
.Append(" (")
.Append(ColumnList(operation.Columns))
.Append(")");
.Append(" (");

GenerateIndexColumnList(operation, model, builder);

builder.Append(")");

IndexOptions(operation, model, builder);

Expand Down Expand Up @@ -1659,23 +1661,41 @@ protected virtual void CheckConstraint(
/// <param name="operation">The operation.</param>
/// <param name="model">The target model which may be <see langword="null" /> if the operations exist without a model.</param>
/// <param name="builder">The command builder to use to add the SQL fragment.</param>
protected virtual void IndexTraits(
MigrationOperation operation,
IModel? model,
MigrationCommandListBuilder builder)
protected virtual void IndexTraits(MigrationOperation operation, IModel? model, MigrationCommandListBuilder builder)
{
}

/// <summary>
/// Returns a SQL fragment for the column list of an index from a <see cref="CreateIndexOperation" />.
/// </summary>
/// <param name="operation">The operation.</param>
/// <param name="model">The target model which may be <see langword="null" /> if the operations exist without a model.</param>
/// <param name="builder">The command builder to use to add the SQL fragment.</param>
protected virtual void GenerateIndexColumnList(CreateIndexOperation operation, IModel? model, MigrationCommandListBuilder builder)
{
for (var i = 0; i < operation.Columns.Length; i++)
{
if (i > 0)
{
builder.Append(", ");
}

builder.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Columns[i]));

if (operation.IsDescending is not null && i < operation.IsDescending.Length && operation.IsDescending[i])
{
builder.Append(" DESC");
}
}
}

/// <summary>
/// Generates a SQL fragment for extras (filter, included columns, options) of an index from a <see cref="CreateIndexOperation" />.
/// </summary>
/// <param name="operation">The operation.</param>
/// <param name="model">The target model which may be <see langword="null" /> if the operations exist without a model.</param>
/// <param name="builder">The command builder to use to add the SQL fragment.</param>
protected virtual void IndexOptions(
CreateIndexOperation operation,
IModel? model,
MigrationCommandListBuilder builder)
protected virtual void IndexOptions(CreateIndexOperation operation, IModel? model, MigrationCommandListBuilder builder)
{
if (!string.IsNullOrEmpty(operation.Filter))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ namespace Microsoft.EntityFrameworkCore.Migrations.Operations;
[DebuggerDisplay("CREATE INDEX {Name} ON {Table}")]
public class CreateIndexOperation : MigrationOperation, ITableMigrationOperation
{
/// <summary>
/// Indicates whether or not the index should enforce uniqueness.
/// </summary>
public virtual bool IsUnique { get; set; }

/// <summary>
/// The name of the index.
/// </summary>
Expand All @@ -37,6 +32,17 @@ public class CreateIndexOperation : MigrationOperation, ITableMigrationOperation
/// </summary>
public virtual string[] Columns { get; set; } = null!;

/// <summary>
/// Indicates whether or not the index should enforce uniqueness.
/// </summary>
public virtual bool IsUnique { get; set; }

/// <summary>
/// A set of values indicating whether each corresponding index column has descending sort order.
/// If less sort order values are provided than there are columns, the remaining columns will have ascending order.
/// </summary>
public virtual bool[] IsDescending { get; set; } = Array.Empty<bool>();

/// <summary>
/// An expression to use as the index filter.
/// </summary>
Expand All @@ -53,11 +59,12 @@ public static CreateIndexOperation CreateFrom(ITableIndex index)

var operation = new CreateIndexOperation
{
IsUnique = index.IsUnique,
Name = index.Name,
Schema = index.Table.Schema,
Table = index.Table.Name,
Columns = index.Columns.Select(p => p.Name).ToArray(),
IsUnique = index.IsUnique,
IsDescending = index.IsDescending.ToArray(),
Filter = index.Filter
};
operation.AddAnnotations(index.GetAnnotations());
Expand Down
8 changes: 7 additions & 1 deletion src/EFCore.Relational/Scaffolding/Metadata/DatabaseIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,16 @@ public class DatabaseIndex : Annotatable
public virtual IList<DatabaseColumn> Columns { get; } = new List<DatabaseColumn>();

/// <summary>
/// Indicates whether or not the index constrains uniqueness.
/// Indicates whether or not the index enforces uniqueness.
/// </summary>
public virtual bool IsUnique { get; set; }

/// <summary>
/// A set of values indicating whether each corresponding index column has descending sort order.
/// If less sort order values are provided than there are columns, the remaining columns will have ascending order.
/// </summary>
public virtual IList<bool> IsDescending { get; set; } = new List<bool>();

/// <summary>
/// The filter expression, or <see langword="null" /> if the index has no filter.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -748,10 +748,9 @@ protected override void Generate(

IndexTraits(operation, model, builder);

builder
.Append("(")
.Append(ColumnList(operation.Columns))
.Append(")");
builder.Append("(");
GenerateIndexColumnList(operation, model, builder);
builder.Append(")");
}
else
{
Expand Down
Loading

0 comments on commit d03e2d9

Please sign in to comment.