diff --git a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs index 0b6aa32cb7..1994f76823 100644 --- a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs +++ b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs @@ -427,6 +427,7 @@ static void GetIndexes( cls.relname AS cls_relname, idxcls.relname AS idx_relname, indisunique, + {(connection.PostgreSqlVersion >= new Version(11, 0) ? "indnkeyatts" : "indnatts AS indnkeyatts")}, indkey, amname, indclass, @@ -478,7 +479,10 @@ NOT indisprimary AND IsUnique = record.GetValueOrDefault("indisunique") }; + var numKeyColumns = record.GetValueOrDefault("indnkeyatts"); var columnIndices = record.GetValueOrDefault("indkey"); + var tableColumns = (List)table.Columns; + if (columnIndices.Any(i => i == 0)) { // Expression index, not supported @@ -493,12 +497,11 @@ NOT indisprimary AND */ } - var columns = (List)table.Columns; - foreach (var i in columnIndices) + // Key columns come before non-key (included) columns, process them first + foreach (var i in columnIndices.Take(numKeyColumns)) { - if (columns[i - 1] is DatabaseColumn indexColumn) - index.Columns.Add(indexColumn); - + if (tableColumns[i - 1] is DatabaseColumn indexKeyColumn) + index.Columns.Add(indexKeyColumn); else { logger.UnsupportedColumnIndexSkippedWarning(index.Name, DisplayName(tableSchema, tableName)); @@ -506,6 +509,25 @@ NOT indisprimary AND } } + // Now go over non-key (included columns) if any are present + if (columnIndices.Length > numKeyColumns) + { + var nonKeyColumns = new List(); + foreach (var i in columnIndices.Skip(numKeyColumns)) + { + if (tableColumns[i - 1] is DatabaseColumn indexKeyColumn) + nonKeyColumns.Add(indexKeyColumn.Name); + else + { + logger.UnsupportedColumnIndexSkippedWarning(index.Name, + DisplayName(tableSchema, tableName)); + goto IndexEnd; + } + } + + index[NpgsqlAnnotationNames.IndexInclude] = nonKeyColumns.ToArray(); + } + if (record.GetValueOrDefault("pred") is string predicate) index.Filter = predicate; diff --git a/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs b/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs index 0608925f25..2c9c2e9c3d 100644 --- a/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs @@ -1555,6 +1555,35 @@ public void Index_operators() @"DROP TABLE ""IndexOperators"""); } + [Fact] + public void Index_covering() + { + if (Fixture.TestStore.GetPostgresVersion() < new Version(11, 0)) + return; + + Test( + @" +CREATE TABLE ""IndexCovering"" (a text, b text, c text); +CREATE INDEX ix_with ON ""IndexCovering"" (a) INCLUDE (b, c); +CREATE INDEX ix_without ON ""IndexCovering"" (a, b, c);", + Enumerable.Empty(), + Enumerable.Empty(), + dbModel => + { + var table = dbModel.Tables.Single(); + + var indexWith = table.Indexes.Single(i => i.Name == "ix_with"); + Assert.Equal("a", indexWith.Columns.Single().Name); + Assert.Equal(new[] { "b", "c" }, indexWith.FindAnnotation(NpgsqlAnnotationNames.IndexInclude).Value); + + var indexWithout = table.Indexes.Single(i => i.Name == "ix_without"); + Assert.Equal(new[] { "a", "b", "c" }, indexWithout.Columns.Select(i => i.Name).ToArray()); + Assert.Null(indexWithout.FindAnnotation(NpgsqlAnnotationNames.IndexInclude)); + + }, + @"DROP TABLE ""IndexCovering"""); + } + [Fact] public void Comments() { diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs index 3c28c9b5a8..9b6cf6db74 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs @@ -416,6 +416,26 @@ private static DbCommand CreateCommand( return command; } + public Version GetPostgresVersion() + { + var opened = false; + if (Connection.State == ConnectionState.Closed) + { + Connection.Open(); + opened = true; + } + + try + { + return ((NpgsqlConnection)Connection).PostgreSqlVersion; + } + finally + { + if (opened) + Connection.Close(); + } + } + public override void Dispose() { base.Dispose();