diff --git a/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Tables.cs b/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Tables.cs index b5d6dd2..8f3a143 100644 --- a/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Tables.cs +++ b/src/SchemaSaurus.PostgreSql/PostgreSqlSchemaReader.Tables.cs @@ -213,17 +213,25 @@ private static async Task ReadTableIndexesAsync( am.amname, ARRAY( SELECT attr.attname - FROM unnest(idx.indkey, idx.indoption) WITH ORDINALITY AS key_column(attnum, option, ordinal_position) - LEFT JOIN pg_attribute AS attr ON attr.attrelid = cls.oid AND attr.attnum = key_column.attnum + FROM unnest(idx.indkey) WITH ORDINALITY AS key_column(attnum, ordinal_position) + JOIN pg_attribute AS attr ON attr.attrelid = cls.oid AND attr.attnum = key_column.attnum WHERE key_column.attnum > 0 ORDER BY key_column.ordinal_position ) AS columns, ARRAY( - SELECT (key_column.option & 1) <> 0 - FROM unnest(idx.indkey, idx.indoption) WITH ORDINALITY AS key_column(attnum, option, ordinal_position) + SELECT (COALESCE(idx.indoption[key_column.ordinal_position - 1], 0) & 1) <> 0 + FROM unnest(idx.indkey) WITH ORDINALITY AS key_column(attnum, ordinal_position) + JOIN pg_attribute AS attr ON attr.attrelid = cls.oid AND attr.attnum = key_column.attnum WHERE key_column.attnum > 0 ORDER BY key_column.ordinal_position ) AS descending_columns, + ARRAY( + SELECT key_column.ordinal_position > idx.indnkeyatts + FROM unnest(idx.indkey) WITH ORDINALITY AS key_column(attnum, ordinal_position) + JOIN pg_attribute AS attr ON attr.attrelid = cls.oid AND attr.attnum = key_column.attnum + WHERE key_column.attnum > 0 + ORDER BY key_column.ordinal_position + ) AS included_columns, ARRAY( SELECT pg_get_indexdef(idx.indexrelid, key_column.ordinal_position::integer, false) FROM unnest(idx.indkey) WITH ORDINALITY AS key_column(attnum, ordinal_position) @@ -257,9 +265,10 @@ FROM pg_constraint con const int methodOrdinal = 5; const int columnsOrdinal = 6; const int descendingOrdinal = 7; - const int expressionsOrdinal = 8; - const int predicateOrdinal = 9; - const int optionsOrdinal = 10; + const int includedOrdinal = 8; + const int expressionsOrdinal = 9; + const int predicateOrdinal = 10; + const int optionsOrdinal = 11; while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) { @@ -272,6 +281,7 @@ FROM pg_constraint con var indexType = reader.GetString(methodOrdinal); var columns = reader.GetFieldValue(columnsOrdinal); var descendingColumns = reader.GetFieldValue(descendingOrdinal); + var includedColumns = reader.GetFieldValue(includedOrdinal); var expressions = reader.GetFieldValue(expressionsOrdinal); var predicate = reader.GetStringNull(predicateOrdinal); var storageParameters = reader.GetFieldValueNull(optionsOrdinal); @@ -287,6 +297,12 @@ FROM pg_constraint con for (var i = 0; i < columns.Length; i++) { + if (includedColumns[i]) + { + indexBuilder.AddIncludedColumn(columns[i]); + continue; + } + var sortDirection = descendingColumns[i] ? SortDirection.Descending : SortDirection.Ascending; diff --git a/test/SchemaSaurus.PostgreSql.Tests/IndexTests.cs b/test/SchemaSaurus.PostgreSql.Tests/IndexTests.cs index fc5b661..fb6fd10 100644 --- a/test/SchemaSaurus.PostgreSql.Tests/IndexTests.cs +++ b/test/SchemaSaurus.PostgreSql.Tests/IndexTests.cs @@ -118,6 +118,32 @@ public async Task WhenReadingDescendingIndexColumnThenSortDirectionIsDescending( keyColumn.SortDirection.Should().Be(SortDirection.Descending); } + [Fact] + public async Task WhenReadingCoveringIndexThenKeyColumnsKeepTheirSortOrder() + { + var model = await GetDatabaseModelAsync(); + var taskTable = model.Tables.First(t => t.QualifiedName.Name == "Task"); + + var index = taskTable.Indexes.First(i => i.Name == "IX_Task_Covering"); + var keyColumns = index.Columns.Where(c => !c.IsIncludedColumn).ToList(); + + keyColumns.Select(c => c.ColumnName).Should().Equal("StatusId", "PriorityId"); + keyColumns[0].SortDirection.Should().Be(SortDirection.Ascending); + keyColumns[1].SortDirection.Should().Be(SortDirection.Descending); + } + + [Fact] + public async Task WhenReadingCoveringIndexThenIncludedColumnsAreMarked() + { + var model = await GetDatabaseModelAsync(); + var taskTable = model.Tables.First(t => t.QualifiedName.Name == "Task"); + + var index = taskTable.Indexes.First(i => i.Name == "IX_Task_Covering"); + var includedColumns = index.Columns.Where(c => c.IsIncludedColumn).ToList(); + + includedColumns.Select(c => c.ColumnName).Should().Equal("Title", "Created"); + } + [Fact] public async Task WhenReadingExpressionIndexThenIndexIsReturnedWithExpressionAnnotation() { diff --git a/test/SchemaSaurus.PostgreSql.Tests/Scripts/Script005.MetadataEnhancements.sql b/test/SchemaSaurus.PostgreSql.Tests/Scripts/Script005.MetadataEnhancements.sql index 6a85626..0ce626f 100644 --- a/test/SchemaSaurus.PostgreSql.Tests/Scripts/Script005.MetadataEnhancements.sql +++ b/test/SchemaSaurus.PostgreSql.Tests/Scripts/Script005.MetadataEnhancements.sql @@ -14,3 +14,5 @@ $$; COMMENT ON PROCEDURE public."StatusPaged"(integer, integer) IS 'Reads a page of statuses.'; COMMENT ON FUNCTION public."FormatAddress"(text, text, text, text) IS 'Formats an address.'; COMMENT ON FUNCTION public."NormalizeEmailAddress"(public."EmailAddress") IS 'Normalizes an email address.'; + +CREATE INDEX "IX_Task_Covering" ON public."Task" ("StatusId", "PriorityId" DESC) INCLUDE ("Title", "Created");