Skip to content

Conversation

@a-fournier
Copy link
Contributor

@a-fournier a-fournier commented Jul 18, 2024

Closes: #9827

Description of change

This MR fix adds the nullsNotDistinct option for unique constraints and indexes.
This option is available with Postgresql 15 to handle all null values as the same value.

https://www.postgresql.org/docs/current/indexes-unique.html

Example :
A Table(id, value), without a unique null not distinct on the value column
You'll be able to create (1, NULL) and (2, NULL) even if it should be unique

A Table(id, value), with a unique null not distinct on the value column
If you create (1, NULL) and then (2, NULL), you'll get a duplicate key error

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • npm run format to apply prettier formatting
  • npm run test passes with this change
  • This pull request links relevant issues as Fixes #9827
  • There are new or updated unit tests validating the change
  • Documentation has been updated to reflect this change
  • The new commits follow conventions explained in CONTRIBUTING.md

Summary by CodeRabbit

  • New Features

    • Added support for “NULLS NOT DISTINCT” on unique indexes and constraints for PostgreSQL 15+.
    • New options on index/unique decorators and entity schemas let you control nullsNotDistinct.
    • Driver detects server capability and generates the correct SQL; schema sync and metadata inspection preserve this flag.
  • Tests

    • Functional tests added for unique constraints and composite unique indexes, including behavior on unsupported PostgreSQL versions.

@a-fournier a-fournier force-pushed the add-null-not-distinct branch 4 times, most recently from f2ec614 to 12c02e2 Compare July 18, 2024 09:25
@a-fournier a-fournier force-pushed the add-null-not-distinct branch 2 times, most recently from b30d76c to a503e5b Compare July 18, 2024 14:18
@a-fournier a-fournier force-pushed the add-null-not-distinct branch from a503e5b to 6de5e99 Compare July 19, 2024 05:42
@a-fournier
Copy link
Contributor Author

Hi @pleerock, all tests seem to pass but the Oracle CI reaches the timeout.
I retried a few times and it's still the case.
Do you have an idea to fix this, should you increase the delay?

@danLeBrown
Copy link

Oh, dear! It would've been good to have this feature running. Nice work, @a-fournier!

@oscardianno
Copy link

Nice! Any progress on this?

@danLeBrown
Copy link

Nice! Any progress on this?

The new PostgreSQL version helped me with my issue. So I found a way around it

@davidenwang
Copy link

This is an awesome PR, anything we can do to get it merged? :)

@mguida22 mguida22 self-requested a review April 23, 2025 15:53
@mguida22
Copy link
Collaborator

@a-fournier thanks for this contribution! I'll review this soon.

If you can do a few things it will improve this PR:

  • merge master into this branch to get the latest testing setup to run
  • add documentation. This can be done as a separate PR and doesn't need to block
  • add tests for the case where nullsNotDistinct: false
  • move the issue tests to functional tests. Our docs are aren't clear on this right now, but this is new functionality (not testing a regression).

Copy link
Member

@michaelbromley michaelbromley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this great contribution!

I agree with Mike's suggestions above - if you are able to do those then this should be good to merge.

@danLeBrown
Copy link

I'll see if I can get it done during the weekend – that is, if @a-fournier doesn't mind, of course...

(I'm already excited thinking about the possibility of my first non-doc related commit)

@mguida22 mguida22 changed the title feat: support PSQL 15 unique nulls not distinct feat(postgres): support unique nulls not distinct Apr 28, 2025
@coveralls
Copy link

coveralls commented Apr 28, 2025

Coverage Status

coverage: 76.441% (+0.01%) from 76.427%
when pulling dc630d7 on a-fournier:add-null-not-distinct
into d4f7b44 on typeorm:master.

@mguida22
Copy link
Collaborator

@alumni @OSA413 do we have a way to run tests against more than one postgres version? This change requires behavior introduced in postgres 15+, but I would like to have tests that verify postgres versions below 15 continue to function properly.

@OSA413
Copy link
Collaborator

OSA413 commented Apr 28, 2025

Yeah, I think this is possible to run tests on several Postgres versions

@alumni
Copy link
Collaborator

alumni commented Apr 28, 2025

Yeah, we just need to add another workflow in .github/workflows/database-tests.yml using another version of postgis, e.g. 17-3.5.

Would be great to keep docker-compose.yml in sync too, so we can easily start the container locally.

Copy link
Collaborator

@sgarner sgarner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good feature but I think the design is too closely tied to the PostgreSQL implementation. Although NULLS NOT DISTINCT is a PostgreSQL syntax extension, the concept can be applied to other databases.

I know in SQL Server that it works in reverse; by default a unique index treats nulls as equivalent, and you have to create a filtered index to obtain the SQL standard behavior (treating nulls as distinct values). It would be great if we could make it work there as well. See https://stackoverflow.com/a/767702/2168021

Rather than a boolean option, what if it was more like:

nulls?: 'distinct' | 'equivalent'

* throwOnUnsupported is set to false.
*/
isNullsNotDistinctSupported({
throwOnUnsupported = true,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove this parameter and gracefully handle it in each situation (i.e. without throwing).

@Unique("unique_constraint_nulls_not_distinct", ["value"], {
nullsNotDistinct: true,
})
export class ConstraintEntityNullsNotDistinct {
Copy link
Collaborator

@alumni alumni Jul 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would place each entity in its own file

unique: true,
nullsNotDistinct: false,
})
export class IndexEntityNullsDistinct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as before, i would place each entity in its own file.

@PrimaryGeneratedColumn()
id: number

@Column({ type: "text", nullable: true })
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can maybe remove type and let it be inferred from the string type of the value property.

Then, if other drivers would support this feature, they would only need to add the driver to the list of supported drivers in the test file.

describe("indices > nulls not distinct", () => {
let connections: DataSource[] = []
before(async () => {
try {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there shouldn't be any try-catch here :)

closeTestingConnections(connections)
}
})
describe("constraints", () => {
Copy link
Collaborator

@alumni alumni Jul 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One empty line between each describe / it test would make it easier to spot the tests.

const table = await queryRunner.getTable(
"constraint_entity_nulls_distinct",
)
table?.uniques[0].nullsNotDistinct?.should.be.equal(false)
Copy link
Collaborator

@alumni alumni Jul 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be great to use chai.expect, it's closer to natural language:

expect(table?.uniques[0].nullsNotDistinct).to.equal(false)

Copy link
Collaborator

@gioboa gioboa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this PR still valid? may I ask you to address the feedback please?

@pkg-pr-new
Copy link

pkg-pr-new bot commented Sep 22, 2025

typeorm-sql-js-example

npm i https://pkg.pr.new/typeorm/typeorm@10986

commit: dc630d7

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 22, 2025

Walkthrough

Introduces a nullsNotDistinct option across decorators, metadata, schema-builder, and entity schema to support PostgreSQL 15+ UNIQUE NULLS NOT DISTINCT for unique constraints and indexes. Postgres driver/query runner gain capability detection and SQL emission/parsing for the flag. Functional tests validate constraints and indexes behavior.

Changes

Cohort / File(s) Summary
Decorators
src/decorator/Index.ts, src/decorator/Unique.ts
Propagate options.nullsNotDistinct into emitted metadata args for indexes and uniques.
Decorator Options
src/decorator/options/IndexOptions.ts, src/decorator/options/UniqueOptions.ts
Add optional nullsNotDistinct?: boolean with docs (PostgreSQL 15+, index-only when unique: true).
Entity Schema Options
src/entity-schema/EntitySchemaIndexOptions.ts
Add nullsNotDistinct?: boolean to index schema options.
Entity Schema Transformer
src/entity-schema/EntitySchemaTransformer.ts
Map schema index option nullsNotDistinct into IndexMetadataArgs.
Metadata Args
src/metadata-args/IndexMetadataArgs.ts, src/metadata-args/UniqueMetadataArgs.ts
Add nullsNotDistinct?: boolean to args interfaces.
Metadata Classes
src/metadata/IndexMetadata.ts, src/metadata/UniqueMetadata.ts
Add properties (isNullsNotDistinct, nullsNotDistinct) and initialize from args.
Schema Builder Options
src/schema-builder/options/TableColumnOptions.ts, src/schema-builder/options/TableIndexOptions.ts, src/schema-builder/options/TableUniqueOptions.ts
Add isNullsNotDistinct?/nullsNotDistinct? to table-level option interfaces.
Schema Builder Tables
src/schema-builder/table/TableColumn.ts, src/schema-builder/table/TableIndex.ts, src/schema-builder/table/TableUnique.ts
Add new properties; initialize/clone/map through create methods.
Postgres Driver/Runner
src/driver/postgres/PostgresDriver.ts, src/driver/postgres/PostgresQueryRunner.ts
Add capability check isNullsNotDistinctSupported (>= v15). Emit/alter SQL with NULLS NOT DISTINCT when supported; parse/load flags from DB metadata. Consider flag in diffing and DDL generation.
Functional Tests
test/functional/indices/nulls-not-distinct/entity/ConstraintEntity.ts, test/functional/indices/nulls-not-distinct/entity/IndexEntity.ts, test/functional/indices/nulls-not-distinct/nulls-not-distinct.ts
Add entities and tests verifying flag behavior on constraints and indexes; guard behavior on unsupported versions.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Developer
  participant Decorator as Decorators<br/>(@Unique/@Index)
  participant Meta as Metadata/Args
  participant SB as Schema Builder
  participant QR as PostgresQueryRunner
  participant PG as PostgreSQL

  Dev->>Decorator: Define unique/index { nullsNotDistinct }
  Decorator->>Meta: Emit *MetadataArgs* with nullsNotDistinct
  Meta->>SB: Build TableUnique/TableIndex with flag
  SB->>QR: Create/Alter table/index plan
  QR->>QR: Check driver.isNullsNotDistinctSupported()
  alt Supported (PG >= 15)
    QR->>PG: CREATE ... UNIQUE ... NULLS NOT DISTINCT
  else Unsupported
    QR->>QR: Omit clause or throw (based on path)
  end
Loading
sequenceDiagram
  autonumber
  participant QR as PostgresQueryRunner
  participant Driver as PostgresDriver
  participant PG as PostgreSQL

  QR->>Driver: isNullsNotDistinctSupported({throwOnUnsupported?})
  Driver->>PG: Query server version (cached)
  PG-->>Driver: Version
  alt version >= 15
    Driver-->>QR: true
  else version < 15
    alt throwOnUnsupported
      Driver-->>QR: throw TypeORMError
    else
      Driver-->>QR: false
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • mguida22
  • OSA413
  • alumni
  • sgarner

Poem

A nibble of NULLs, a hop of constraint,
I drum tiny paws where indexes paint.
“NOT DISTINCT!” I cheer on v15’s breeze,
Carrots aligned in unique entries, please.
If version’s shy, I twitch and desist—
But when it’s ripe, I persist. 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat(postgres): support unique nulls not distinct" concisely and accurately summarizes the primary change introduced by the PR. It names the affected subsystem (postgres) and the feature (unique NULLS NOT DISTINCT), which matches the code changes to decorators, metadata, driver, and SQL emission. The phrasing is clear, specific, and free of noise, making it suitable for history scanning.
Linked Issues Check ✅ Passed The PR implements the requested nullsNotDistinct option across Unique and Index decorators, metadata args, schema/table builders, Index/Unique metadata, and the Postgres driver/query-runner, and it includes functional tests for both true and false cases; these changes align with linked issue #9827's goal to support PostgreSQL 15+ NULLS NOT DISTINCT behavior. The driver includes a version capability check (>=15) and SQL emission paths append NULLS NOT DISTINCT when supported, matching the issue requirement that the option only applies on PostgreSQL 15+. Documentation is noted as pending in the PR, but the core code, capability gating, and tests meet the linked issue objectives.
Out of Scope Changes Check ✅ Passed I found no changes that appear unrelated to the linked issue; edits are concentrated on index/unique options, metadata, schema objects, the Postgres driver/query-runner, and tests to support NULLS NOT DISTINCT. No other database drivers or unrelated subsystems were modified and the API additions are additive with default false values, preserving backward compatibility. Therefore the changes are in-scope for the feature request.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/schema-builder/table/TableColumn.ts (1)

201-234: clone() drops isNullsNotDistinct; property lost on copy.

This will cause schema-diff surprises. Include it in the cloned options.

         return new TableColumn(<TableColumnOptions>{
             name: this.name,
             type: this.type,
             length: this.length,
             width: this.width,
             charset: this.charset,
             collation: this.collation,
             precision: this.precision,
             scale: this.scale,
             zerofill: this.zerofill,
             unsigned: this.unsigned,
             enum: this.enum,
             enumName: this.enumName,
             primaryKeyConstraintName: this.primaryKeyConstraintName,
             asExpression: this.asExpression,
             generatedType: this.generatedType,
             default: this.default,
             onUpdate: this.onUpdate,
             isNullable: this.isNullable,
             isGenerated: this.isGenerated,
             generationStrategy: this.generationStrategy,
             generatedIdentity: this.generatedIdentity,
             isPrimary: this.isPrimary,
             isUnique: this.isUnique,
+            isNullsNotDistinct: this.isNullsNotDistinct,
             isArray: this.isArray,
             comment: this.comment,
             spatialFeatureType: this.spatialFeatureType,
             srid: this.srid,
         })
src/schema-builder/table/TableUnique.ts (1)

54-60: Preserve nullsNotDistinct in TableUnique.clone().

clone() currently omits nullsNotDistinct, causing copied uniques to lose that flag — add it to the returned options.

File: src/schema-builder/table/TableUnique.ts (clone method)

   clone(): TableUnique {
     return new TableUnique(<TableUniqueOptions>{
       name: this.name,
       columnNames: [...this.columnNames],
       deferrable: this.deferrable,
+      nullsNotDistinct: this.nullsNotDistinct,
     })
   }
src/entity-schema/EntitySchemaTransformer.ts (1)

336-347: Propagate nullsNotDistinct for entity‑schema uniques

  • Add nullsNotDistinct?: boolean to src/entity-schema/EntitySchemaUniqueOptions.ts.
  • In src/entity-schema/EntitySchemaTransformer.ts include nullsNotDistinct when building UniqueMetadataArgs, e.g.:
    nullsNotDistinct: unique.nullsNotDistinct === true ? true : false.
  • Add tests for entity‑schema uniques (and indexes) asserting the flag is honored.
src/driver/postgres/PostgresQueryRunner.ts (1)

1859-1884: Altering NULLS NOT DISTINCT on an existing UNIQUE needs drop+recreate.

When toggling newColumn.isNullsNotDistinct while keeping unique=true, this adds a second constraint with the same generated name, which will fail. Drop the existing unique first, then add the updated one; mirror in downQueries.

-                if (newColumn.isUnique === true) {
-                    const uniqueConstraint = new TableUnique({
+                if (newColumn.isUnique === true) {
+                    // If a single-column UNIQUE already exists, drop it before re-adding with updated NULLS NOT DISTINCT
+                    const existingUnique = clonedTable.uniques.find(
+                        (u) =>
+                            u.columnNames.length === 1 &&
+                            u.columnNames[0] === newColumn.name,
+                    )
+                    if (existingUnique) {
+                        upQueries.push(this.dropUniqueConstraintSql(table, existingUnique))
+                        // Down: restore the prior definition (including its previous NULLS NOT DISTINCT state)
+                        downQueries.push(
+                            new Query(
+                                `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
+                                    existingUnique.name
+                                }" UNIQUE ${
+                                    existingUnique.nullsNotDistinct &&
+                                    this.driver.isNullsNotDistinctSupported()
+                                        ? "NULLS NOT DISTINCT"
+                                        : ""
+                                } ("${newColumn.name}")`,
+                            ),
+                        )
+                        clonedTable.uniques.splice(
+                            clonedTable.uniques.indexOf(existingUnique),
+                            1,
+                        )
+                    }
+
+                    const uniqueConstraint = new TableUnique({
                         name: this.connection.namingStrategy.uniqueConstraintName(
                             table,
                             [newColumn.name],
                         ),
                         columnNames: [newColumn.name],
                     })
                     clonedTable.uniques.push(uniqueConstraint)
                     upQueries.push(
                         new Query(
                             `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
                                 uniqueConstraint.name
                             }" UNIQUE ${
                                 newColumn.isNullsNotDistinct &&
                                 this.driver.isNullsNotDistinctSupported()
                                     ? "NULLS NOT DISTINCT"
                                     : ""
                             } ("${newColumn.name}")`,
                         ),
                     )
                     downQueries.push(
                         new Query(
                             `ALTER TABLE ${this.escapePath(
                                 table,
                             )} DROP CONSTRAINT "${uniqueConstraint.name}"`,
                         ),
                     )
                 } else {
🧹 Nitpick comments (15)
src/decorator/Index.ts (1)

136-137: Set nullsNotDistinct only when the index is unique.

Avoid setting a meaningless flag on non-unique indexes.

-            nullsNotDistinct:
-                options && options.nullsNotDistinct ? true : false,
+            nullsNotDistinct:
+                options && options.unique && options.nullsNotDistinct
+                    ? true
+                    : false,
src/metadata/UniqueMetadata.ts (1)

42-47: Docstring wording is reversed (nulls are NOT distinct).

Change to accurately reflect semantics, e.g., “Treats NULL values as not distinct (PostgreSQL 15+).”

Apply:

-  /**
-   * Indicates if column handles null values as distinct.
-   * Works only in PostgreSQL 15 and above.
-   */
+  /**
+   * Treats NULL values as not distinct (NULLS NOT DISTINCT).
+   * Works only in PostgreSQL 15 and above.
+   */
src/decorator/Unique.ts (1)

103-105: LGTM; minor style nit (optional).

You can simplify boolean normalization: nullsNotDistinct: !!options?.nullsNotDistinct.

src/schema-builder/options/TableUniqueOptions.ts (1)

25-30: Fix docstring semantics (NOT distinct).

Align wording with PostgreSQL feature name.

-  /**
-   * Indicates if column handles null values as distinct.
-   * Works only in PostgreSQL 15 and above.
-   */
+  /**
+   * Treats NULL values as not distinct (NULLS NOT DISTINCT).
+   * Works only in PostgreSQL 15 and above.
+   */
src/entity-schema/EntitySchemaIndexOptions.ts (1)

29-34: Docstring semantics inverted; update to “not distinct.”

Keep terminology consistent across surfaces.

-  /**
-   * Indicates if column handles null values as distinct.
-   * Works only in PostgreSQL 15 and above.
-   */
+  /**
+   * Treats NULL values as not distinct (NULLS NOT DISTINCT).
+   * Works only in PostgreSQL 15 and above.
+   */
src/schema-builder/table/TableUnique.ts (1)

30-35: Docstring semantics inverted; use “not distinct.”

-  /**
-   * Indicates if column handles null values as distinct.
-   * Works only in PostgreSQL 15 and above.
-   */
+  /**
+   * Treats NULL values as not distinct (NULLS NOT DISTINCT).
+   * Works only in PostgreSQL 15 and above.
+   */
src/schema-builder/options/TableIndexOptions.ts (1)

24-29: Clarify docstring to reflect NULLS NOT DISTINCT semantics.

Replace "Indicates if column handles null values as distinct." with "Treats NULL values as not distinct (NULLS NOT DISTINCT)." Update in src/schema-builder/options/TableIndexOptions.ts (lines 24–29) and the matching occurrences in src/schema-builder/table/TableColumn.ts and src/schema-builder/table/TableIndex.ts.

-  /**
-   * Indicates if column handles null values as distinct.
-   * Works only in PostgreSQL 15 and above.
-   */
+  /**
+   * Treats NULL values as not distinct (NULLS NOT DISTINCT).
+   * Works only in PostgreSQL 15 and above.
+   */
src/schema-builder/table/TableIndex.ts (1)

29-34: Fix docstring: NULLs are “not distinct,” not “distinct”.

Wording is inverted. Suggest clarifying the semantics and scope.

-    /**
-     * Indicates if column handles null values as distinct.
-     * Works only in PostgreSQL 15 and above.
-     */
+    /**
+     * Treats NULL values as not distinct (NULLs compare equal for uniqueness).
+     * PostgreSQL 15+ only.
+     */
src/metadata/IndexMetadata.ts (1)

31-36: Fix docstring: clarify “NULLS NOT DISTINCT”.

Align description with PostgreSQL semantics and constrain scope.

-    /**
-     * Indicates if column handles null values as distinct.
-     * Works only in PostgreSQL 15 and above.
-     */
+    /**
+     * Treats NULL values as not distinct (NULLs compare equal for uniqueness).
+     * PostgreSQL 15+ only.
+     */
test/functional/indices/nulls-not-distinct/nulls-not-distinct.ts (4)

56-61: Release QueryRunner in tests.

Avoids open runners across tests.

                     const queryRunner = connection.createQueryRunner()
                     const table = await queryRunner.getTable(
                         "constraint_entity_nulls_not_distinct",
                     )
                     table?.uniques[0].nullsNotDistinct?.should.be.equal(true)
+                    await queryRunner.release()

66-71: Release QueryRunner in tests.

Same leak pattern here.

                     const queryRunner = connection.createQueryRunner()
                     const table = await queryRunner.getTable(
                         "constraint_entity_nulls_distinct",
                     )
                     table?.uniques[0].nullsNotDistinct?.should.be.equal(false)
+                    await queryRunner.release()

87-92: Release QueryRunner in tests.

Same adjustment for index case.

                     const queryRunner = connection.createQueryRunner()
                     const table = await queryRunner.getTable(
                         "index_entity_nulls_not_distinct",
                     )
                     table?.indices[0].isNullsNotDistinct?.should.be.equal(true)
+                    await queryRunner.release()

97-102: Release QueryRunner in tests.

Same adjustment for index false case.

                     const queryRunner = connection.createQueryRunner()
                     const table = await queryRunner.getTable(
                         "index_entity_nulls_distinct",
                     )
                     table?.indices[0].isNullsNotDistinct?.should.be.equal(false)
+                    await queryRunner.release()
src/driver/postgres/PostgresQueryRunner.ts (2)

3257-3259: Tiny SQL literal nit: add space after ELSE.

Cosmetic; improves readability and avoids any parser edge cases.

-            `CASE "ix"."indisunique" WHEN 't' THEN 'TRUE' ELSE'FALSE' END AS "is_unique", ` +
+            `CASE "ix"."indisunique" WHEN 't' THEN 'TRUE' ELSE 'FALSE' END AS "is_unique", ` +

3421-3423: Same tiny nit in tables indices SELECT.

-            `CASE "ix"."indisunique" WHEN 't' THEN 'TRUE' ELSE'FALSE' END AS "is_unique", ` +
+            `CASE "ix"."indisunique" WHEN 't' THEN 'TRUE' ELSE 'FALSE' END AS "is_unique", ` +
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4f7b44 and dc630d7.

📒 Files selected for processing (21)
  • src/decorator/Index.ts (1 hunks)
  • src/decorator/Unique.ts (1 hunks)
  • src/decorator/options/IndexOptions.ts (1 hunks)
  • src/decorator/options/UniqueOptions.ts (1 hunks)
  • src/driver/postgres/PostgresDriver.ts (1 hunks)
  • src/driver/postgres/PostgresQueryRunner.ts (13 hunks)
  • src/entity-schema/EntitySchemaIndexOptions.ts (1 hunks)
  • src/entity-schema/EntitySchemaTransformer.ts (1 hunks)
  • src/metadata-args/IndexMetadataArgs.ts (1 hunks)
  • src/metadata-args/UniqueMetadataArgs.ts (1 hunks)
  • src/metadata/IndexMetadata.ts (2 hunks)
  • src/metadata/UniqueMetadata.ts (2 hunks)
  • src/schema-builder/options/TableColumnOptions.ts (1 hunks)
  • src/schema-builder/options/TableIndexOptions.ts (1 hunks)
  • src/schema-builder/options/TableUniqueOptions.ts (1 hunks)
  • src/schema-builder/table/TableColumn.ts (2 hunks)
  • src/schema-builder/table/TableIndex.ts (4 hunks)
  • src/schema-builder/table/TableUnique.ts (3 hunks)
  • test/functional/indices/nulls-not-distinct/entity/ConstraintEntity.ts (1 hunks)
  • test/functional/indices/nulls-not-distinct/entity/IndexEntity.ts (1 hunks)
  • test/functional/indices/nulls-not-distinct/nulls-not-distinct.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/driver/postgres/PostgresDriver.ts (2)
src/util/VersionUtils.ts (1)
  • VersionUtils (1-23)
src/error/TypeORMError.ts (1)
  • TypeORMError (1-17)
test/functional/indices/nulls-not-distinct/entity/IndexEntity.ts (2)
test/functional/indices/nulls-not-distinct/entity/ConstraintEntity.ts (2)
  • Entity (6-16)
  • Entity (18-28)
src/decorator/columns/Column.ts (1)
  • Column (134-220)
test/functional/indices/nulls-not-distinct/nulls-not-distinct.ts (2)
test/utils/test-utils.ts (2)
  • createTestingConnections (388-482)
  • closeTestingConnections (487-499)
src/util/VersionUtils.ts (1)
  • VersionUtils (1-23)
test/functional/indices/nulls-not-distinct/entity/ConstraintEntity.ts (4)
test/functional/indices/nulls-not-distinct/entity/IndexEntity.ts (2)
  • Entity (6-20)
  • Entity (22-36)
src/decorator/Unique.ts (1)
  • Unique (43-108)
src/decorator/columns/PrimaryGeneratedColumn.ts (1)
  • PrimaryGeneratedColumn (55-119)
src/decorator/columns/Column.ts (1)
  • Column (134-220)
🪛 GitHub Check: formatting
test/functional/indices/nulls-not-distinct/nulls-not-distinct.ts

[failure] 41-41:
Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the void operator

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: tests-linux (20) / mongodb
  • GitHub Check: tests-linux (20) / postgres (14)
  • GitHub Check: tests-linux (20) / sqlite
  • GitHub Check: tests-linux (20) / sqljs
  • GitHub Check: tests-linux (20) / mssql
  • GitHub Check: tests-linux (18) / postgres (17)
  • GitHub Check: tests-linux (20) / better-sqlite3
  • GitHub Check: tests-linux (18) / postgres (14)
  • GitHub Check: tests-linux (18) / sqlite
  • GitHub Check: tests-linux (18) / mysql_mariadb
  • GitHub Check: tests-linux (18) / sqljs
  • GitHub Check: tests-linux (18) / mysql_mariadb_latest
  • GitHub Check: tests-linux (18) / better-sqlite3
  • GitHub Check: tests-linux (18) / oracle
  • GitHub Check: tests-linux (18) / mssql
  • GitHub Check: tests-linux (18) / sap
  • GitHub Check: tests-windows / sqljs
  • GitHub Check: tests-windows / sqlite
  • GitHub Check: tests-windows / better-sqlite3
🔇 Additional comments (18)
src/driver/postgres/PostgresDriver.ts (1)

1442-1461: Make capability check pure; don’t throw here.

Prior feedback suggested removing the parameter and not throwing. Let callers decide whether to error.

-    isNullsNotDistinctSupported({
-        throwOnUnsupported = true,
-    }: { throwOnUnsupported?: boolean } = {}): boolean {
-        if (VersionUtils.isGreaterOrEqual(this.version, "15")) {
-            return true
-        }
-        if (throwOnUnsupported) {
-            throw new TypeORMError(
-                "nullsNotDistinct is only supported in PostgreSQL 15 and above.",
-            )
-        }
-        return false
-    }
+    // Pure capability check: returns true iff PostgreSQL >= 15.
+    isNullsNotDistinctSupported(): boolean {
+        return VersionUtils.isGreaterOrEqual(this.version, "15")
+    }
src/metadata-args/IndexMetadataArgs.ts (1)

25-30: Clarify semantics and scope (only relevant for unique indexes).

Also aligns with a prior nit about wording. Apply across similar comments.

-    /**
-     * Indicates if column handles null values as distinct.
-     * Works only in PostgreSQL 15 and above.
-     */
+    /**
+     * Treat NULL values as not distinct (equal) in uniqueness checks.
+     * Only relevant when `unique: true`. PostgreSQL 15+ only.
+     */
     nullsNotDistinct?: boolean
src/entity-schema/EntitySchemaTransformer.ts (1)

305-307: Index flag propagation LGTM.

nullsNotDistinct flows correctly into IndexMetadataArgs.

test/functional/indices/nulls-not-distinct/entity/IndexEntity.ts (1)

1-36: Split entities into separate files for clarity (repeat).

Same feedback as earlier review—one entity per file keeps fixtures focused.

src/schema-builder/table/TableUnique.ts (1)

69-77: Creation path propagation LGTM.

TableUnique.create correctly forwards uniqueMetadata.nullsNotDistinct.

src/metadata/UniqueMetadata.ts (1)

92-93: Propagation verified — no action required.
UniqueMetadata.nullsNotDistinct is assigned from args and rounds‑trip into TableUnique/TableIndex/TableColumn and DDL generation (PostgresQueryRunner emits/reads "NULLS NOT DISTINCT" and PostgresDriver enforces support).

src/schema-builder/table/TableIndex.ts (1)

81-81: Propagation LGTM.

Constructor/init, clone(), and static create() correctly carry isNullsNotDistinct through.

Also applies to: 102-102, 126-126

src/metadata/IndexMetadata.ts (1)

156-157: Option wiring LGTM; confirm intent for non-unique indexes.

Setting isNullsNotDistinct regardless of unique=true is fine at metadata level, but DDL must ignore/forbid it when not unique.

Would you like us to hard‑guard at SQL emission time so the clause is only appended for unique indexes?

test/functional/indices/nulls-not-distinct/entity/ConstraintEntity.ts (3)

6-16: Entity for nullsNotDistinct=true: OK.

Definition matches the test’s expectations.


18-28: Entity for nullsNotDistinct=false: OK.

Covers the “false” branch explicitly.


6-28: Split entities into separate files (test readability).

Keeps fixtures small and discoverable.

test/functional/indices/nulls-not-distinct/nulls-not-distinct.ts (2)

20-37: Avoid try/catch in before hook (skip unsupported).

Prefer version‑gating/conditional test skip over asserting on thrown errors in hooks.


44-44: Minor test style: spacing and assertions.

Add one empty line between tests; consider chai.expect for readability.

src/driver/postgres/PostgresQueryRunner.ts (5)

1100-1108: Add-column UNIQUE DDL: NULLS NOT DISTINCT handled correctly.

SQL placement after UNIQUE is valid and gated by capability.


3315-3323: View index reflection: flag mapping LGTM.

isNullsNotDistinct mapped from catalog output as expected.


4069-4071: Table index reflection: flag mapping LGTM.

Consistent with views path.


4124-4130: CREATE TABLE uniques: correct emission with gating.

Appending NULLS NOT DISTINCT under capability is correct.


3411-3418: Capability-safe SELECT lists LGTM.

Avoids referencing pg_index.indnullsnotdistinct on <15; nice.

Also applies to: 3247-3254

Comment on lines +10 to +19
/**
* By default, NULL values are not treated as distinct entries.
* Specifying NULLS NOT DISTINCT on unique indexes
* will cause NULL values to be treated distinctly.
*
* Only applicable when unique: true.
* Works only in PostgreSQL 15 and above.
*/
nullsNotDistinct?: boolean

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct description: default vs effect of NULLS NOT DISTINCT.

-    /**
-     * By default, NULL values are not treated as distinct entries.
-     * Specifying NULLS NOT DISTINCT on unique indexes
-     * will cause NULL values to be treated distinctly.
-     *
-     * Only applicable when unique: true.
-     * Works only in PostgreSQL 15 and above.
-     */
+    /**
+     * PostgreSQL by default treats NULLs as distinct in unique checks
+     * (allowing multiple rows with NULLs). When set, emits `NULLS NOT DISTINCT`
+     * so NULLs are considered equal and duplicates are rejected.
+     *
+     * Only applicable when `unique: true`. PostgreSQL 15+ only.
+     */
     nullsNotDistinct?: boolean
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* By default, NULL values are not treated as distinct entries.
* Specifying NULLS NOT DISTINCT on unique indexes
* will cause NULL values to be treated distinctly.
*
* Only applicable when unique: true.
* Works only in PostgreSQL 15 and above.
*/
nullsNotDistinct?: boolean
/**
* PostgreSQL by default treats NULLs as distinct in unique checks
* (allowing multiple rows with NULLs). When set, emits `NULLS NOT DISTINCT`
* so NULLs are considered equal and duplicates are rejected.
*
* Only applicable when `unique: true`. PostgreSQL 15+ only.
*/
nullsNotDistinct?: boolean
🤖 Prompt for AI Agents
In src/decorator/options/IndexOptions.ts around lines 10 to 19, the comment
incorrectly describes the default behavior and the effect of NULLS NOT DISTINCT;
update the doc to state that by default NULL values are treated as distinct
(multiple NULLs allowed for unique indexes), and that specifying
nullsNotDistinct: true adds NULLS NOT DISTINCT so NULLs are treated as not
distinct (i.e., NULLs are considered equal and uniqueness will prevent multiple
NULLs), and keep the note about PostgreSQL 15+ and applicability only when
unique: true.

Comment on lines +11 to +19

/**
* By default, NULL values are not treated as distinct entries.
* Specifying NULLS NOT DISTINCT on unique constraints
* will cause NULL values to be treated distinctly.
*
* Works only in PostgreSQL 15 and above.
*/
nullsNotDistinct?: boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct misleading description of default behavior and effect.

By default PostgreSQL treats NULLs as distinct; enabling this option makes NULLs not distinct. Please fix wording.

-    /**
-     * By default, NULL values are not treated as distinct entries.
-     * Specifying NULLS NOT DISTINCT on unique constraints
-     * will cause NULL values to be treated distinctly.
-     *
-     * Works only in PostgreSQL 15 and above.
-     */
+    /**
+     * By default in PostgreSQL, NULLs are considered distinct in unique checks
+     * (multiple rows with NULLs are allowed). Setting this option emits
+     * `NULLS NOT DISTINCT`, making NULLs equal for uniqueness.
+     * PostgreSQL 15+ only.
+     */
     nullsNotDistinct?: boolean
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* By default, NULL values are not treated as distinct entries.
* Specifying NULLS NOT DISTINCT on unique constraints
* will cause NULL values to be treated distinctly.
*
* Works only in PostgreSQL 15 and above.
*/
nullsNotDistinct?: boolean
/**
* By default in PostgreSQL, NULLs are considered distinct in unique checks
* (multiple rows with NULLs are allowed). Setting this option emits
* `NULLS NOT DISTINCT`, making NULLs equal for uniqueness.
* PostgreSQL 15+ only.
*/
nullsNotDistinct?: boolean
🤖 Prompt for AI Agents
In src/decorator/options/UniqueOptions.ts around lines 11 to 19, the doc comment
incorrectly states the default behavior and effect of the option; update the
comment to say that PostgreSQL by default treats NULL values as distinct and
that setting nullsNotDistinct?: boolean will cause NULLs to be treated as NOT
DISTINCT (i.e., considered equal) for the unique constraint; keep the note that
this only applies to PostgreSQL 15 and above and make the wording concise and
unambiguous.

Comment on lines +4407 to 4413
}(${columns}) ${
index.isNullsNotDistinct &&
this.driver.isNullsNotDistinctSupported()
? "NULLS NOT DISTINCT"
: ""
} ${index.where ? "WHERE " + index.where : ""}`,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard clause: emit NULLS NOT DISTINCT only for UNIQUE indexes.

PostgreSQL accepts the clause only with UNIQUE indexes. Current code would append it even when isUnique=false, producing invalid SQL.

-            }(${columns}) ${
-                index.isNullsNotDistinct &&
-                this.driver.isNullsNotDistinctSupported()
-                    ? "NULLS NOT DISTINCT"
-                    : ""
-            } ${index.where ? "WHERE " + index.where : ""}`,
+            }(${columns}) ${
+                index.isUnique &&
+                index.isNullsNotDistinct &&
+                this.driver.isNullsNotDistinctSupported()
+                    ? "NULLS NOT DISTINCT"
+                    : ""
+            } ${index.where ? "WHERE " + index.where : ""}`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
}(${columns}) ${
index.isNullsNotDistinct &&
this.driver.isNullsNotDistinctSupported()
? "NULLS NOT DISTINCT"
: ""
} ${index.where ? "WHERE " + index.where : ""}`,
)
}(${columns}) ${
- index.isNullsNotDistinct &&
index.isUnique &&
index.isNullsNotDistinct &&
this.driver.isNullsNotDistinctSupported()
? "NULLS NOT DISTINCT"
: ""
} ${index.where ? "WHERE " + index.where : ""}`,
)
🤖 Prompt for AI Agents
In src/driver/postgres/PostgresQueryRunner.ts around lines 4407 to 4413, the
code unconditionally emits "NULLS NOT DISTINCT" when index.isNullsNotDistinct is
true and the driver supports it, but PostgreSQL only allows that clause for
UNIQUE indexes; update the condition so "NULLS NOT DISTINCT" is appended only
when index.isUnique is true AND index.isNullsNotDistinct is true AND
this.driver.isNullsNotDistinctSupported() returns true, leaving the rest of the
index SQL construction unchanged.

Comment on lines +4427 to 4433
index.isNullsNotDistinct &&
this.driver.isNullsNotDistinctSupported()
? "NULLS NOT DISTINCT"
: ""
} ${index.where ? "WHERE " + index.where : ""}`,
)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Same guard for view indexes.

Mirror the uniqueness check when creating indexes on views/materialized views.

-            }" ON ${this.escapePath(view)} (${columns}) ${
-                index.isNullsNotDistinct &&
-                this.driver.isNullsNotDistinctSupported()
-                    ? "NULLS NOT DISTINCT"
-                    : ""
-            } ${index.where ? "WHERE " + index.where : ""}`,
+            }" ON ${this.escapePath(view)} (${columns}) ${
+                index.isUnique &&
+                index.isNullsNotDistinct &&
+                this.driver.isNullsNotDistinctSupported()
+                    ? "NULLS NOT DISTINCT"
+                    : ""
+            } ${index.where ? "WHERE " + index.where : ""}`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
index.isNullsNotDistinct &&
this.driver.isNullsNotDistinctSupported()
? "NULLS NOT DISTINCT"
: ""
} ${index.where ? "WHERE " + index.where : ""}`,
)
}
index.isUnique &&
index.isNullsNotDistinct &&
this.driver.isNullsNotDistinctSupported()
? "NULLS NOT DISTINCT"
: ""
} ${index.where ? "WHERE " + index.where : ""}`,
)
}
🤖 Prompt for AI Agents
In src/driver/postgres/PostgresQueryRunner.ts around lines 4427 to 4433, the
index creation logic for views/materialized views is missing the same uniqueness
guard used elsewhere; update the view index SQL generation to mirror the
uniqueness check (i.e., only emit "UNIQUE" when the index.isUnique flag is true
and when uniqueness is supported for view indexes), applying the same
conditional used for regular table indexes and ensuring the rest of the index
options (NULLS NOT DISTINCT, WHERE clause) are preserved unchanged.

Comment on lines +27 to +31
/**
* Indicates if column handles null values as distinct.
* Works only in PostgreSQL 15 and above.
*/
nullsNotDistinct?: boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix semantics: NULLs become not distinct (equal) when enabled.

-    /**
-     * Indicates if column handles null values as distinct.
-     * Works only in PostgreSQL 15 and above.
-     */
+    /**
+     * Treat NULL values as not distinct (equal) in the unique constraint.
+     * PostgreSQL 15+ only.
+     */
     nullsNotDistinct?: boolean
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Indicates if column handles null values as distinct.
* Works only in PostgreSQL 15 and above.
*/
nullsNotDistinct?: boolean
/**
* Treat NULL values as not distinct (equal) in the unique constraint.
* PostgreSQL 15+ only.
*/
nullsNotDistinct?: boolean
🤖 Prompt for AI Agents
In src/metadata-args/UniqueMetadataArgs.ts around lines 27 to 31, the JSDoc
currently says the flag "Indicates if column handles null values as distinct"
but the intended semantics are the opposite: when enabled NULLs become not
distinct (treated as equal). Update the comment to state that nullsNotDistinct
marks that NULL values are considered equal (not distinct) in uniqueness checks,
and keep the note that this only works in PostgreSQL 15 and above.

Comment on lines +54 to +59
/**
* Indicates if column handles null values as distinct.
* Works only in PostgreSQL 15 and above.
*/
isNullsNotDistinct?: boolean

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix semantics: NULLS NOT DISTINCT treats NULLs as equal, not distinct.

The doc currently says "handles null values as distinct" which is the opposite of the feature. Suggest rewording.

-    /**
-     * Indicates if column handles null values as distinct.
-     * Works only in PostgreSQL 15 and above.
-     */
+    /**
+     * Treat NULL values as not distinct (i.e., equal) for uniqueness checks.
+     * PostgreSQL 15+ only.
+     */
     isNullsNotDistinct?: boolean
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Indicates if column handles null values as distinct.
* Works only in PostgreSQL 15 and above.
*/
isNullsNotDistinct?: boolean
/**
* Treat NULL values as not distinct (i.e., equal) for uniqueness checks.
* PostgreSQL 15+ only.
*/
isNullsNotDistinct?: boolean
🤖 Prompt for AI Agents
In src/schema-builder/options/TableColumnOptions.ts around lines 54 to 59, the
JSDoc for isNullsNotDistinct is incorrect: it currently says "handles null
values as distinct" but NULLS NOT DISTINCT means NULLs are treated as equal.
Update the comment to state that the option makes NULL values compare as equal
(i.e., NOT DISTINCT) and mention it applies to PostgreSQL 15 and above — for
example: "Indicates whether NULL values are treated as equal (NULLS NOT
DISTINCT). Works only in PostgreSQL 15 and above."

Comment on lines +59 to +64
/**
* Indicates if column handles null values as distinct.
* Works only in PostgreSQL 15 and above.
*/
isNullsNotDistinct: boolean = false

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix semantics in JSDoc: NULLS NOT DISTINCT = NULLs are equal.

-    /**
-     * Indicates if column handles null values as distinct.
-     * Works only in PostgreSQL 15 and above.
-     */
+    /**
+     * Treat NULL values as not distinct (equal) for uniqueness checks.
+     * PostgreSQL 15+ only.
+     */
     isNullsNotDistinct: boolean = false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Indicates if column handles null values as distinct.
* Works only in PostgreSQL 15 and above.
*/
isNullsNotDistinct: boolean = false
/**
* Treat NULL values as not distinct (equal) for uniqueness checks.
* PostgreSQL 15+ only.
*/
isNullsNotDistinct: boolean = false
🤖 Prompt for AI Agents
In src/schema-builder/table/TableColumn.ts around lines 59 to 64, the JSDoc
incorrectly describes the semantics of NULLS NOT DISTINCT; update the comment to
state that isNullsNotDistinct indicates the column treats NULL values as equal
(i.e., NULLS NOT DISTINCT), not that it treats them as distinct, and keep the
note about PostgreSQL 15+ support and the default value.

⚠️ Potential issue

🧩 Analysis chain

Confirm column-level flag is actually consumed; otherwise remove.

NULLS NOT DISTINCT is a constraint/index attribute. If column-level isNullsNotDistinct isn’t read during schema diff/introspection, consider dropping it to avoid API surface that has no effect, or wire it through single-column unique creation and diff checks.

Also applies to: 184-184, 201-234


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf "CWD: %s\n\n" "$(pwd)"

echo "---- list src/schema-builder/table ----"
ls -la src/schema-builder/table || true
echo

echo "---- rg: isNullsNotDistinct (case-sensitive) ----"
rg -n --hidden -S "isNullsNotDistinct" -C3 || true
echo

echo "---- rg: .isNullsNotDistinct property access ----"
rg -n --hidden -S "\.isNullsNotDistinct\b" -C3 || true
echo

echo "---- rg: nullsNotDistinct (case-insensitive) ----"
rg -n --hidden -S -i "nullsNotDistinct" -C3 || true
echo

echo "---- rg: literal 'NULLS NOT DISTINCT' ----"
rg -n --hidden -S "NULLS NOT DISTINCT" -C3 || true
echo

echo "---- show src/schema-builder/table/TableColumn.ts (if present) ----"
if [ -f src/schema-builder/table/TableColumn.ts ]; then
  sed -n '1,260p' src/schema-builder/table/TableColumn.ts || true
else
  echo "File not found: src/schema-builder/table/TableColumn.ts"
  rg -n --hidden -S "TableColumn.ts" -C3 || true
fi

Length of output: 74179


Keep column-level isNullsNotDistinct but fix cloning

  • Confirmed: isNullsNotDistinct is actively used (PostgresQueryRunner builds UNIQUE/INDEX SQL and schema-diff checks newColumn.isNullsNotDistinct !== oldColumn.isNullsNotDistinct).
  • Action: TableColumn.clone() omits the flag — add isNullsNotDistinct: this.isNullsNotDistinct to the cloned TableColumnOptions (src/schema-builder/table/TableColumn.ts; constructor sets it at ~line 184; clone() near the file end).
🤖 Prompt for AI Agents
In src/schema-builder/table/TableColumn.ts around the constructor at ~line 184
and the clone() method near the file end (lines ~59-64 reference the property),
the isNullsNotDistinct boolean is declared but not copied when cloning; update
TableColumn.clone() to include isNullsNotDistinct: this.isNullsNotDistinct in
the TableColumnOptions passed to the new TableColumn so cloned instances
preserve the flag used by PostgresQueryRunner and schema-diff checks.

Comment on lines +39 to +43
after(() => {
if (connections && connections.length > 0) {
closeTestingConnections(connections)
}
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Await teardown to avoid dangling connections.

closeTestingConnections returns a Promise; not awaiting can leak resources and flake CI.

-    after(() => {
-        if (connections && connections.length > 0) {
-            closeTestingConnections(connections)
-        }
-    })
+    after(async () => {
+        if (connections && connections.length > 0) {
+            await closeTestingConnections(connections)
+        }
+    })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
after(() => {
if (connections && connections.length > 0) {
closeTestingConnections(connections)
}
})
after(async () => {
if (connections && connections.length > 0) {
await closeTestingConnections(connections)
}
})
🧰 Tools
🪛 GitHub Check: formatting

[failure] 41-41:
Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the void operator

🤖 Prompt for AI Agents
In test/functional/indices/nulls-not-distinct/nulls-not-distinct.ts around lines
39 to 43, the test teardown calls closeTestingConnections(connections) without
awaiting its returned Promise which can leak connections and flake CI; change
the after hook to either be async and await closeTestingConnections(connections)
or return the Promise from the after callback so the test runner waits for
teardown completion.

Copy link
Collaborator

@gioboa gioboa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@a-fournier thanks for opening this PR.
I'm closing this for now because it's stale and with conflicts, feel free to create a new PR with your improvements. 💪

@gioboa gioboa closed this Jan 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support PostgreSQL 15 UNIQUE NULLS NOT DISTINCT