Skip to content

feat(invalid-where-values-behavior): make throw the default#11710

Merged
naorpeled merged 21 commits intotypeorm:masterfrom
naorpeled:feat/change-default-behavior-of-invalid-where-values-to-be-throw
Mar 9, 2026
Merged

feat(invalid-where-values-behavior): make throw the default#11710
naorpeled merged 21 commits intotypeorm:masterfrom
naorpeled:feat/change-default-behavior-of-invalid-where-values-to-be-throw

Conversation

@naorpeled
Copy link
Copy Markdown
Member

@naorpeled naorpeled commented Oct 3, 2025

User description

Description of change

Closes #11695

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • This pull request links relevant issues as Fixes #00000
  • There are new or updated unit tests validating the change
  • Documentation has been updated to reflect this change

PR Type

Enhancement


Description

  • Change default behavior for null/undefined in where conditions to throw

  • Update QueryBuilder and SelectQueryBuilder to use throw as default

  • Update documentation to reflect new default behavior

  • Update tests to expect errors instead of ignoring values


Diagram Walkthrough

flowchart LR
  A["Default Behavior:<br/>ignore null/undefined"] -->|Change| B["Default Behavior:<br/>throw on null/undefined"]
  B --> C["QueryBuilder"]
  B --> D["SelectQueryBuilder"]
  B --> E["Documentation"]
  C --> F["Error on null/undefined"]
  D --> F
Loading

File Walkthrough

Relevant files
Documentation
BaseDataSourceOptions.ts
Update documentation for new default behavior                       

src/data-source/BaseDataSourceOptions.ts

  • Updated JSDoc comments to indicate 'throw' is now the default for null
    values
  • Updated JSDoc comments to indicate 'throw' is now the default for
    undefined values
  • Removed '(default)' marker from 'ignore' options
+4/-4     
2-data-source-options.md
Update data source options documentation                                 

docs/docs/data-source/2-data-source-options.md

  • Updated null behavior documentation to show 'throw' as default instead
    of 'ignore'
  • Updated undefined behavior documentation to show 'throw' as default
    instead of 'ignore'
  • Updated example configuration to use 'ignore' for undefined instead of
    'throw'
+5/-5     
5-null-and-undefined-handling.md
Update null/undefined handling documentation                         

docs/docs/data-source/5-null-and-undefined-handling.md

  • Updated introduction to reflect new default behavior of throwing
    errors
  • Removed deprecation note about future behavior changes
  • Updated default behavior section to explain error throwing instead of
    ignoring
  • Updated section headers to indicate 'throw' is now the default
  • Clarified that IsNull operator should be used for matching null values
+10/-17 
Enhancement
QueryBuilder.ts
Change default null/undefined handling to throw                   

src/query-builder/QueryBuilder.ts

  • Changed default null behavior from 'ignore' to 'throw'
  • Changed default undefined behavior from 'ignore' to 'throw'
  • Maintains error throwing logic when defaults are applied
+2/-2     
SelectQueryBuilder.ts
Change default null/undefined handling to throw                   

src/query-builder/SelectQueryBuilder.ts

  • Changed default undefined behavior from 'ignore' to 'throw' in
    setFindOptions
  • Changed default null behavior from 'ignore' to 'throw' in
    setFindOptions
  • Changed default null behavior from 'ignore' to 'throw' in relation
    handling
+3/-3     
Tests
find-options-empty-properties.ts
Configure test to use ignore behavior explicitly                 

test/functional/find-options/empty-properties/find-options-empty-properties.ts

  • Added explicit invalidWhereValuesBehavior configuration to test
    connections
  • Set both null and undefined behaviors to 'ignore' to maintain test
    compatibility
  • Ensures existing tests continue to work with new default behavior
+9/-1     
find-options.ts
Update tests to expect throw behavior by default                 

test/functional/null-undefined-handling/find-options.ts

  • Updated test suite description to clarify default behavior is now
    'throw'
  • Changed test names to reflect error throwing expectations
  • Converted tests from expecting successful queries to expecting
    TypeORMError exceptions
  • Updated assertions to verify error messages contain 'Null value
    encountered' or 'Undefined value encountered'
  • Applied changes to all four test cases: null properties, undefined
    properties, null relations, undefined relations
+109/-81

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 3, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • 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.

@naorpeled naorpeled changed the base branch from master to next October 3, 2025 06:23
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Oct 3, 2025

commit: d3dee94

@alumni
Copy link
Copy Markdown
Collaborator

alumni commented Oct 3, 2025

@naorpeled I wonder why the commit workflow is running also in the TypeORM repo (not just in your repo). Maybe it's a new feature in GitHub? In that case we might not need the full PR workflow.

@naorpeled
Copy link
Copy Markdown
Member Author

@naorpeled I wonder why the commit workflow is running also in the TypeORM repo (not just in your repo). Maybe it's a new feature in GitHub? In that case we might not need the full PR workflow.

Not sure either honestly 🤔

Copy link
Copy Markdown
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.

LGTM 👍

Noting we ought to have a "Migrating to TypeORM v1.0" doc which explains how to deal with this impactful change, for projects which are upgrading.

@naorpeled
Copy link
Copy Markdown
Member Author

/describe

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

PR Description updated to latest commit (3d6db1c)

1 similar comment
@qodo-code-review
Copy link
Copy Markdown

PR Description updated to latest commit (3d6db1c)

@naorpeled naorpeled force-pushed the feat/change-default-behavior-of-invalid-where-values-to-be-throw branch from 3d6db1c to 7937eb1 Compare December 19, 2025 21:44
@naorpeled naorpeled changed the base branch from next to master December 19, 2025 22:04
@naorpeled naorpeled marked this pull request as ready for review December 19, 2025 22:18
Copilot AI review requested due to automatic review settings December 19, 2025 22:18
@naorpeled naorpeled marked this pull request as draft December 19, 2025 22:18
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Breaking Change Impact

This test file now explicitly sets invalidWhereValuesBehavior to 'ignore' for both null and undefined. This suggests existing tests were relying on the old default behavior. Need to verify that all other test files and examples in the codebase have been updated to handle the new default 'throw' behavior, or explicitly configure the old 'ignore' behavior where needed.

(connections = await createTestingConnections({
    __dirname,
    driverSpecific: {
        invalidWhereValuesBehavior: {
            null: "ignore",
            undefined: "ignore",
        },
    },
Test Coverage Gap

The tests verify that errors are thrown for null/undefined values with the new default behavior, but there's no test suite that validates the 'ignore' behavior still works correctly when explicitly configured. Consider adding tests that verify backward compatibility by setting invalidWhereValuesBehavior to 'ignore' and ensuring the old behavior is preserved.

describe("with default behavior (throw)", () => {
    before(async () => {
        connections = await createTestingConnections({
            entities: [Post, Category],
            schemaCreate: true,
            dropSchema: true,
        })
    })
    beforeEach(() => reloadTestingDatabases(connections))
    after(() => closeTestingConnections(connections))

    async function prepareData(connection: DataSource) {
        const category1 = new Category()
        category1.name = "Category #1"
        await connection.manager.save(category1)

        const category2 = new Category()
        category2.name = "Category #2"
        await connection.manager.save(category2)

        const post1 = new Post()
        post1.title = "Post #1"
        post1.text = "About post #1"
        post1.category = category1
        await connection.manager.save(post1)

        const post2 = new Post()
        post2.title = "Post #2"
        post2.text = null
        post2.category = category2
        await connection.manager.save(post2)

        const post3 = new Post()
        post3.title = "Post #3"
        post3.text = "About post #3"
        post3.category = null
        await connection.manager.save(post3)
    }

    it("should throw error for null properties by default", () =>
        Promise.all(
            connections.map(async (connection) => {
                await prepareData(connection)

                // Test with QueryBuilder
                try {
                    await connection
                        .createQueryBuilder(Post, "post")
                        .setFindOptions({
                            // @ts-expect-error - null should be marked as unsafe by default
                            where: {
                                title: "Post #1",
                                text: null,
                            },
                        })
                        .getMany()
                    expect.fail("Expected query to throw an error")
                } catch (error) {
                    expect(error).to.be.instanceOf(TypeORMError)
                    expect(error.message).to.include(
                        "Null value encountered",
                    )
                }

                // Test with Repository find
                try {
                    await connection.getRepository(Post).find({
                        // @ts-expect-error - null should be marked as unsafe by default
                        where: {
                            text: null,
                        },
                    })
                    expect.fail("Expected query to throw an error")
                } catch (error) {
                    expect(error).to.be.instanceOf(TypeORMError)
                    expect(error.message).to.include(
                        "Null value encountered",
                    )
                }
            }),
        ))

    it("should throw error for undefined properties by default", () =>
        Promise.all(
            connections.map(async (connection) => {
                await prepareData(connection)

                // Test with QueryBuilder
                try {
                    await connection
                        .createQueryBuilder(Post, "post")
                        .setFindOptions({
                            where: {
                                title: "Post #1",
                                text: undefined,
                            },
                        })
                        .getMany()
                    expect.fail("Expected query to throw an error")
                } catch (error) {
                    expect(error).to.be.instanceOf(TypeORMError)
                    expect(error.message).to.include(
                        "Undefined value encountered",
                    )
                }

                // Test with Repository
                try {
                    await connection.getRepository(Post).find({
                        where: {
                            text: undefined,
                        },
                    })
                    expect.fail("Expected query to throw an error")
                } catch (error) {
                    expect(error).to.be.instanceOf(TypeORMError)
                    expect(error.message).to.include(
                        "Undefined value encountered",
                    )
                }
            }),
        ))

    it("should throw error for null relation properties by default", () =>
        Promise.all(
            connections.map(async (connection) => {
                await prepareData(connection)

                // Test with QueryBuilder
                try {
                    await connection
                        .createQueryBuilder(Post, "post")
                        .setFindOptions({
                            // @ts-expect-error - null should be marked as unsafe by default
                            where: {
                                category: null,
                            },
                        })
                        .getMany()
                    expect.fail("Expected query to throw an error")
                } catch (error) {
                    expect(error).to.be.instanceOf(TypeORMError)
                    expect(error.message).to.include(
                        "Null value encountered",
                    )
                }

                // Test with Repository
                try {
                    await connection.getRepository(Post).find({
                        // @ts-expect-error - null should be marked as unsafe by default
                        where: {
                            category: null,
                        },
                    })
                    expect.fail("Expected query to throw an error")
                } catch (error) {
                    expect(error).to.be.instanceOf(TypeORMError)
                    expect(error.message).to.include(
                        "Null value encountered",
                    )
                }
            }),
        ))

    it("should throw error for undefined relation properties by default", () =>
        Promise.all(
            connections.map(async (connection) => {
                await prepareData(connection)

                // Test with QueryBuilder
                try {
                    await connection
                        .createQueryBuilder(Post, "post")
                        .setFindOptions({
                            where: {
                                category: undefined,
                            },
                        })
                        .getMany()
                    expect.fail("Expected query to throw an error")
                } catch (error) {
                    expect(error).to.be.instanceOf(TypeORMError)
                    expect(error.message).to.include(
                        "Undefined value encountered",
                    )
                }

                // Test with Repository
                try {
                    await connection.getRepository(Post).find({
                        where: {
                            category: undefined,
                        },
                    })
                    expect.fail("Expected query to throw an error")
                } catch (error) {
                    expect(error).to.be.instanceOf(TypeORMError)
                    expect(error.message).to.include(
                        "Undefined value encountered",
                    )
                }
            }),
        ))
})
Error Message Clarity

The error messages for null and undefined values suggest setting configuration options to handle them, but don't mention that this is a breaking change from previous versions. Users upgrading may be confused why their previously working code now throws errors. Consider enhancing error messages to indicate this is new default behavior.

            `Null value encountered in property '${aliasPath}' of a where condition. ` +
                `To match with SQL NULL, the IsNull() operator must be used. ` +
                `Set 'invalidWhereValuesBehavior.null' to 'ignore' or 'sql-null' in connection options to skip or handle null values.`,
        )
    }
} else if (parameterValue === undefined) {
    const undefinedBehavior =
        this.connection.options.invalidWhereValuesBehavior?.undefined ||
        "throw"
    if (undefinedBehavior === "throw") {
        throw new TypeORMError(
            `Undefined value encountered in property '${aliasPath}' of a where condition. ` +
                `Set 'invalidWhereValuesBehavior.undefined' to 'ignore' in connection options to skip properties with undefined values.`,

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects bot commented Dec 19, 2025

PR Code Suggestions ✨

Latest suggestions up to 2a73db5

CategorySuggestion                                                                                                                                    Impact
Possible issue
Avoid invalid predicates for ignored values
Suggestion Impact:The commit modified the same null/undefined handling area, but instead of adding a no-op predicate for the 'ignore' case, it removed the null/undefined invalidWhereValuesBehavior handling and also removed the helper that skipped ignored values, changing how ignored/invalid where values are processed (and not implementing the proposed 1=1 no-op predicate).

code diff:

@@ -1516,21 +1517,12 @@
                         true,
                     )
 
-                    if (this.shouldSkipWhereValue(parameterValue)) {
-                        continue
-                    }
-
                     yield [aliasPath, parameterValue]
                 }
             }
         } else {
             for (const key of Object.keys(where)) {
                 const parameterValue = where[key]
-
-                if (this.shouldSkipWhereValue(parameterValue)) {
-                    continue
-                }
-
                 const aliasPath = this.expressionMap.aliasNamePrefixingEnabled
                     ? `${this.alias}.${key}`
                     : key
@@ -1538,27 +1530,6 @@
                 yield [aliasPath, parameterValue]
             }
         }
-    }
-
-    /**
-     * Checks if a where value should be skipped based on the
-     * invalidWhereValuesBehavior configuration.
-     * @param value
-     */
-    private shouldSkipWhereValue(value: any): boolean {
-        if (value === undefined) {
-            const behavior =
-                this.connection.options.invalidWhereValuesBehavior?.undefined ||
-                "throw"
-            return behavior === "ignore"
-        }
-        if (value === null) {
-            const behavior =
-                this.connection.options.invalidWhereValuesBehavior?.null ||
-                "throw"
-            return behavior === "ignore"
-        }
-        return false
     }
 
     protected getWherePredicateCondition(
@@ -1637,32 +1608,6 @@
                     parameters: [aliasPath, ...parameters],
                 }
             }
-        } else if (parameterValue === null) {
-            const nullBehavior =
-                this.connection.options.invalidWhereValuesBehavior?.null ||
-                "throw"
-            if (nullBehavior === "sql-null") {
-                return {
-                    operator: "isNull",
-                    parameters: [aliasPath],
-                }
-            } else if (nullBehavior === "throw") {
-                throw new TypeORMError(
-                    `Null value encountered in property '${aliasPath}' of a where condition. ` +
-                        `To match with SQL NULL, the IsNull() operator must be used. ` +
-                        `Set 'invalidWhereValuesBehavior.null' to 'ignore' or 'sql-null' in connection options to skip or handle null values.`,
-                )
-            }
-        } else if (parameterValue === undefined) {
-            const undefinedBehavior =
-                this.connection.options.invalidWhereValuesBehavior?.undefined ||
-                "throw"
-            if (undefinedBehavior === "throw") {
-                throw new TypeORMError(
-                    `Undefined value encountered in property '${aliasPath}' of a where condition. ` +
-                        `Set 'invalidWhereValuesBehavior.undefined' to 'ignore' in connection options to skip properties with undefined values.`,
-                )
-            }
         }

In getWherePredicateCondition, handle the 'ignore' case for null and undefined
values by returning a no-op predicate (e.g., 1=1) instead of letting it fall
through and generate invalid SQL.

src/query-builder/QueryBuilder.ts [1640-1671]

 } else if (parameterValue === null) {
     const nullBehavior =
         this.connection.options.invalidWhereValuesBehavior?.null ||
         "throw"
     if (nullBehavior === "sql-null") {
         return {
             operator: "isNull",
             parameters: [aliasPath],
         }
     } else if (nullBehavior === "throw") {
         throw new TypeORMError(
             `Null value encountered in property '${aliasPath}' of a where condition. ` +
                 `To match with SQL NULL, the IsNull() operator must be used. ` +
                 `Set 'invalidWhereValuesBehavior.null' to 'ignore' or 'sql-null' in connection options to skip or handle null values.`,
         )
+    } else {
+        // 'ignore' => produce a no-op predicate
+        return { operator: "equal", parameters: ["1", "1"] }
     }
 } else if (parameterValue === undefined) {
     const undefinedBehavior =
         this.connection.options.invalidWhereValuesBehavior?.undefined ||
         "throw"
     if (undefinedBehavior === "throw") {
         throw new TypeORMError(
             `Undefined value encountered in property '${aliasPath}' of a where condition. ` +
                 `Set 'invalidWhereValuesBehavior.undefined' to 'ignore' in connection options to skip properties with undefined values.`,
         )
+    } else {
+        // 'ignore' => produce a no-op predicate
+        return { operator: "equal", parameters: ["1", "1"] }
     }
 }
 
 return {
     operator: "equal",
     parameters: [aliasPath, this.createParameter(parameterValue)],
 }

[Suggestion processed]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a bug where null or undefined values, when invalidWhereValuesBehavior is set to 'ignore', would fall through and generate an invalid ... = NULL predicate. The proposed fix of returning a no-op predicate (1=1) is a valid and robust solution to ensure the query remains correct.

Medium
  • Update

Previous suggestions

Suggestions up to commit fb2a41a
CategorySuggestion                                                                                                                                    Impact
Possible issue
Enforce configured null handling

Explicitly handle all three modes for invalidWhereValuesBehavior.null ("ignore",
"throw", and "sql-null") to ensure consistent behavior and prevent bugs from
null values in WHERE conditions.

src/query-builder/SelectQueryBuilder.ts [4526-4535]

 if (where[key] === null) {
     const nullBehavior =
         this.connection.options.invalidWhereValuesBehavior
             ?.null || "throw"
+
+    if (nullBehavior === "ignore") {
+        continue
+    }
+
+    if (nullBehavior === "throw") {
+        throw new TypeORMError(
+            `Null value encountered in property '${alias}.${propertyPath}' of a where condition. ` +
+                "Use `IsNull()` to explicitly check for null values in where conditions.",
+        )
+    }
+
     if (nullBehavior === "sql-null") {
-        andConditions.push(
-            `${alias}.${propertyPath} IS NULL`,
-        )
+        andConditions.push(`${alias}.${propertyPath} IS NULL`)
         continue
     }
 }
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a bug where the new default behavior of throwing an error for null values is not implemented in a specific code path, which undermines the main purpose of the PR.

High
Suggestions up to commit 51504f2
CategorySuggestion                                                                                                                                    Impact
Possible issue
Missing return for ignore behavior

In getWherePredicateCondition, add return undefined for the 'ignore' cases for
null and undefined values to prevent generating invalid SQL.

src/query-builder/QueryBuilder.ts [1584-1610]

 } else if (parameterValue === null) {
     const nullBehavior =
         this.connection.options.invalidWhereValuesBehavior?.null ||
         "throw"
     if (nullBehavior === "sql-null") {
         return {
             operator: "isNull",
             parameters: [aliasPath],
         }
     } else if (nullBehavior === "throw") {
         throw new TypeORMError(
             `Null value encountered in property '${aliasPath}' of a where condition. ` +
                 `To match with SQL NULL, the IsNull() operator must be used. ` +
                 `Set 'invalidWhereValuesBehavior.null' to 'ignore' or 'sql-null' in connection options to skip or handle null values.`,
         )
     }
+    return undefined
 } else if (parameterValue === undefined) {
     const undefinedBehavior =
         this.connection.options.invalidWhereValuesBehavior?.undefined ||
         "throw"
     if (undefinedBehavior === "throw") {
         throw new TypeORMError(
             `Undefined value encountered in property '${aliasPath}' of a where condition. ` +
                 `Set 'invalidWhereValuesBehavior.undefined' to 'ignore' in connection options to skip properties with undefined values.`,
         )
     }
+    return undefined
 }
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical bug where the 'ignore' behavior for null and undefined values is not handled, causing the function to fall through and generate an incorrect query condition. Applying this fix is essential for the correctness of the feature.

High
Suggestions up to commit 23e905e
CategorySuggestion                                                                                                                                    Impact
Possible issue
Missing return for ignore behavior

Add logic to handle the 'ignore' case for null and undefined values in
getWherePredicateCondition. Currently, it falls through and creates an incorrect
'equal' condition instead of skipping the property.

src/query-builder/QueryBuilder.ts [1584-1610]

 } else if (parameterValue === null) {
     const nullBehavior =
         this.connection.options.invalidWhereValuesBehavior?.null ||
         "throw"
     if (nullBehavior === "sql-null") {
         return {
             operator: "isNull",
             parameters: [aliasPath],
         }
     } else if (nullBehavior === "throw") {
         throw new TypeORMError(
             `Null value encountered in property '${aliasPath}' of a where condition. ` +
                 `To match with SQL NULL, the IsNull() operator must be used. ` +
                 `Set 'invalidWhereValuesBehavior.null' to 'ignore' or 'sql-null' in connection options to skip or handle null values.`,
         )
     }
+    return undefined
 } else if (parameterValue === undefined) {
     const undefinedBehavior =
         this.connection.options.invalidWhereValuesBehavior?.undefined ||
         "throw"
     if (undefinedBehavior === "throw") {
         throw new TypeORMError(
             `Undefined value encountered in property '${aliasPath}' of a where condition. ` +
                 `Set 'invalidWhereValuesBehavior.undefined' to 'ignore' in connection options to skip properties with undefined values.`,
         )
     }
+    return undefined
 }
Suggestion importance[1-10]: 10

__

Why: The suggestion correctly identifies a critical bug where the ignore behavior for null and undefined values is not handled, causing the query builder to incorrectly generate an equality check instead of skipping the property.

High
✅ Suggestions up to commit e18a9fd
CategorySuggestion                                                                                                                                    Impact
General
Handle ignore behavior explicitly
Suggestion Impact:The commit adds a dedicated `shouldSkipWhereValue` helper that checks `invalidWhereValuesBehavior` for null/undefined and skips those entries when building predicates, effectively implementing the intended "ignore" handling (though via early skipping rather than explicit `return undefined` in the predicate builder).

code diff:

@@ -1490,12 +1516,21 @@
                         true,
                     )
 
+                    if (this.shouldSkipWhereValue(parameterValue)) {
+                        continue
+                    }
+
                     yield [aliasPath, parameterValue]
                 }
             }
         } else {
             for (const key of Object.keys(where)) {
                 const parameterValue = where[key]
+
+                if (this.shouldSkipWhereValue(parameterValue)) {
+                    continue
+                }
+
                 const aliasPath = this.expressionMap.aliasNamePrefixingEnabled
                     ? `${this.alias}.${key}`
                     : key
@@ -1503,6 +1538,27 @@
                 yield [aliasPath, parameterValue]
             }
         }
+    }
+
+    /**
+     * Checks if a where value should be skipped based on the
+     * invalidWhereValuesBehavior configuration.
+     * @param value
+     */
+    private shouldSkipWhereValue(value: any): boolean {
+        if (value === undefined) {
+            const behavior =
+                this.connection.options.invalidWhereValuesBehavior?.undefined ||
+                "throw"
+            return behavior === "ignore"
+        }
+        if (value === null) {
+            const behavior =
+                this.connection.options.invalidWhereValuesBehavior?.null ||
+                "throw"
+            return behavior === "ignore"
+        }
+        return false
     }

Explicitly return undefined for the 'ignore' behavior for null and undefined
values to prevent fall-through logic that creates an incorrect query condition.

src/query-builder/QueryBuilder.ts [1584-1610]

 } else if (parameterValue === null) {
     const nullBehavior =
         this.connection.options.invalidWhereValuesBehavior?.null ||
         "throw"
     if (nullBehavior === "sql-null") {
         return {
             operator: "isNull",
             parameters: [aliasPath],
         }
     } else if (nullBehavior === "throw") {
         throw new TypeORMError(
             `Null value encountered in property '${aliasPath}' of a where condition. ` +
                 `To match with SQL NULL, the IsNull() operator must be used. ` +
                 `Set 'invalidWhereValuesBehavior.null' to 'ignore' or 'sql-null' in connection options to skip or handle null values.`,
         )
     }
+    return undefined // explicitly return for 'ignore' behavior
 } else if (parameterValue === undefined) {
     const undefinedBehavior =
         this.connection.options.invalidWhereValuesBehavior?.undefined ||
         "throw"
     if (undefinedBehavior === "throw") {
         throw new TypeORMError(
             `Undefined value encountered in property '${aliasPath}' of a where condition. ` +
                 `Set 'invalidWhereValuesBehavior.undefined' to 'ignore' in connection options to skip properties with undefined values.`,
         )
     }
+    return undefined // explicitly return for 'ignore' behavior
 }
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a bug where the 'ignore' case for null and undefined values falls through to create an invalid query condition, and proposes an accurate fix by explicitly returning undefined to skip the predicate.

High
Suggestions up to commit 7123a5c
CategorySuggestion                                                                                                                                    Impact
Possible issue
Add explicit return for ignore behavior

Add explicit return statements to handle the 'ignore' behavior for both null and
undefined values in getWherePredicateCondition to prevent generating incorrect
SQL.

src/query-builder/QueryBuilder.ts [1584-1610]

 } else if (parameterValue === null) {
     const nullBehavior =
         this.connection.options.invalidWhereValuesBehavior?.null ||
         "throw"
     if (nullBehavior === "sql-null") {
         return {
             operator: "isNull",
             parameters: [aliasPath],
         }
     } else if (nullBehavior === "throw") {
         throw new TypeORMError(
             `Null value encountered in property '${aliasPath}' of a where condition. ` +
                 `To match with SQL NULL, the IsNull() operator must be used. ` +
                 `Set 'invalidWhereValuesBehavior.null' to 'ignore' or 'sql-null' in connection options to skip or handle null values.`,
         )
     }
+    return
 } else if (parameterValue === undefined) {
     const undefinedBehavior =
         this.connection.options.invalidWhereValuesBehavior?.undefined ||
         "throw"
     if (undefinedBehavior === "throw") {
         throw new TypeORMError(
             `Undefined value encountered in property '${aliasPath}' of a where condition. ` +
                 `Set 'invalidWhereValuesBehavior.undefined' to 'ignore' in connection options to skip properties with undefined values.`,
         )
     }
+    return
 }
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a bug where undefinedBehavior: 'ignore' leads to incorrect query generation, and also points out that nullBehavior: 'ignore' has the same issue, which is a critical flaw.

High

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR changes the default behavior for handling null and undefined values in WHERE conditions from "ignore" (silently skip) to "throw" (raise an error). This is a breaking change that makes TypeORM more explicit and helps prevent unintended bugs where null/undefined values could lead to unexpected query results.

  • Changed default null/undefined handling from "ignore" to "throw" in query builders
  • Updated all documentation to reflect the new default behavior
  • Modified tests to validate error throwing instead of value ignoring

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/query-builder/QueryBuilder.ts Changed default fallback values from "ignore" to "throw" for both null and undefined handling
src/query-builder/SelectQueryBuilder.ts Changed default fallback values from "ignore" to "throw" in three locations (setFindOptions for undefined, null, and relation null handling)
src/data-source/BaseDataSourceOptions.ts Updated JSDoc comments to mark "throw" as default and removed "(default)" marker from "ignore" options
docs/docs/data-source/2-data-source-options.md Updated documentation to show "throw" as default and adjusted example configuration
docs/docs/data-source/5-null-and-undefined-handling.md Updated entire documentation structure to reflect "throw" as default, clarified error behavior, and updated section headers
test/functional/null-undefined-handling/find-options.test.ts Updated test suite to validate error throwing behavior instead of ignoring behavior, wrapping queries in try-catch blocks and asserting TypeORMError is thrown
test/functional/find-options/empty-properties/find-options-empty-properties.test.ts Added explicit invalidWhereValuesBehavior configuration to maintain "ignore" behavior for this specific test suite

@naorpeled naorpeled marked this pull request as ready for review December 19, 2025 22:39
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Breaking Change Impact

Changing the default behavior from 'ignore' to 'throw' for null/undefined values is a breaking change that will affect existing codebases. Existing queries that previously worked by silently ignoring null/undefined values will now throw errors. Consider the migration path for users and whether this should be a major version bump.

        "throw"
    if (nullBehavior === "sql-null") {
        return {
            operator: "isNull",
            parameters: [aliasPath],
        }
    } else if (nullBehavior === "throw") {
        throw new TypeORMError(
            `Null value encountered in property '${aliasPath}' of a where condition. ` +
                `To match with SQL NULL, the IsNull() operator must be used. ` +
                `Set 'invalidWhereValuesBehavior.null' to 'ignore' or 'sql-null' in connection options to skip or handle null values.`,
        )
    }
} else if (parameterValue === undefined) {
    const undefinedBehavior =
        this.connection.options.invalidWhereValuesBehavior?.undefined ||
        "throw"

Test File Deletion
The test file for empty properties was completely deleted. Verify that the test coverage for edge cases with empty/null/undefined properties is maintained in other test files, or if these scenarios are no longer relevant with the new default behavior.

Test Coverage Gap

The tests now only verify the 'throw' behavior but don't test the 'ignore' and 'sql-null' configuration options. Consider adding tests that verify these alternative behaviors still work correctly when explicitly configured.

    Promise.all(
        connections.map(async (connection) => {
            await prepareData(connection)

            // Test with QueryBuilder
            try {
                await connection
                    .createQueryBuilder(Post, "post")
                    .setFindOptions({
                        // @ts-expect-error - null should be marked as unsafe by default
                        where: {
                            title: "Post #1",
                            text: null,
                        },
                    })
                    .getMany()
                expect.fail("Expected query to throw an error")
            } catch (error) {
                expect(error).to.be.instanceOf(TypeORMError)
                expect(error.message).to.include(
                    "Null value encountered",
                )
            }

            // Test with Repository find
            try {
                await connection.getRepository(Post).find({
                    // @ts-expect-error - null should be marked as unsafe by default
                    where: {
                        text: null,
                    },
                })
                expect.fail("Expected query to throw an error")
            } catch (error) {
                expect(error).to.be.instanceOf(TypeORMError)
                expect(error.message).to.include(
                    "Null value encountered",
                )
            }
        }),
    ))

it("should throw error for undefined properties by default", () =>
    Promise.all(
        connections.map(async (connection) => {
            await prepareData(connection)

            // Test with QueryBuilder
            try {
                await connection
                    .createQueryBuilder(Post, "post")
                    .setFindOptions({
                        where: {
                            title: "Post #1",
                            text: undefined,
                        },
                    })
                    .getMany()
                expect.fail("Expected query to throw an error")
            } catch (error) {
                expect(error).to.be.instanceOf(TypeORMError)
                expect(error.message).to.include(
                    "Undefined value encountered",
                )
            }

            // Test with Repository
            try {
                await connection.getRepository(Post).find({
                    where: {
                        text: undefined,
                    },
                })
                expect.fail("Expected query to throw an error")
            } catch (error) {
                expect(error).to.be.instanceOf(TypeORMError)
                expect(error.message).to.include(
                    "Undefined value encountered",
                )
            }
        }),
    ))

it("should throw error for null relation properties by default", () =>
    Promise.all(
        connections.map(async (connection) => {
            await prepareData(connection)

            // Test with QueryBuilder
            try {
                await connection
                    .createQueryBuilder(Post, "post")
                    .setFindOptions({
                        // @ts-expect-error - null should be marked as unsafe by default
                        where: {
                            category: null,
                        },
                    })
                    .getMany()
                expect.fail("Expected query to throw an error")
            } catch (error) {
                expect(error).to.be.instanceOf(TypeORMError)
                expect(error.message).to.include(
                    "Null value encountered",
                )
            }

            // Test with Repository
            try {
                await connection.getRepository(Post).find({
                    // @ts-expect-error - null should be marked as unsafe by default
                    where: {
                        category: null,
                    },
                })
                expect.fail("Expected query to throw an error")
            } catch (error) {
                expect(error).to.be.instanceOf(TypeORMError)
                expect(error.message).to.include(
                    "Null value encountered",
                )
            }
        }),
    ))

it("should throw error for undefined relation properties by default", () =>
    Promise.all(
        connections.map(async (connection) => {
            await prepareData(connection)

            // Test with QueryBuilder
            try {
                await connection
                    .createQueryBuilder(Post, "post")
                    .setFindOptions({
                        where: {
                            category: undefined,
                        },
                    })
                    .getMany()
                expect.fail("Expected query to throw an error")
            } catch (error) {
                expect(error).to.be.instanceOf(TypeORMError)
                expect(error.message).to.include(
                    "Undefined value encountered",
                )
            }

            // Test with Repository
            try {
                await connection.getRepository(Post).find({
                    where: {
                        category: undefined,
                    },
                })
                expect.fail("Expected query to throw an error")
            } catch (error) {
                expect(error).to.be.instanceOf(TypeORMError)
                expect(error.message).to.include(
                    "Undefined value encountered",
                )
            }
        }),
    ))

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects bot commented Dec 19, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Handle 'ignore' behavior for null/undefined

In getWherePredicateCondition, add logic to handle the 'ignore' case for null
and undefined values by returning undefined to prevent generating an invalid
query condition.

src/query-builder/QueryBuilder.ts [1584-1610]

 } else if (parameterValue === null) {
     const nullBehavior =
         this.connection.options.invalidWhereValuesBehavior?.null ||
         "throw"
     if (nullBehavior === "sql-null") {
         return {
             operator: "isNull",
             parameters: [aliasPath],
         }
     } else if (nullBehavior === "throw") {
         throw new TypeORMError(
             `Null value encountered in property '${aliasPath}' of a where condition. ` +
                 `To match with SQL NULL, the IsNull() operator must be used. ` +
                 `Set 'invalidWhereValuesBehavior.null' to 'ignore' or 'sql-null' in connection options to skip or handle null values.`,
         )
+    } else if (nullBehavior === "ignore") {
+        return undefined
     }
 } else if (parameterValue === undefined) {
     const undefinedBehavior =
         this.connection.options.invalidWhereValuesBehavior?.undefined ||
         "throw"
     if (undefinedBehavior === "throw") {
         throw new TypeORMError(
             `Undefined value encountered in property '${aliasPath}' of a where condition. ` +
                 `Set 'invalidWhereValuesBehavior.undefined' to 'ignore' in connection options to skip properties with undefined values.`,
         )
+    } else if (undefinedBehavior === "ignore") {
+        return undefined
     }
 }
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a bug where the 'ignore' behavior for null and undefined is not handled, leading to incorrect SQL generation. This is a critical fix for the query builder logic.

High
Possible issue
Add missing return statements

Add explicit return undefined statements for the 'ignore' cases in
getWherePredicateCondition to prevent incorrect query generation.

src/query-builder/QueryBuilder.ts [1584-1610]

 } else if (parameterValue === null) {
     const nullBehavior =
         this.connection.options.invalidWhereValuesBehavior?.null ||
         "throw"
     if (nullBehavior === "sql-null") {
         return {
             operator: "isNull",
             parameters: [aliasPath],
         }
     } else if (nullBehavior === "throw") {
         throw new TypeORMError(
             `Null value encountered in property '${aliasPath}' of a where condition. ` +
                 `To match with SQL NULL, the IsNull() operator must be used. ` +
                 `Set 'invalidWhereValuesBehavior.null' to 'ignore' or 'sql-null' in connection options to skip or handle null values.`,
         )
     }
+    return undefined
 } else if (parameterValue === undefined) {
     const undefinedBehavior =
         this.connection.options.invalidWhereValuesBehavior?.undefined ||
         "throw"
     if (undefinedBehavior === "throw") {
         throw new TypeORMError(
             `Undefined value encountered in property '${aliasPath}' of a where condition. ` +
                 `Set 'invalidWhereValuesBehavior.undefined' to 'ignore' in connection options to skip properties with undefined values.`,
         )
     }
+    return undefined
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that if nullBehavior or undefinedBehavior is set to 'ignore', the function implicitly returns undefined, which is then handled incorrectly by the calling code. This leads to a bug where ignored conditions are treated as an equality check against undefined.

Medium
  • More

@naorpeled naorpeled changed the base branch from master to fix/cockroach-db-transaction-not-using-useStructuredResult December 20, 2025 19:58
@naorpeled naorpeled force-pushed the feat/change-default-behavior-of-invalid-where-values-to-be-throw branch 2 times, most recently from f099ecc to 7123a5c Compare December 20, 2025 20:02
@coveralls
Copy link
Copy Markdown

coveralls commented Dec 20, 2025

Coverage Status

coverage: 74.773% (+0.005%) from 74.768%
when pulling d3dee94 on naorpeled:feat/change-default-behavior-of-invalid-where-values-to-be-throw
into 428457a on typeorm:master.

@naorpeled naorpeled changed the base branch from fix/cockroach-db-transaction-not-using-useStructuredResult to master December 20, 2025 21:28
@naorpeled naorpeled force-pushed the feat/change-default-behavior-of-invalid-where-values-to-be-throw branch from 7123a5c to e18a9fd Compare December 20, 2025 21:30
@alumni
Copy link
Copy Markdown
Collaborator

alumni commented Dec 30, 2025

Noting we ought to have a "Migrating to TypeORM v1.0" doc which explains how to deal with this impactful change, for projects which are upgrading.

we do: docs/docs/guides/8-migration-v1.md

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Persistent review updated to latest commit 0913a31

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Persistent review updated to latest commit 601dcdd

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Persistent review updated to latest commit 9ff1f8a

naorpeled and others added 4 commits March 9, 2026 00:53
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Persistent review updated to latest commit 279e426

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Persistent review updated to latest commit 26dc724

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Persistent review updated to latest commit 08ce9d5

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Persistent review updated to latest commit 2b1f20b

@naorpeled naorpeled requested a review from gioboa March 8, 2026 23:01
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Persistent review updated to latest commit 44a5182

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Persistent review updated to latest commit cf8ce9a

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Persistent review updated to latest commit 08b5d0a

@naorpeled naorpeled enabled auto-merge (squash) March 9, 2026 07:44
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Persistent review updated to latest commit 615fe8e

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Mar 9, 2026

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Persistent review updated to latest commit d3dee94

@naorpeled naorpeled disabled auto-merge March 9, 2026 18:46
@naorpeled naorpeled enabled auto-merge (squash) March 9, 2026 19:02
@naorpeled naorpeled merged commit c6745f3 into typeorm:master Mar 9, 2026
38 checks passed
@github-actions github-actions bot added this to the 1.0 milestone Mar 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Use strict null handling by default

6 participants