|
| 1 | +# Handling null and undefined values in where conditions |
| 2 | + |
| 3 | +In 'WHERE' conditions the values `null` and `undefined` are not strictly valid values in TypeORM. |
| 4 | + |
| 5 | +Passing a known `null` value is disallowed by TypeScript (when you've enabled `strictNullChecks` in tsconfig.json) at compile time. But the default behavior is for `null` values encountered at runtime to be ignored. Similarly, `undefined` values are allowed by TypeScript and ignored at runtime. |
| 6 | + |
| 7 | +The acceptance of `null` and `undefined` values can sometimes cause unexpected results and requires caution. This is especially a concern when values are passed from user input without adequate validation. |
| 8 | + |
| 9 | +For example, calling `Repository.findOneBy({ id: undefined })` returns the first row from the table, and `Repository.findBy({ userId: null })` is unfiltered and returns all rows. |
| 10 | + |
| 11 | +The way in which `null` and `undefined` values are handled can be customised through the `invalidWhereValuesBehavior` configuration option in your data source options. This applies to all operations that support 'WHERE' conditions, including find operations, query builders, and repository methods. |
| 12 | + |
| 13 | +:::note |
| 14 | +The current behavior will be changing in future versions of TypeORM, |
| 15 | +we recommend setting both `null` and `undefined` behaviors to throw to prepare for these changes |
| 16 | +::: |
| 17 | + |
| 18 | +## Default Behavior |
| 19 | + |
| 20 | +By default, TypeORM skips both `null` and `undefined` values in where conditions. This means that if you include a property with a `null` or `undefined` value in your where clause, it will be ignored: |
| 21 | + |
| 22 | +```typescript |
| 23 | +// Both queries will return all posts, ignoring the text property |
| 24 | +const posts1 = await repository.find({ |
| 25 | + where: { |
| 26 | + text: null, |
| 27 | + }, |
| 28 | +}) |
| 29 | + |
| 30 | +const posts2 = await repository.find({ |
| 31 | + where: { |
| 32 | + text: undefined, |
| 33 | + }, |
| 34 | +}) |
| 35 | +``` |
| 36 | + |
| 37 | +The correct way to match null values in where conditions is to use the `IsNull` operator (for details see [Find Options](../working-with-entity-manager/3-find-options.md)): |
| 38 | + |
| 39 | +```typescript |
| 40 | +const posts = await repository.find({ |
| 41 | + where: { |
| 42 | + text: IsNull(), |
| 43 | + }, |
| 44 | +}) |
| 45 | +``` |
| 46 | + |
| 47 | +## Configuration |
| 48 | + |
| 49 | +You can customize how null and undefined values are handled using the `invalidWhereValuesBehavior` option in your connection configuration: |
| 50 | + |
| 51 | +```typescript |
| 52 | +const dataSource = new DataSource({ |
| 53 | + // ... other options |
| 54 | + invalidWhereValuesBehavior: { |
| 55 | + null: "ignore" | "sql-null" | "throw", |
| 56 | + undefined: "ignore" | "throw", |
| 57 | + }, |
| 58 | +}) |
| 59 | +``` |
| 60 | + |
| 61 | +### Null Behavior Options |
| 62 | + |
| 63 | +The `null` behavior can be set to one of three values: |
| 64 | + |
| 65 | +#### `'ignore'` (default) |
| 66 | + |
| 67 | +JavaScript `null` values in where conditions are ignored and the property is skipped: |
| 68 | + |
| 69 | +```typescript |
| 70 | +const dataSource = new DataSource({ |
| 71 | + // ... other options |
| 72 | + invalidWhereValuesBehavior: { |
| 73 | + null: "ignore", |
| 74 | + }, |
| 75 | +}) |
| 76 | + |
| 77 | +// This will return all posts, ignoring the text property |
| 78 | +const posts = await repository.find({ |
| 79 | + where: { |
| 80 | + text: null, |
| 81 | + }, |
| 82 | +}) |
| 83 | +``` |
| 84 | + |
| 85 | +#### `'sql-null'` |
| 86 | + |
| 87 | +JavaScript `null` values are transformed into SQL `NULL` conditions: |
| 88 | + |
| 89 | +```typescript |
| 90 | +const dataSource = new DataSource({ |
| 91 | + // ... other options |
| 92 | + invalidWhereValuesBehavior: { |
| 93 | + null: "sql-null", |
| 94 | + }, |
| 95 | +}) |
| 96 | + |
| 97 | +// This will only return posts where the text column is NULL in the database |
| 98 | +const posts = await repository.find({ |
| 99 | + where: { |
| 100 | + text: null, |
| 101 | + }, |
| 102 | +}) |
| 103 | +``` |
| 104 | + |
| 105 | +#### `'throw'` |
| 106 | + |
| 107 | +JavaScript `null` values cause a TypeORMError to be thrown: |
| 108 | + |
| 109 | +```typescript |
| 110 | +const dataSource = new DataSource({ |
| 111 | + // ... other options |
| 112 | + invalidWhereValuesBehavior: { |
| 113 | + null: "throw", |
| 114 | + }, |
| 115 | +}) |
| 116 | + |
| 117 | +// This will throw an error |
| 118 | +const posts = await repository.find({ |
| 119 | + where: { |
| 120 | + text: null, |
| 121 | + }, |
| 122 | +}) |
| 123 | +// Error: Null value encountered in property 'text' of a where condition. |
| 124 | +// To match with SQL NULL, the IsNull() operator must be used. |
| 125 | +// Set 'invalidWhereValuesBehavior.null' to 'ignore' or 'sql-null' in connection options to skip or handle null values. |
| 126 | +``` |
| 127 | + |
| 128 | +### Undefined Behavior Options |
| 129 | + |
| 130 | +The `undefined` behavior can be set to one of two values: |
| 131 | + |
| 132 | +#### `'ignore'` (default) |
| 133 | + |
| 134 | +JavaScript `undefined` values in where conditions are ignored and the property is skipped: |
| 135 | + |
| 136 | +```typescript |
| 137 | +const dataSource = new DataSource({ |
| 138 | + // ... other options |
| 139 | + invalidWhereValuesBehavior: { |
| 140 | + undefined: "ignore", |
| 141 | + }, |
| 142 | +}) |
| 143 | + |
| 144 | +// This will return all posts, ignoring the text property |
| 145 | +const posts = await repository.find({ |
| 146 | + where: { |
| 147 | + text: undefined, |
| 148 | + }, |
| 149 | +}) |
| 150 | +``` |
| 151 | + |
| 152 | +#### `'throw'` |
| 153 | + |
| 154 | +JavaScript `undefined` values cause a TypeORMError to be thrown: |
| 155 | + |
| 156 | +```typescript |
| 157 | +const dataSource = new DataSource({ |
| 158 | + // ... other options |
| 159 | + invalidWhereValuesBehavior: { |
| 160 | + undefined: "throw", |
| 161 | + }, |
| 162 | +}) |
| 163 | + |
| 164 | +// This will throw an error |
| 165 | +const posts = await repository.find({ |
| 166 | + where: { |
| 167 | + text: undefined, |
| 168 | + }, |
| 169 | +}) |
| 170 | +// Error: Undefined value encountered in property 'text' of a where condition. |
| 171 | +// Set 'invalidWhereValuesBehavior.undefined' to 'ignore' in connection options to skip properties with undefined values. |
| 172 | +``` |
| 173 | + |
| 174 | +Note that this only applies to explicitly set `undefined` values, not omitted properties. |
| 175 | + |
| 176 | +## Using Both Options Together |
| 177 | + |
| 178 | +You can configure both behaviors independently for comprehensive control: |
| 179 | + |
| 180 | +```typescript |
| 181 | +const dataSource = new DataSource({ |
| 182 | + // ... other options |
| 183 | + invalidWhereValuesBehavior: { |
| 184 | + null: "sql-null", |
| 185 | + undefined: "throw", |
| 186 | + }, |
| 187 | +}) |
| 188 | +``` |
| 189 | + |
| 190 | +This configuration will: |
| 191 | + |
| 192 | +1. Transform JavaScript `null` values to SQL `NULL` in where conditions |
| 193 | +2. Throw an error if any `undefined` values are encountered |
| 194 | +3. Still ignore properties that are not provided in the where clause |
| 195 | + |
| 196 | +This combination is useful when you want to: |
| 197 | + |
| 198 | +- Be explicit about searching for NULL values in the database |
| 199 | +- Catch potential programming errors where undefined values might slip into your queries |
| 200 | + |
| 201 | +## Works with all where operations |
| 202 | + |
| 203 | +The `invalidWhereValuesBehavior` configuration applies to **all TypeORM operations** that support where conditions, not just repository find methods: |
| 204 | + |
| 205 | +### Query Builders |
| 206 | + |
| 207 | +```typescript |
| 208 | +// UpdateQueryBuilder |
| 209 | +await dataSource |
| 210 | + .createQueryBuilder() |
| 211 | + .update(Post) |
| 212 | + .set({ title: "Updated" }) |
| 213 | + .where({ text: null }) // Respects invalidWhereValuesBehavior |
| 214 | + .execute() |
| 215 | + |
| 216 | +// DeleteQueryBuilder |
| 217 | +await dataSource |
| 218 | + .createQueryBuilder() |
| 219 | + .delete() |
| 220 | + .from(Post) |
| 221 | + .where({ text: null }) // Respects invalidWhereValuesBehavior |
| 222 | + .execute() |
| 223 | + |
| 224 | +// SoftDeleteQueryBuilder |
| 225 | +await dataSource |
| 226 | + .createQueryBuilder() |
| 227 | + .softDelete() |
| 228 | + .from(Post) |
| 229 | + .where({ text: null }) // Respects invalidWhereValuesBehavior |
| 230 | + .execute() |
| 231 | +``` |
| 232 | + |
| 233 | +### Repository Methods |
| 234 | + |
| 235 | +```typescript |
| 236 | +// Repository.update() |
| 237 | +await repository.update({ text: null }, { title: "Updated" }) // Respects invalidWhereValuesBehavior |
| 238 | + |
| 239 | +// Repository.delete() |
| 240 | +await repository.delete({ text: null }) // Respects invalidWhereValuesBehavior |
| 241 | + |
| 242 | +// EntityManager.update() |
| 243 | +await manager.update(Post, { text: null }, { title: "Updated" }) // Respects invalidWhereValuesBehavior |
| 244 | + |
| 245 | +// EntityManager.delete() |
| 246 | +await manager.delete(Post, { text: null }) // Respects invalidWhereValuesBehavior |
| 247 | + |
| 248 | +// EntityManager.softDelete() |
| 249 | +await manager.softDelete(Post, { text: null }) // Respects invalidWhereValuesBehavior |
| 250 | +``` |
| 251 | + |
| 252 | +All these operations will consistently apply your configured `invalidWhereValuesBehavior` settings. |
0 commit comments