Skip to content

feat: add null-safe nullable-argument extensions for querydsl-kotlin - #1846

Merged
velo merged 1 commit into
OpenFeign:masterfrom
HarryJhin:feat/null-safe-kotlin-extensions
Jul 21, 2026
Merged

feat: add null-safe nullable-argument extensions for querydsl-kotlin#1846
velo merged 1 commit into
OpenFeign:masterfrom
HarryJhin:feat/null-safe-kotlin-extensions

Conversation

@HarryJhin

Copy link
Copy Markdown
Contributor

Fixes #1845

Problem

Building dynamic queries in Kotlin is more verbose than in Java. where(...) already ignores
null predicates (DefaultQueryMetadata.addWhere returns early on null), but Kotlin has no
ternary operator, so an optional filter forces if/else, ?.let, a BooleanBuilder, or a
per-field helper. The existing ExpressionExtensions.kt provides only non-null operator overloads,
which do not cover the "skip this condition when the argument is null" case that dominates
dynamic-query code.

Approach

Null-safe infix extensions that return null (dropped by where(...)) when the receiver or the
argument is null:

where(
    member.name eq name,   // name: String? -> skipped when null
    member.age gt age,      // age: Int?      -> skipped when null
)
  • New per-type files: SimpleExpressionExtensions, ComparableExpressionExtensions,
    NumberExpressionExtensions, StringExpressionExtensions.
  • Each comparison/equality helper has a value form and an expression form
    (gt(value) and gt(expr)), covering parameter and column-to-column comparisons.
  • between / notBetween support partial ranges: both bounds yields BETWEEN, one bound yields
    a one-sided comparison, neither is skipped. A ClosedRange overload (age between (20..60)) is
    also provided.
  • The existing top-level and / or / xor / xnor / not in ExpressionExtensions.kt are
    folded into null-safe variants (and / or with partial application, the others skip on null).

Compatibility

  • Additive: eq / ne / in / gt / goe / lt / loe / between / like / contains
    / startsWith / endsWith reuse member-backed names, so member-over-extension keeps existing
    non-null calls intact.
  • Source-breaking: folding and / or / xor / xnor / not changes their return type to
    BooleanExpression?. In practice BooleanExpression.and/or/not stay non-null via their member
    methods, so only raw Expression<Boolean> callers are affected. I'll leave the release and
    version placement entirely to your judgement.

Tests

  • The four null combinations (this-null / arg-null / both-null / both-non-null) per function, for
    both the value and expression overloads; partial application for between / notBetween /
    and / or.
  • Non-null cases are asserted equal to the member-method result (member-over-extension check).
  • Existing CollectionTest still passes. 39 tests total, green with the JDK 17 compile target and
    the JDK 25 build toolchain.

Nullable-receiver / nullable-argument infix extensions that return null so
where(...) drops them, letting optional filters skip the if-else /
BooleanBuilder / per-field helper boilerplate:

    where(member.name eq name, member.age gt age)  // null args skipped

Comparison / equality / string / collection helpers, each null-safe against
both a value and another expression (member-over-extension keeps existing
non-null calls intact): eq, ne, in, gt, goe, lt, loe,
between (partial range: both/lower-only/upper-only/skip), notBetween,
between(ClosedRange), like, contains, startsWith, endsWith.

Boolean operators are folded into null-safe variants (and/or with partial
application, xor/xnor/not skip on null). This changes their return type to
BooleanExpression?, a source-breaking change for raw Expression<Boolean>
callers; BooleanExpression.and/or/not stay non-null via member methods.

Each function covers the four null combinations (value and expression
overloads, plus partial application for between/and/or); existing
CollectionTest still passes.

@velo velo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Null-safe Kotlin infix extensions are additive, well-documented, and cover all four null combinations in tests. The and/or/xor/xnor/not fold to nullable is an acknowledged minor source-break that stays non-null via the BooleanExpression member methods for typical callers. Full CI matrix green. LGTM.

@velo
velo merged commit 3d5d3e4 into OpenFeign:master Jul 21, 2026
18 checks passed
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.

Provide null-safe (nullable-argument) extensions for querydsl-kotlin dynamic queries

2 participants