Skip to content

Commit

Permalink
Split SortOptions into Collation, DescendingOrder and NullsFirst
Browse files Browse the repository at this point in the history
  • Loading branch information
khellang committed Feb 14, 2019
1 parent a1d681f commit 58b7161
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,12 @@ public override MethodCallCodeFragment GenerateFluentApi(IIndex index, IAnnotati
return new MethodCallCodeFragment(nameof(NpgsqlIndexBuilderExtensions.ForNpgsqlHasMethod), annotation.Value);
if (annotation.Name == NpgsqlAnnotationNames.IndexOperators)
return new MethodCallCodeFragment(nameof(NpgsqlIndexBuilderExtensions.ForNpgsqlHasOperators), annotation.Value);
if (annotation.Name == NpgsqlAnnotationNames.IndexSortOptions)
return new MethodCallCodeFragment(nameof(NpgsqlIndexBuilderExtensions.ForNpgsqlHasSortOptions), annotation.Value);
if (annotation.Name == NpgsqlAnnotationNames.IndexCollation)
return new MethodCallCodeFragment(nameof(NpgsqlIndexBuilderExtensions.ForNpgsqlHasCollation), annotation.Value);
if (annotation.Name == NpgsqlAnnotationNames.IndexDescendingOrder)
return new MethodCallCodeFragment(nameof(NpgsqlIndexBuilderExtensions.ForNpgsqlHasDescendingOrder), annotation.Value);
if (annotation.Name == NpgsqlAnnotationNames.IndexNullsFirst)
return new MethodCallCodeFragment(nameof(NpgsqlIndexBuilderExtensions.ForNpgsqlHasNullsFirst), annotation.Value);
if (annotation.Name == NpgsqlAnnotationNames.IndexInclude)
return new MethodCallCodeFragment(nameof(NpgsqlIndexBuilderExtensions.ForNpgsqlInclude), annotation.Value);

Expand Down
54 changes: 47 additions & 7 deletions src/EFCore.PG/Extensions/NpgsqlIndexBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Linq.Expressions;
using JetBrains.Annotations;
Expand Down Expand Up @@ -57,22 +57,62 @@ public static IndexBuilder ForNpgsqlHasOperators(
}

/// <summary>
/// The PostgreSQL index sort options to be used.
/// The PostgreSQL index collation to be used.
/// </summary>
/// <remarks>
/// https://www.postgresql.org/docs/current/static/indexes-collations.html
/// </remarks>
/// <param name="indexBuilder"> The builder for the index being configured. </param>
/// <param name="values"> The sort options to use for each column. </param>
/// <returns> A builder to further configure the index. </returns>
public static IndexBuilder ForNpgsqlHasCollation(
[NotNull] this IndexBuilder indexBuilder,
[CanBeNull, ItemNotNull] params string[] values)
{
Check.NotNull(indexBuilder, nameof(indexBuilder));
Check.NullButNotEmpty(values, nameof(values));

indexBuilder.Metadata.Npgsql().Collation = values;

return indexBuilder;
}

/// <summary>
/// The PostgreSQL index order to be used.
/// </summary>
/// <remarks>
/// https://www.postgresql.org/docs/current/static/indexes-ordering.html
/// </remarks>
/// <param name="indexBuilder"> The builder for the index being configured. </param>
/// <param name="values"> The sort order to use for each column. </param>
/// <returns> A builder to further configure the index. </returns>
public static IndexBuilder ForNpgsqlHasDescendingOrder(
[NotNull] this IndexBuilder indexBuilder,
[CanBeNull, ItemNotNull] params bool?[] values)
{
Check.NotNull(indexBuilder, nameof(indexBuilder));

indexBuilder.Metadata.Npgsql().DescendingOrder = values;

return indexBuilder;
}

/// <summary>
/// The PostgreSQL index NULL ordering to be used.
/// </summary>
/// <remarks>
/// https://www.postgresql.org/docs/current/static/indexes-ordering.html
/// </remarks>
/// <param name="indexBuilder"> The builder for the index being configured. </param>
/// <param name="sortOptions"> The sort options to use for each column. </param>
/// <param name="values"> The sort order to use for each column. </param>
/// <returns> A builder to further configure the index. </returns>
public static IndexBuilder ForNpgsqlHasSortOptions(
public static IndexBuilder ForNpgsqlHasNullsFirst(
[NotNull] this IndexBuilder indexBuilder,
[CanBeNull, ItemNotNull] params string[] sortOptions)
[CanBeNull, ItemNotNull] params bool?[] values)
{
Check.NotNull(indexBuilder, nameof(indexBuilder));
Check.NullButNotEmpty(sortOptions, nameof(sortOptions));

indexBuilder.Metadata.Npgsql().SortOptions = sortOptions;
indexBuilder.Metadata.Npgsql().NullsFirst = values;

return indexBuilder;
}
Expand Down
26 changes: 21 additions & 5 deletions src/EFCore.PG/Metadata/INpgsqlIndexAnnotations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,47 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata
public interface INpgsqlIndexAnnotations : IRelationalIndexAnnotations
{
/// <summary>
/// The PostgreSQL index method to be used. Null selects the default (currently btree).
/// The method to be used, or <c>null</c> if it hasn't been specified.
/// </summary>
/// <remarks>
/// http://www.postgresql.org/docs/current/static/sql-createindex.html
/// </remarks>
string Method { get; }

/// <summary>
/// The PostgreSQL index operators to be used, or <c>null</c> if they have not been specified.
/// The column operators to be used, or <c>null</c> if they have not been specified.
/// </summary>
/// <remarks>
/// https://www.postgresql.org/docs/current/static/indexes-opclass.html
/// </remarks>
IReadOnlyList<string> Operators { get; }

/// <summary>
/// The PostgreSQL index sort options to be used, or <c>null</c> if they have not been specified.
/// The column collations to be used, or <c>null</c> if they have not been specified.
/// </summary>
/// <remarks>
/// https://www.postgresql.org/docs/current/static/indexes-collations.html
/// </remarks>
IReadOnlyList<string> Collation { get; }

/// <summary>
/// The column sort orders to be used, or <c>null</c> if they have not been specified.
/// </summary>
/// <remarks>
/// https://www.postgresql.org/docs/current/static/indexes-ordering.html
/// </remarks>
IReadOnlyList<bool?> DescendingOrder { get; }

/// <summary>
/// The column NULL sort orders to be used, or <c>null</c> if they have not been specified.
/// </summary>
/// <remarks>
/// https://www.postgresql.org/docs/current/static/indexes-ordering.html
/// </remarks>
IReadOnlyList<string> SortOptions { get; }
IReadOnlyList<bool?> NullsFirst { get; }

/// <summary>
/// The PostgreSQL included property names, or <c>null</c> if they have not been specified.
/// The included property names, or <c>null</c> if they have not been specified.
/// </summary>
/// <remarks>
/// https://www.postgresql.org/docs/current/static/sql-createindex.html
Expand Down
4 changes: 3 additions & 1 deletion src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ public static class NpgsqlAnnotationNames
public const string HiLoSequenceSchema = Prefix + "HiLoSequenceSchema";
public const string IndexMethod = Prefix + "IndexMethod";
public const string IndexOperators = Prefix + "IndexOperators";
public const string IndexSortOptions = Prefix + "IndexSortOptions";
public const string IndexCollation = Prefix + "IndexCollation";
public const string IndexDescendingOrder = Prefix + "IndexDescendingOrder";
public const string IndexNullsFirst = Prefix + "IndexNullsFirst";
public const string IndexInclude = Prefix + "IndexInclude";
public const string PostgresExtensionPrefix = Prefix + "PostgresExtension:";
public const string EnumPrefix = Prefix + "Enum:";
Expand Down
54 changes: 44 additions & 10 deletions src/EFCore.PG/Metadata/NpgsqlIndexAnnotations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ protected NpgsqlIndexAnnotations([NotNull] RelationalAnnotations annotations)
}

/// <summary>
/// The PostgreSQL index method to be used. Null selects the default (currently btree).
/// The method to be used, or <c>null</c> if it hasn't been specified.
/// </summary>
/// <remarks>
/// http://www.postgresql.org/docs/current/static/sql-createindex.html
Expand All @@ -30,7 +30,7 @@ public string Method
}

/// <summary>
/// The PostgreSQL index operators to be used, or <c>null</c> if they have not been specified.
/// The column operators to be used, or <c>null</c> if they have not been specified.
/// </summary>
/// <remarks>
/// https://www.postgresql.org/docs/current/static/indexes-opclass.html
Expand All @@ -44,21 +44,49 @@ public string[] Operators
IReadOnlyList<string> INpgsqlIndexAnnotations.Operators => Operators;

/// <summary>
/// The PostgreSQL index sort options to be used, or <c>null</c> if they have not been specified.
/// The column collations to be used, or <c>null</c> if they have not been specified.
/// </summary>
/// <remarks>
/// https://www.postgresql.org/docs/current/static/indexes-collations.html
/// </remarks>
public string[] Collation
{
get => (string[])Annotations.Metadata[NpgsqlAnnotationNames.IndexCollation];
set => SetCollation(value);
}

IReadOnlyList<string> INpgsqlIndexAnnotations.Collation => Collation;

/// <summary>
/// The column sort orders to be used, or <c>null</c> if they have not been specified.
/// </summary>
/// <remarks>
/// https://www.postgresql.org/docs/current/static/indexes-ordering.html
/// </remarks>
public string[] SortOptions
public bool?[] DescendingOrder
{
get => (string[])Annotations.Metadata[NpgsqlAnnotationNames.IndexSortOptions];
set => SetSortOptions(value);
get => (bool?[])Annotations.Metadata[NpgsqlAnnotationNames.IndexDescendingOrder];
set => SetDescendingOrder(value);
}

IReadOnlyList<string> INpgsqlIndexAnnotations.SortOptions => SortOptions;
IReadOnlyList<bool?> INpgsqlIndexAnnotations.DescendingOrder => DescendingOrder;

/// <summary>
/// The PostgreSQL included property names, or <c>null</c> if they have not been specified.
/// The column NULL sort orders to be used, or <c>null</c> if they have not been specified.
/// </summary>
/// <remarks>
/// https://www.postgresql.org/docs/current/static/indexes-ordering.html
/// </remarks>
public bool?[] NullsFirst
{
get => (bool?[])Annotations.Metadata[NpgsqlAnnotationNames.IndexNullsFirst];
set => SetNullsFirst(value);
}

IReadOnlyList<bool?> INpgsqlIndexAnnotations.NullsFirst => NullsFirst;

/// <summary>
/// The included property names, or <c>null</c> if they have not been specified.
/// </summary>
/// <remarks>
/// https://www.postgresql.org/docs/current/static/sql-createindex.html
Expand All @@ -77,8 +105,14 @@ protected virtual bool SetMethod(string value)
protected virtual bool SetOperators(string[] value)
=> Annotations.SetAnnotation(NpgsqlAnnotationNames.IndexOperators, value);

protected virtual bool SetSortOptions(string[] value)
=> Annotations.SetAnnotation(NpgsqlAnnotationNames.IndexSortOptions, value);
protected virtual bool SetCollation(string[] value)
=> Annotations.SetAnnotation(NpgsqlAnnotationNames.IndexCollation, value);

protected virtual bool SetDescendingOrder(bool?[] value)
=> Annotations.SetAnnotation(NpgsqlAnnotationNames.IndexDescendingOrder, value);

protected virtual bool SetNullsFirst(bool?[] value)
=> Annotations.SetAnnotation(NpgsqlAnnotationNames.IndexNullsFirst, value);

protected virtual bool SetIncludeProperties(string[] value)
=> Annotations.SetAnnotation(NpgsqlAnnotationNames.IndexInclude, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,12 @@ public override IEnumerable<IAnnotation> For(IIndex index)
yield return new Annotation(NpgsqlAnnotationNames.IndexMethod, index.Npgsql().Method);
if (index.Npgsql().Operators != null)
yield return new Annotation(NpgsqlAnnotationNames.IndexOperators, index.Npgsql().Operators);
if (index.Npgsql().SortOptions != null)
yield return new Annotation(NpgsqlAnnotationNames.IndexSortOptions, index.Npgsql().SortOptions);
if (index.Npgsql().Collation != null)
yield return new Annotation(NpgsqlAnnotationNames.IndexCollation, index.Npgsql().Collation);
if (index.Npgsql().DescendingOrder != null)
yield return new Annotation(NpgsqlAnnotationNames.IndexDescendingOrder, index.Npgsql().DescendingOrder);
if (index.Npgsql().NullsFirst != null)
yield return new Annotation(NpgsqlAnnotationNames.IndexNullsFirst, index.Npgsql().NullsFirst);
if (index.Npgsql().IncludeProperties != null)
yield return new Annotation(NpgsqlAnnotationNames.IndexInclude, index.Npgsql().IncludeProperties);
}
Expand Down
88 changes: 67 additions & 21 deletions src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -600,12 +600,11 @@ protected override void Generate(
.Append(method);
}

var operators = operation[NpgsqlAnnotationNames.IndexOperators] as string[];
var sortOptions = operation[NpgsqlAnnotationNames.IndexSortOptions] as string[];
var indexColumns = GetIndexColumns(operation);

builder
.Append(" (")
.Append(IndexColumnList(operation.Columns, operators, sortOptions))
.Append(IndexColumnList(indexColumns))
.Append(")");

IndexOptions(operation, model, builder);
Expand Down Expand Up @@ -1202,40 +1201,42 @@ static string GenerateStorageParameterValue(object value)
bool VersionAtLeast(int major, int minor)
=> _postgresVersion is null || new Version(major, minor) <= _postgresVersion;

string IndexColumnList(string[] columns, string[] operators, string[] sortOptions)
string IndexColumnList(IndexColumn[] columns)
{
var isFirst = true;
var builder = new StringBuilder();

for (int i = 0; i < columns.Length; i++)
for (var i = 0; i < columns.Length; i++)
{
if (!isFirst)
builder.Append(", ");

builder.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(columns[i]));
var column = columns[i];

builder.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(column.Name));

if (operators != null && i < operators.Length)
if (!string.IsNullOrEmpty(column.Operator))
{
var @operator = operators[i];
var delimitedOperator = TryParseSchema(column.Operator, out var name, out var schema)
? Dependencies.SqlGenerationHelper.DelimitIdentifier(name, schema)
: Dependencies.SqlGenerationHelper.DelimitIdentifier(column.Operator);

if (!string.IsNullOrEmpty(@operator))
{
var delimitedOperator = TryParseSchema(@operator, out var name, out var schema)
? Dependencies.SqlGenerationHelper.DelimitIdentifier(name, schema)
: Dependencies.SqlGenerationHelper.DelimitIdentifier(@operator);
builder.Append(" ").Append(delimitedOperator);
}

builder.Append(" ").Append(delimitedOperator);
}
if (!string.IsNullOrEmpty(column.Collation))
{
builder.Append(" COLLATE ").Append(column.Collation);
}

if (sortOptions != null && i < sortOptions.Length)
if (column.DescendingOrder.HasValue)
{
var sortOption = sortOptions[i];
builder.Append(" ").Append(column.DescendingOrder.Value ? "DESC" : "ASC");
}

if (!string.IsNullOrEmpty(sortOption))
{
builder.Append(" ").Append(sortOption);
}
if (column.NullsFirst.HasValue)
{
builder.Append(" NULLS ").Append(column.NullsFirst.Value ? "FIRST" : "LAST");
}

isFirst = false;
Expand All @@ -1260,6 +1261,51 @@ static bool TryParseSchema(string identifier, out string name, out string schema
return false;
}

static IndexColumn[] GetIndexColumns(CreateIndexOperation operation)
{
var operators = operation[NpgsqlAnnotationNames.IndexOperators] as string[];
var collations = operation[NpgsqlAnnotationNames.IndexCollation] as string[];
var descendingOrders = operation[NpgsqlAnnotationNames.IndexDescendingOrder] as bool?[];
var nullsFirsts = operation[NpgsqlAnnotationNames.IndexNullsFirst] as bool?[];

var columns = new IndexColumn[operation.Columns.Length];

for (var i = 0; i < columns.Length; i++)
{
var name = operation.Columns[i];
var @operator = i < operators?.Length ? operators[i] : null;
var collation = i < collations?.Length ? collations[i] : null;
var descendingOrder = i < descendingOrders?.Length ? descendingOrders[i] : null;
var nullsFirst = i < nullsFirsts?.Length ? nullsFirsts[i] : null;

columns[i] = new IndexColumn(name, @operator, collation, descendingOrder, nullsFirst);
}

return columns;
}

struct IndexColumn
{
public IndexColumn(string name, string @operator, string collation, bool? descendingOrder, bool? nullsFirst)
{
Name = name;
Operator = @operator;
Collation = collation;
DescendingOrder = descendingOrder;
NullsFirst = nullsFirst;
}

public string Name { get; }

public string Operator { get; }

public string Collation { get; }

public bool? DescendingOrder { get; }

public bool? NullsFirst { get; }
}

#endregion
}
}
Loading

0 comments on commit 58b7161

Please sign in to comment.