diff --git a/src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs b/src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs
index 03d7d436c..6efacd7a2 100644
--- a/src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs
+++ b/src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs
@@ -135,6 +135,8 @@ public override MethodCallCodeFragment GenerateFluentApi(IIndex index, IAnnotati
{
if (annotation.Name == NpgsqlAnnotationNames.IndexMethod)
return new MethodCallCodeFragment(nameof(NpgsqlIndexBuilderExtensions.ForNpgsqlHasMethod), annotation.Value);
+ if (annotation.Name == NpgsqlAnnotationNames.IndexOperators)
+ return new MethodCallCodeFragment(nameof(NpgsqlIndexBuilderExtensions.ForNpgsqlHasOperators), annotation.Value);
return null;
}
diff --git a/src/EFCore.PG/Extensions/NpgsqlIndexBuilderExtensions.cs b/src/EFCore.PG/Extensions/NpgsqlIndexBuilderExtensions.cs
index aea94006a..3286fcec3 100644
--- a/src/EFCore.PG/Extensions/NpgsqlIndexBuilderExtensions.cs
+++ b/src/EFCore.PG/Extensions/NpgsqlIndexBuilderExtensions.cs
@@ -28,5 +28,24 @@ public static IndexBuilder ForNpgsqlHasMethod([NotNull] this IndexBuilder indexB
return indexBuilder;
}
+
+ ///
+ /// The PostgreSQL index operators to be used.
+ ///
+ ///
+ /// https://www.postgresql.org/docs/current/static/indexes-opclass.html
+ ///
+ /// The builder for the index being configured.
+ /// The operators to use for each column.
+ /// A builder to further configure the index.
+ public static IndexBuilder ForNpgsqlHasOperators([NotNull] this IndexBuilder indexBuilder, [CanBeNull] params string[] operators)
+ {
+ Check.NotNull(indexBuilder, nameof(indexBuilder));
+ Check.NullButNotEmpty(operators, nameof(operators));
+
+ indexBuilder.Metadata.Npgsql().Operators = operators;
+
+ return indexBuilder;
+ }
}
}
diff --git a/src/EFCore.PG/Metadata/INpgsqlIndexAnnotations.cs b/src/EFCore.PG/Metadata/INpgsqlIndexAnnotations.cs
index d1919798e..2d8011af8 100644
--- a/src/EFCore.PG/Metadata/INpgsqlIndexAnnotations.cs
+++ b/src/EFCore.PG/Metadata/INpgsqlIndexAnnotations.cs
@@ -1,4 +1,5 @@
-using Microsoft.EntityFrameworkCore.Metadata;
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore.Metadata;
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata
{
@@ -11,5 +12,13 @@ public interface INpgsqlIndexAnnotations : IRelationalIndexAnnotations
/// http://www.postgresql.org/docs/current/static/sql-createindex.html
///
string Method { get; }
+
+ ///
+ /// The PostgreSQL index operators to be used.
+ ///
+ ///
+ /// https://www.postgresql.org/docs/current/static/indexes-opclass.html
+ ///
+ IReadOnlyList Operators { get; }
}
}
diff --git a/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationNames.cs b/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationNames.cs
index b360d0f02..ef2ad788f 100644
--- a/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationNames.cs
+++ b/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationNames.cs
@@ -10,6 +10,7 @@ public static class NpgsqlAnnotationNames
public const string HiLoSequenceName = Prefix + "HiLoSequenceName";
public const string HiLoSequenceSchema = Prefix + "HiLoSequenceSchema";
public const string IndexMethod = Prefix + "IndexMethod";
+ public const string IndexOperators = Prefix + "IndexOperators";
public const string PostgresExtensionPrefix = Prefix + "PostgresExtension:";
public const string EnumPrefix = Prefix + "Enum:";
public const string RangePrefix = Prefix + "Range:";
diff --git a/src/EFCore.PG/Metadata/NpgsqlIndexAnnotations.cs b/src/EFCore.PG/Metadata/NpgsqlIndexAnnotations.cs
index cab3e7973..30a52c53e 100644
--- a/src/EFCore.PG/Metadata/NpgsqlIndexAnnotations.cs
+++ b/src/EFCore.PG/Metadata/NpgsqlIndexAnnotations.cs
@@ -1,4 +1,5 @@
-using JetBrains.Annotations;
+using System.Collections.Generic;
+using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Internal;
@@ -28,8 +29,24 @@ public string Method
set => SetMethod(value);
}
+ ///
+ /// The PostgreSQL index operators to be used.
+ ///
+ ///
+ /// https://www.postgresql.org/docs/current/static/indexes-opclass.html
+ ///
+ public string[] Operators
+ {
+ get => (string[]) Annotations.Metadata[NpgsqlAnnotationNames.IndexOperators];
+ set => SetOperators(value);
+ }
+
+ IReadOnlyList INpgsqlIndexAnnotations.Operators => Operators;
+
protected virtual bool SetMethod(string value)
=> Annotations.SetAnnotation(NpgsqlAnnotationNames.IndexMethod, value);
+ protected virtual bool SetOperators(string[] value)
+ => Annotations.SetAnnotation(NpgsqlAnnotationNames.IndexOperators, value);
}
}
diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlMigrationsAnnotationProvider.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlMigrationsAnnotationProvider.cs
index 082a488af..7727f5d50 100644
--- a/src/EFCore.PG/Migrations/Internal/NpgsqlMigrationsAnnotationProvider.cs
+++ b/src/EFCore.PG/Migrations/Internal/NpgsqlMigrationsAnnotationProvider.cs
@@ -47,6 +47,8 @@ public override IEnumerable For(IIndex index)
{
if (index.Npgsql().Method != null)
yield return new Annotation(NpgsqlAnnotationNames.IndexMethod, index.Npgsql().Method);
+ if (index.Npgsql().Operators != null)
+ yield return new Annotation(NpgsqlAnnotationNames.IndexOperators, index.Npgsql().Operators);
}
public override IEnumerable For(IModel model)
diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs
index 1f1bd68ea..60ece9273 100644
--- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs
+++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs
@@ -536,6 +536,7 @@ protected override void Generate(
Check.NotNull(builder, nameof(builder));
var method = (string)operation[NpgsqlAnnotationNames.IndexMethod];
+ var operators = (string[])operation[NpgsqlAnnotationNames.IndexOperators];
builder.Append("CREATE ");
@@ -559,7 +560,7 @@ protected override void Generate(
builder
.Append(" (")
- .Append(ColumnList(operation.Columns))
+ .Append(IndexColumnList(operation.Columns, operators))
.Append(")");
if (!string.IsNullOrEmpty(operation.Filter))
@@ -1133,6 +1134,47 @@ static string GetSchemaOrDefault([CanBeNull] string schema, [CanBeNull] IAnnotat
bool VersionAtLeast(int major, int minor)
=> _postgresVersion is null || new Version(major, minor) <= _postgresVersion;
+ string IndexColumnList(string[] columns, string[] operators)
+ {
+ if (operators == null || operators.Length == 0)
+ return ColumnList(columns);
+
+ return string.Join(", ", columns.Select((v, i) =>
+ {
+ var identifier = Dependencies.SqlGenerationHelper.DelimitIdentifier(v);
+
+ if (i >= operators.Length)
+ return identifier;
+
+ var @operator = operators[i];
+
+ if (string.IsNullOrEmpty(@operator))
+ return identifier;
+
+ var delimitedOperator = TryParseSchema(@operator, out var name, out var schema)
+ ? Dependencies.SqlGenerationHelper.DelimitIdentifier(name, schema)
+ : Dependencies.SqlGenerationHelper.DelimitIdentifier(@operator);
+
+ return string.Concat(identifier, " ", delimitedOperator);
+ }));
+ }
+
+ static bool TryParseSchema(string identifier, out string name, out string schema)
+ {
+ var index = identifier.IndexOf('.');
+
+ if (index >= 0)
+ {
+ schema = identifier.Substring(0, index);
+ name = identifier.Substring(index + 1);
+ return true;
+ }
+
+ schema = default;
+ name = default;
+ return false;
+ }
+
#endregion
}
}
diff --git a/src/EFCore.PG/Utilities/Check.cs b/src/EFCore.PG/Utilities/Check.cs
index 79f59f7ef..948bf20b4 100644
--- a/src/EFCore.PG/Utilities/Check.cs
+++ b/src/EFCore.PG/Utilities/Check.cs
@@ -92,6 +92,19 @@ public static string NullButNotEmpty([CanBeNull] string value, [InvokerParameter
return value;
}
+ public static IReadOnlyCollection NullButNotEmpty([CanBeNull] IReadOnlyCollection value, [InvokerParameterName] [NotNull] string parameterName)
+ {
+ if (!ReferenceEquals(value, null)
+ && (value.Count == 0))
+ {
+ NotEmpty(parameterName, nameof(parameterName));
+
+ throw new ArgumentException(AbstractionsStrings.ArgumentIsEmpty(parameterName));
+ }
+
+ return value;
+ }
+
public static IReadOnlyList HasNoNulls(IReadOnlyList value, [InvokerParameterName] [NotNull] string parameterName)
where T : class
{
diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlMigrationSqlGeneratorTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlMigrationSqlGeneratorTest.cs
index b6b35333a..0126f053e 100644
--- a/test/EFCore.PG.FunctionalTests/NpgsqlMigrationSqlGeneratorTest.cs
+++ b/test/EFCore.PG.FunctionalTests/NpgsqlMigrationSqlGeneratorTest.cs
@@ -616,6 +616,40 @@ public void CreateIndexOperation_method()
Sql);
}
+ [Fact]
+ public void CreateIndexOperation_operations()
+ {
+ Generate(new CreateIndexOperation
+ {
+ Name = "IX_People_Name",
+ Table = "People",
+ Schema = "dbo",
+ Columns = new[] { "FirstName", "LastName" },
+ [NpgsqlAnnotationNames.IndexOperators] = new[] { "text_pattern_ops" }
+ });
+
+ Assert.Equal(
+ "CREATE INDEX \"IX_People_Name\" ON dbo.\"People\" (\"FirstName\" text_pattern_ops, \"LastName\");" + EOL,
+ Sql);
+ }
+
+ [Fact]
+ public void CreateIndexOperation_schema_qualified_operations()
+ {
+ Generate(new CreateIndexOperation
+ {
+ Name = "IX_People_Name",
+ Table = "People",
+ Schema = "dbo",
+ Columns = new[] { "FirstName" },
+ [NpgsqlAnnotationNames.IndexOperators] = new[] { "myschema.TextOperation" }
+ });
+
+ Assert.Equal(
+ "CREATE INDEX \"IX_People_Name\" ON dbo.\"People\" (\"FirstName\" myschema.\"TextOperation\");" + EOL,
+ Sql);
+ }
+
[Fact]
public void RenameIndexOperation()
{