Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b87a343
feat: add `NULLIF()` implementation
bakasmarius Jan 13, 2026
28b376a
Merge branch 'master' into feature/1429-add-nullif-implementation
bakasmarius Jan 13, 2026
1b29008
fix: update import
bakasmarius Jan 13, 2026
3c25fee
fix: add missing import
bakasmarius Jan 15, 2026
0bad547
Merge branch 'master' into feature/1429-add-nullif-implementation
bakasmarius Jan 17, 2026
b72c1b0
Merge branch 'master' into feature/1429-add-nullif-implementation
bakasmarius Jan 17, 2026
9ec8757
Merge branch 'master' into feature/1429-add-nullif-implementation
bakasmarius Jan 18, 2026
630205c
Merge branch 'master' into feature/1429-add-nullif-implementation
bakasmarius Jan 18, 2026
4359bcc
Merge branch 'master' into feature/1429-add-nullif-implementation
bakasmarius Jan 18, 2026
c1807d7
Merge branch 'master' into feature/1429-add-nullif-implementation
bakasmarius Jan 26, 2026
bf09ae4
Merge branch 'master' into feature/1429-add-nullif-implementation
bakasmarius Jan 31, 2026
64f29d7
chore: remove operation node
bakasmarius Jan 31, 2026
c9ed3bf
refactor: move `nullif` to `FunctionModule` and update types
bakasmarius Jan 31, 2026
2b178ab
test: simplify nullif tests
bakasmarius Jan 31, 2026
979674d
Merge branch 'master' into feature/1429-add-nullif-implementation
bakasmarius Jan 31, 2026
d22320b
Merge branch 'master' into feature/1429-add-nullif-implementation
bakasmarius Feb 8, 2026
f61c706
Merge branch 'master' into feature/1429-add-nullif-implementation
bakasmarius Mar 26, 2026
019f28b
chore: change `var` to `const`
bakasmarius Apr 14, 2026
53a7d27
Merge branch 'master' into feature/1429-add-nullif-implementation
bakasmarius Apr 14, 2026
b0607e4
Discard changes to src/operation-node/operation-node-transformer.ts
igalklebanov Apr 17, 2026
bb89630
Discard changes to src/operation-node/operator-node.ts
igalklebanov Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/operation-node/operation-node-visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,4 +324,5 @@ export abstract class OperationNodeVisitor {
protected abstract visitOutput(node: OutputNode): void
protected abstract visitOrAction(node: OrActionNode): void
protected abstract visitCollate(node: CollateNode): void
protected abstract visitNullIf(node: FunctionNode): void
}
51 changes: 51 additions & 0 deletions src/query-builder/function-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
type StringReference,
parseReferenceExpressionOrList,
type ExtractTypeFromStringReference,
parseReferenceExpression,
} from '../parser/reference-parser.js'
import { parseSelectAll } from '../parser/select-parser.js'
import type { KyselyTypeError } from '../util/type-error.js'
Expand All @@ -29,6 +30,11 @@ import type { SelectQueryBuilderExpression } from '../query-builder/select-query
import { isString } from '../util/object-utils.js'
import { parseTable } from '../parser/table-parser.js'
import type { Selectable, SelectType } from '../util/column-type.js'
import {
isSafeImmediateValue,
parseSafeImmediateValue,
parseValueExpression,
} from '../parser/value-parser.js'

/**
* Helpers for type safe SQL function calls.
Expand Down Expand Up @@ -769,6 +775,31 @@ export interface FunctionModule<DB, TB extends keyof DB> {
? Simplify<ShallowDehydrateObject<O>>
: never
>

/**
* Creates a `nullif` expression.
*
* ### Examples
*
* ```ts
* const result = await db.selectFrom('person')
* .select(({fn}) => [
* 'first_name',
* fn.nullif(sql.lit(1), 1).as('null_if_result'),
* ])
* .execute()
* ```
*
* The generated SQL (PostgreSQL):
*
* ```sql
* select first_name, nullif(1, 1) as null_if_result from "person"
* ```
*/
nullif<T extends ReferenceExpression<DB, TB>>(
expr1: T,
expr2: number | boolean | string | null,
): ExpressionWrapper<DB, TB, T | null>
}

export function createFunctionModule<DB, TB extends keyof DB>(): FunctionModule<
Expand Down Expand Up @@ -860,5 +891,25 @@ export function createFunctionModule<DB, TB extends keyof DB>(): FunctionModule<
]),
)
},

nullif<T extends ReferenceExpression<DB, TB>>(
expr1: T,
expr2: number | boolean | string | null,
): ExpressionWrapper<DB, TB, T | null> {
const v1 =
typeof expr1 === 'string'
? parseValueExpression(expr1)
: isSafeImmediateValue(expr1)
? parseSafeImmediateValue(expr1)
: parseReferenceExpression(expr1)
const v2 =
typeof expr2 === 'string'
? parseValueExpression(expr2)
: isSafeImmediateValue(expr2)
? parseSafeImmediateValue(expr2)
: parseReferenceExpression(expr2)

return new ExpressionWrapper(FunctionNode.create('nullif', [v1, v2]))
},
})
}
8 changes: 6 additions & 2 deletions src/query-compiler/default-query-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import type { OrderByItemNode } from '../operation-node/order-by-item-node.js'
import type { OrderByNode } from '../operation-node/order-by-node.js'
import { ParensNode } from '../operation-node/parens-node.js'
import type { PrimitiveValueListNode } from '../operation-node/primitive-value-list-node.js'
import type { QueryNode } from '../operation-node/query-node.js'
import { RawNode } from '../operation-node/raw-node.js'
import type { ReferenceNode } from '../operation-node/reference-node.js'
import type { ReferencesNode } from '../operation-node/references-node.js'
Expand Down Expand Up @@ -117,7 +116,6 @@ import { logOnce } from '../util/log-once.js'
import type { CollateNode } from '../operation-node/collate-node.js'
import type { QueryId } from '../util/query-id.js'
import type { RenameConstraintNode } from '../operation-node/rename-constraint-node.js'

const LIT_WRAP_REGEX = /'/g

export class DefaultQueryCompiler
Expand Down Expand Up @@ -942,6 +940,12 @@ export class DefaultQueryCompiler
this.compileList(node.updates)
}

protected override visitNullIf(node: FunctionNode): void {
this.append('nullif(')
this.compileList(node.arguments)
this.append(')')
}

protected override visitCreateIndex(node: CreateIndexNode): void {
this.append('create ')

Expand Down
46 changes: 46 additions & 0 deletions test/node/src/null-if.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Kysely, sql } from '../../../dist/cjs'

import {
destroyTest,
initTest,
TestContext,
expect,
Database,
clearDatabase,
DIALECTS,
} from './test-setup.js'

for (const dialect of DIALECTS) {
describe(`${dialect} null if`, () => {
let ctx: TestContext
let db: Kysely<Database>

before(async function () {
ctx = await initTest(this, dialect)

db = ctx.db as any
})

afterEach(async () => {
await clearDatabase(ctx)
})

after(async () => {
await destroyTest(ctx)
})

it('should return correct values for NULLIF without selection from a table', async () => {
const result = await db
.selectNoFrom(({ fn }) => [
fn.nullif(sql.lit('2000-01-01'), '2000-01-01').as('null_dates_equal'),
fn.nullif(sql.lit(1), 2).as('null_numbers_different'),
])
.executeTakeFirstOrThrow()

expect(result).to.eql({
null_dates_equal: null,
null_numbers_different: dialect === 'mysql' ? '1' : 1,
})
})
})
}