diff --git a/Directory.Packages.props b/Directory.Packages.props index c2742e6..cc94025 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,7 +7,7 @@ - + diff --git a/src/NSchema.Postgres/NSchema.Postgres.csproj b/src/NSchema.Postgres/NSchema.Postgres.csproj index b019ac7..4bff55c 100644 --- a/src/NSchema.Postgres/NSchema.Postgres.csproj +++ b/src/NSchema.Postgres/NSchema.Postgres.csproj @@ -20,7 +20,7 @@ true true snupkg - 3.0.0-alpha.12 + 3.0.0-beta.1 $(Version.Split('-')[0]) $(Version.Split('-')[0]) true diff --git a/src/NSchema.Postgres/Sql/PostgresSchemaProvider.cs b/src/NSchema.Postgres/Sql/PostgresSchemaProvider.cs index 3d0b3f4..37625d0 100644 --- a/src/NSchema.Postgres/Sql/PostgresSchemaProvider.cs +++ b/src/NSchema.Postgres/Sql/PostgresSchemaProvider.cs @@ -132,10 +132,10 @@ private static async Task> QueryColumns(NpgsqlConnection conn, s seq.seqincrement AS identity_increment, CASE WHEN c.is_generated = 'ALWAYS' THEN c.generation_expression END AS generation_expression FROM information_schema.columns c + LEFT JOIN pg_namespace n ON n.nspname = c.table_schema LEFT JOIN pg_class t ON t.relname = c.table_name + AND t.relnamespace = n.oid AND t.relkind = 'r' - LEFT JOIN pg_namespace n ON n.oid = t.relnamespace - AND n.nspname = c.table_schema LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attname = c.column_name LEFT JOIN pg_depend d ON d.refobjid = t.oid diff --git a/tests/NSchema.Postgres.Tests/Sql/PostgresSchemaProviderTests.cs b/tests/NSchema.Postgres.Tests/Sql/PostgresSchemaProviderTests.cs index 7d4c5aa..dec3769 100644 --- a/tests/NSchema.Postgres.Tests/Sql/PostgresSchemaProviderTests.cs +++ b/tests/NSchema.Postgres.Tests/Sql/PostgresSchemaProviderTests.cs @@ -1042,4 +1042,45 @@ public async Task GetSchema_Extensions_AreReportedAtRootWithVersion() citext.Version.ShouldNotBeNull(); schema.Extensions.ShouldNotContain(e => e.Name == "plpgsql"); } + + // ── Same table name across schemas ──────────────────────────────────────── + + // Regression: the columns query joined pg_class on relname alone (not namespace), so a table name shared by + // two schemas matched both pg_class rows and fanned every column row out once per schema — columns appeared + // duplicated in each table. Each table must report only its own columns. + [Fact] + public async Task GetSchema_SameTableNameInDifferentSchemas_DoesNotDuplicateColumns() + { + // Arrange + var other = $"test_{Guid.NewGuid():N}"; + await Exec($"CREATE SCHEMA \"{other}\""); + try + { + await Exec($""" + CREATE TABLE "{_schema}".users ( + id INTEGER NOT NULL, + name TEXT NOT NULL + ) + """); + await Exec($""" + CREATE TABLE "{other}".users ( + code INTEGER NOT NULL, + region TEXT NOT NULL + ) + """); + + // Act + var model = await _sut.GetSchema([_schema, other], TestContext.Current.CancellationToken); + + // Assert + var primary = model.Schemas.Single(s => s.Name == _schema).Tables.Single(t => t.Name == "users"); + var secondary = model.Schemas.Single(s => s.Name == other).Tables.Single(t => t.Name == "users"); + primary.Columns.Select(c => c.Name).ShouldBe(["id", "name"]); + secondary.Columns.Select(c => c.Name).ShouldBe(["code", "region"]); + } + finally + { + await Exec($"DROP SCHEMA IF EXISTS \"{other}\" CASCADE"); + } + } }