diff --git a/querydsl-libraries/querydsl-kotlin/src/main/kotlin/com/querydsl/kotlin/ComparableExpressionExtensions.kt b/querydsl-libraries/querydsl-kotlin/src/main/kotlin/com/querydsl/kotlin/ComparableExpressionExtensions.kt new file mode 100644 index 0000000000..06d5776c9b --- /dev/null +++ b/querydsl-libraries/querydsl-kotlin/src/main/kotlin/com/querydsl/kotlin/ComparableExpressionExtensions.kt @@ -0,0 +1,140 @@ +/* + * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.querydsl.kotlin + +import com.querydsl.core.types.Expression +import com.querydsl.core.types.dsl.BooleanExpression +import com.querydsl.core.types.dsl.ComparableExpression + +/** + * Null-safe greater-than against a value, skipping when either side is null. + * + * @param value the value to compare against, or null to skip + * @return this > value, or null + */ +infix fun > ComparableExpression?.gt(value: T?): BooleanExpression? = + if (this == null || value == null) null else this.gt(value) + +/** + * Null-safe greater-than against another expression, skipping when either side is null. + * + * @param expr the expression to compare against, or null to skip + * @return this > expr, or null + */ +infix fun > ComparableExpression?.gt(expr: Expression?): BooleanExpression? = + if (this == null || expr == null) null else this.gt(expr) + +/** + * Null-safe greater-than-or-equal against a value, skipping when either side is null. + * + * @param value the value to compare against, or null to skip + * @return this >= value, or null + */ +infix fun > ComparableExpression?.goe(value: T?): BooleanExpression? = + if (this == null || value == null) null else this.goe(value) + +/** + * Null-safe greater-than-or-equal against another expression, skipping when either side is null. + * + * @param expr the expression to compare against, or null to skip + * @return this >= expr, or null + */ +infix fun > ComparableExpression?.goe(expr: Expression?): BooleanExpression? = + if (this == null || expr == null) null else this.goe(expr) + +/** + * Null-safe less-than against a value, skipping when either side is null. + * + * @param value the value to compare against, or null to skip + * @return this < value, or null + */ +infix fun > ComparableExpression?.lt(value: T?): BooleanExpression? = + if (this == null || value == null) null else this.lt(value) + +/** + * Null-safe less-than against another expression, skipping when either side is null. + * + * @param expr the expression to compare against, or null to skip + * @return this < expr, or null + */ +infix fun > ComparableExpression?.lt(expr: Expression?): BooleanExpression? = + if (this == null || expr == null) null else this.lt(expr) + +/** + * Null-safe less-than-or-equal against a value, skipping when either side is null. + * + * @param value the value to compare against, or null to skip + * @return this <= value, or null + */ +infix fun > ComparableExpression?.loe(value: T?): BooleanExpression? = + if (this == null || value == null) null else this.loe(value) + +/** + * Null-safe less-than-or-equal against another expression, skipping when either side is null. + * + * @param expr the expression to compare against, or null to skip + * @return this <= expr, or null + */ +infix fun > ComparableExpression?.loe(expr: Expression?): BooleanExpression? = + if (this == null || expr == null) null else this.loe(expr) + +/** + * Null-safe BETWEEN with partial-range support, judging each bound independently. + * + * Both bounds present yields BETWEEN, lower-only yields `>=`, upper-only yields `<=`, + * and neither is skipped. + * + * @param range a `from to to` pair where either bound may be null + * @return this BETWEEN from AND to (or a one-sided `>=` / `<=`), or null + */ +infix fun > ComparableExpression?.between(range: Pair): BooleanExpression? { + if (this == null) return null + val (from, to) = range + return when { + from != null && to != null -> this.between(from, to) + from != null -> this.goe(from) + to != null -> this.loe(to) + else -> null + } +} + +/** + * Null-safe BETWEEN over a Kotlin [ClosedRange], where both bounds are always present. + * + * @param range the closed range (`from..to`) + * @return this BETWEEN from AND to, or null + */ +infix fun > ComparableExpression?.between(range: ClosedRange): BooleanExpression? = + this?.between(range.start, range.endInclusive) + +/** + * Null-safe NOT BETWEEN, mirroring [between] with partial-range support. + * + * Both bounds present yields NOT BETWEEN, lower-only yields `<`, upper-only yields `>`, + * and neither is skipped. + * + * @param range a `from to to` pair where either bound may be null + * @return this NOT BETWEEN from AND to (or a one-sided `<` / `>`), or null + */ +infix fun > ComparableExpression?.notBetween(range: Pair): BooleanExpression? { + if (this == null) return null + val (from, to) = range + return when { + from != null && to != null -> this.notBetween(from, to) + from != null -> this.lt(from) + to != null -> this.gt(to) + else -> null + } +} diff --git a/querydsl-libraries/querydsl-kotlin/src/main/kotlin/com/querydsl/kotlin/ExpressionExtensions.kt b/querydsl-libraries/querydsl-kotlin/src/main/kotlin/com/querydsl/kotlin/ExpressionExtensions.kt index f54daa0a73..06cf42d3dc 100644 --- a/querydsl-libraries/querydsl-kotlin/src/main/kotlin/com/querydsl/kotlin/ExpressionExtensions.kt +++ b/querydsl-libraries/querydsl-kotlin/src/main/kotlin/com/querydsl/kotlin/ExpressionExtensions.kt @@ -20,52 +20,66 @@ import com.querydsl.core.types.dsl.* import com.querydsl.core.types.dsl.Expressions.constant /** - * Get a negation of this boolean expression + * Null-safe negation that skips when this is null. * - * @return !this + * @return NOT this, or null */ -operator fun Expression.not() : BooleanExpression { - return Expressions.booleanOperation(Ops.NOT, this) +operator fun Expression?.not() : BooleanExpression? { + return if (this == null) null else Expressions.booleanOperation(Ops.NOT, this) } /** - * Get an intersection of this and the given expression + * Null-safe intersection. A null side is skipped: both present -> AND, one present -> that side, + * both null -> null (dropped by where()). * - * @param predicate right hand side of the union - * @return this and right + * @param predicate right-hand side, or null to skip + * @return this AND predicate (or the present side), or null */ -infix fun Expression.and(predicate: Expression) : BooleanExpression { - return Expressions.booleanOperation(Ops.AND, this, predicate); +infix fun Expression?.and(predicate: Expression?) : BooleanExpression? { + return when { + this != null && predicate != null -> Expressions.booleanOperation(Ops.AND, this, predicate) + this != null -> Expressions.asBoolean(this) + predicate != null -> Expressions.asBoolean(predicate) + else -> null + } } /** - * Get a union of this and the given expression + * Null-safe union. A null side is skipped: both present -> OR, one present -> that side, + * both null -> null. * - * @param predicate right hand side of the union - * @return this || right + * @param predicate right-hand side, or null to skip + * @return this OR predicate (or the present side), or null */ -infix fun Expression.or(predicate: Expression) : BooleanExpression { - return Expressions.booleanOperation(Ops.OR, this, predicate); +infix fun Expression?.or(predicate: Expression?) : BooleanExpression? { + return when { + this != null && predicate != null -> Expressions.booleanOperation(Ops.OR, this, predicate) + this != null -> Expressions.asBoolean(this) + predicate != null -> Expressions.asBoolean(predicate) + else -> null + } } /** - * Get a union of this and the given expression + * Null-safe XOR that skips when either side is null (XOR has no meaningful one-sided form). * - * @param predicate right hand side of the union - * @return this || right + * @param predicate right-hand side, or null to skip + * @return this XOR predicate, or null */ -infix fun Expression.xor(predicate: Expression) : BooleanExpression { - return Expressions.booleanOperation(Ops.XOR, this, predicate); +infix fun Expression?.xor(predicate: Expression?) : BooleanExpression? { + return if (this == null || predicate == null) null + else Expressions.booleanOperation(Ops.XOR, this, predicate) } /** - * Get a union of this and the given expression + * Null-safe XNOR that skips when either side is null. * - * @param predicate right hand side of the union - * @return this || right + * @param predicate right-hand side, or null to skip + * @return this XNOR predicate, or null */ -infix fun Expression.xnor(predicate: Expression) : BooleanExpression { - return Expressions.booleanOperation(Ops.XNOR, this, predicate); +infix fun Expression?.xnor(predicate: Expression?) : BooleanExpression? { + return if (this == null || predicate == null) null + else Expressions.booleanOperation(Ops.XNOR, this, predicate) } /** diff --git a/querydsl-libraries/querydsl-kotlin/src/main/kotlin/com/querydsl/kotlin/NumberExpressionExtensions.kt b/querydsl-libraries/querydsl-kotlin/src/main/kotlin/com/querydsl/kotlin/NumberExpressionExtensions.kt new file mode 100644 index 0000000000..1af5316083 --- /dev/null +++ b/querydsl-libraries/querydsl-kotlin/src/main/kotlin/com/querydsl/kotlin/NumberExpressionExtensions.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.querydsl.kotlin + +import com.querydsl.core.types.Expression +import com.querydsl.core.types.dsl.BooleanExpression +import com.querydsl.core.types.dsl.NumberExpression + +// NumberExpression does not extend ComparableExpression, so these comparison helpers are declared +// separately from the ComparableExpression extensions. + +/** + * Null-safe greater-than against a value, skipping when either side is null. + * + * @param value the value to compare against, or null to skip + * @return this > value, or null + */ +infix fun NumberExpression?.gt(value: T?): BooleanExpression? where T : Number, T : Comparable = + if (this == null || value == null) null else this.gt(value) + +/** + * Null-safe greater-than against another expression, skipping when either side is null. + * + * @param expr the expression to compare against, or null to skip + * @return this > expr, or null + */ +infix fun NumberExpression?.gt(expr: Expression?): BooleanExpression? where T : Number, T : Comparable = + if (this == null || expr == null) null else this.gt(expr) + +/** + * Null-safe greater-than-or-equal against a value, skipping when either side is null. + * + * @param value the value to compare against, or null to skip + * @return this >= value, or null + */ +infix fun NumberExpression?.goe(value: T?): BooleanExpression? where T : Number, T : Comparable = + if (this == null || value == null) null else this.goe(value) + +/** + * Null-safe greater-than-or-equal against another expression, skipping when either side is null. + * + * @param expr the expression to compare against, or null to skip + * @return this >= expr, or null + */ +infix fun NumberExpression?.goe(expr: Expression?): BooleanExpression? where T : Number, T : Comparable = + if (this == null || expr == null) null else this.goe(expr) + +/** + * Null-safe less-than against a value, skipping when either side is null. + * + * @param value the value to compare against, or null to skip + * @return this < value, or null + */ +infix fun NumberExpression?.lt(value: T?): BooleanExpression? where T : Number, T : Comparable = + if (this == null || value == null) null else this.lt(value) + +/** + * Null-safe less-than against another expression, skipping when either side is null. + * + * @param expr the expression to compare against, or null to skip + * @return this < expr, or null + */ +infix fun NumberExpression?.lt(expr: Expression?): BooleanExpression? where T : Number, T : Comparable = + if (this == null || expr == null) null else this.lt(expr) + +/** + * Null-safe less-than-or-equal against a value, skipping when either side is null. + * + * @param value the value to compare against, or null to skip + * @return this <= value, or null + */ +infix fun NumberExpression?.loe(value: T?): BooleanExpression? where T : Number, T : Comparable = + if (this == null || value == null) null else this.loe(value) + +/** + * Null-safe less-than-or-equal against another expression, skipping when either side is null. + * + * @param expr the expression to compare against, or null to skip + * @return this <= expr, or null + */ +infix fun NumberExpression?.loe(expr: Expression?): BooleanExpression? where T : Number, T : Comparable = + if (this == null || expr == null) null else this.loe(expr) diff --git a/querydsl-libraries/querydsl-kotlin/src/main/kotlin/com/querydsl/kotlin/SimpleExpressionExtensions.kt b/querydsl-libraries/querydsl-kotlin/src/main/kotlin/com/querydsl/kotlin/SimpleExpressionExtensions.kt new file mode 100644 index 0000000000..2b6ae70621 --- /dev/null +++ b/querydsl-libraries/querydsl-kotlin/src/main/kotlin/com/querydsl/kotlin/SimpleExpressionExtensions.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.querydsl.kotlin + +import com.querydsl.core.types.Expression +import com.querydsl.core.types.dsl.BooleanExpression +import com.querydsl.core.types.dsl.SimpleExpression + +/** + * Null-safe equality against a value, skipping the condition when either side is null. + * + * @param value the value to compare against, or null to skip + * @return this = value, or null + */ +infix fun SimpleExpression?.eq(value: T?): BooleanExpression? = + if (this == null || value == null) null else this.eq(value) + +/** + * Null-safe equality against another expression, skipping the condition when either side is null. + * + * @param expr the expression to compare against, or null to skip + * @return this = expr, or null + */ +infix fun SimpleExpression?.eq(expr: Expression?): BooleanExpression? = + if (this == null || expr == null) null else this.eq(expr) + +/** + * Null-safe inequality against a value, skipping the condition when either side is null. + * + * @param value the value to compare against, or null to skip + * @return this != value, or null + */ +infix fun SimpleExpression?.ne(value: T?): BooleanExpression? = + if (this == null || value == null) null else this.ne(value) + +/** + * Null-safe inequality against another expression, skipping the condition when either side is null. + * + * @param expr the expression to compare against, or null to skip + * @return this != expr, or null + */ +infix fun SimpleExpression?.ne(expr: Expression?): BooleanExpression? = + if (this == null || expr == null) null else this.ne(expr) + +/** + * Null-safe IN that skips the condition when this is null or the collection is null or empty. + * + * @param values the values to match, or null/empty to skip + * @return this IN values, or null + */ +infix fun SimpleExpression?.`in`(values: Collection?): BooleanExpression? = + if (this == null || values.isNullOrEmpty()) null else this.`in`(values) diff --git a/querydsl-libraries/querydsl-kotlin/src/main/kotlin/com/querydsl/kotlin/StringExpressionExtensions.kt b/querydsl-libraries/querydsl-kotlin/src/main/kotlin/com/querydsl/kotlin/StringExpressionExtensions.kt new file mode 100644 index 0000000000..a38c6f2700 --- /dev/null +++ b/querydsl-libraries/querydsl-kotlin/src/main/kotlin/com/querydsl/kotlin/StringExpressionExtensions.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.querydsl.kotlin + +import com.querydsl.core.types.dsl.BooleanExpression +import com.querydsl.core.types.dsl.StringExpression + +/** + * Null-safe LIKE that skips the condition when either side is null. + * + * @param pattern the LIKE pattern, or null to skip + * @return this LIKE pattern, or null + */ +infix fun StringExpression?.like(pattern: String?): BooleanExpression? = + if (this == null || pattern == null) null else this.like(pattern) + +/** + * Null-safe CONTAINS that skips the condition when either side is null. + * + * @param substring the substring to match, or null to skip + * @return this LIKE %substring%, or null + */ +infix fun StringExpression?.contains(substring: String?): BooleanExpression? = + if (this == null || substring == null) null else this.contains(substring) + +/** + * Null-safe STARTS WITH that skips the condition when either side is null. + * + * @param prefix the prefix to match, or null to skip + * @return this LIKE prefix%, or null + */ +infix fun StringExpression?.startsWith(prefix: String?): BooleanExpression? = + if (this == null || prefix == null) null else this.startsWith(prefix) + +/** + * Null-safe ENDS WITH that skips the condition when either side is null. + * + * @param suffix the suffix to match, or null to skip + * @return this LIKE %suffix, or null + */ +infix fun StringExpression?.endsWith(suffix: String?): BooleanExpression? = + if (this == null || suffix == null) null else this.endsWith(suffix) diff --git a/querydsl-libraries/querydsl-kotlin/src/test/kotlin/com/querydsl/kotlin/ComparableExpressionExtensionsTest.kt b/querydsl-libraries/querydsl-kotlin/src/test/kotlin/com/querydsl/kotlin/ComparableExpressionExtensionsTest.kt new file mode 100644 index 0000000000..445628d2b4 --- /dev/null +++ b/querydsl-libraries/querydsl-kotlin/src/test/kotlin/com/querydsl/kotlin/ComparableExpressionExtensionsTest.kt @@ -0,0 +1,138 @@ +/* + * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.querydsl.kotlin + +import com.querydsl.core.types.Expression +import com.querydsl.core.types.dsl.ComparableExpression +import com.querydsl.core.types.dsl.Expressions +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ComparableExpressionExtensionsTest { + + private val score: ComparableExpression = Expressions.comparablePath(Int::class.javaObjectType, "score") + private val other: ComparableExpression = Expressions.comparablePath(Int::class.javaObjectType, "other") + private val nil: ComparableExpression? = null + private val nilValue: Int? = null + private val nilExpr: Expression? = null + + @Test + fun `gt against a value covers the four null combinations`() { + val c: ComparableExpression? = score + assertEquals(score.gt(5), c.gt(5)) + assertNull(c.gt(nilValue)) + assertNull(nil.gt(5)) + assertNull(nil.gt(nilValue)) + } + + @Test + fun `gt against an expression covers the four null combinations`() { + val c: ComparableExpression? = score + val e: Expression? = other + assertEquals(score.gt(other), c.gt(e)) + assertNull(c.gt(nilExpr)) + assertNull(nil.gt(e)) + assertNull(nil.gt(nilExpr)) + } + + @Test + fun `goe against a value covers the four null combinations`() { + val c: ComparableExpression? = score + assertEquals(score.goe(5), c.goe(5)) + assertNull(c.goe(nilValue)) + assertNull(nil.goe(5)) + assertNull(nil.goe(nilValue)) + } + + @Test + fun `goe against an expression covers the four null combinations`() { + val c: ComparableExpression? = score + val e: Expression? = other + assertEquals(score.goe(other), c.goe(e)) + assertNull(c.goe(nilExpr)) + assertNull(nil.goe(e)) + assertNull(nil.goe(nilExpr)) + } + + @Test + fun `lt against a value covers the four null combinations`() { + val c: ComparableExpression? = score + assertEquals(score.lt(5), c.lt(5)) + assertNull(c.lt(nilValue)) + assertNull(nil.lt(5)) + assertNull(nil.lt(nilValue)) + } + + @Test + fun `lt against an expression covers the four null combinations`() { + val c: ComparableExpression? = score + val e: Expression? = other + assertEquals(score.lt(other), c.lt(e)) + assertNull(c.lt(nilExpr)) + assertNull(nil.lt(e)) + assertNull(nil.lt(nilExpr)) + } + + @Test + fun `loe against a value covers the four null combinations`() { + val c: ComparableExpression? = score + assertEquals(score.loe(5), c.loe(5)) + assertNull(c.loe(nilValue)) + assertNull(nil.loe(5)) + assertNull(nil.loe(nilValue)) + } + + @Test + fun `loe against an expression covers the four null combinations`() { + val c: ComparableExpression? = score + val e: Expression? = other + assertEquals(score.loe(other), c.loe(e)) + assertNull(c.loe(nilExpr)) + assertNull(nil.loe(e)) + assertNull(nil.loe(nilExpr)) + } + + @Test + fun `between judges each bound independently`() { + val c: ComparableExpression? = score + val lo: Int? = 1 + val hi: Int? = 10 + assertEquals(score.between(1, 10), c.between(lo to hi)) // both -> BETWEEN + assertEquals(score.goe(1), c.between(lo to null)) // lower-only -> >= + assertEquals(score.loe(10), c.between(null to hi)) // upper-only -> <= + assertNull(c.between(null to null)) // neither -> skip + assertNull(nil.between(lo to hi)) // this null + } + + @Test + fun `notBetween mirrors between`() { + val c: ComparableExpression? = score + val lo: Int? = 1 + val hi: Int? = 10 + assertEquals(score.notBetween(1, 10), c.notBetween(lo to hi)) + assertEquals(score.lt(1), c.notBetween(lo to null)) + assertEquals(score.gt(10), c.notBetween(null to hi)) + assertNull(c.notBetween(null to null)) + assertNull(nil.notBetween(lo to hi)) + } + + @Test + fun `between with closed range`() { + val c: ComparableExpression? = score + assertEquals(score.between(20, 60), c.between(20..60)) + assertNull(nil.between(20..60)) + } +} diff --git a/querydsl-libraries/querydsl-kotlin/src/test/kotlin/com/querydsl/kotlin/ExpressionExtensionsTest.kt b/querydsl-libraries/querydsl-kotlin/src/test/kotlin/com/querydsl/kotlin/ExpressionExtensionsTest.kt new file mode 100644 index 0000000000..4a16005bbc --- /dev/null +++ b/querydsl-libraries/querydsl-kotlin/src/test/kotlin/com/querydsl/kotlin/ExpressionExtensionsTest.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.querydsl.kotlin + +import com.querydsl.core.types.Expression +import com.querydsl.core.types.Ops +import com.querydsl.core.types.dsl.BooleanExpression +import com.querydsl.core.types.dsl.Expressions +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ExpressionExtensionsTest { + + private val a: BooleanExpression = Expressions.booleanPath("a") + private val b: BooleanExpression = Expressions.booleanPath("b") + + private fun nil(): Expression? = null + + @Test + fun `and judges each side independently`() { + val an: Expression? = a + val bn: Expression? = b + assertEquals(Expressions.booleanOperation(Ops.AND, a, b), an and bn) // both -> AND + assertEquals(Expressions.asBoolean(a), an and null) // this only -> this + assertEquals(Expressions.asBoolean(b), nil() and bn) // arg only -> arg + assertNull(nil() and null) // neither -> skip + } + + @Test + fun `or judges each side independently`() { + val an: Expression? = a + val bn: Expression? = b + assertEquals(Expressions.booleanOperation(Ops.OR, a, b), an or bn) + assertEquals(Expressions.asBoolean(a), an or null) + assertEquals(Expressions.asBoolean(b), nil() or bn) + assertNull(nil() or null) + } + + @Test + fun `xor skips when either side is null`() { + val an: Expression? = a + val bn: Expression? = b + assertEquals(Expressions.booleanOperation(Ops.XOR, a, b), an xor bn) + assertNull(an xor null) + assertNull(nil() xor bn) + assertNull(nil() xor null) + } + + @Test + fun `not skips when null`() { + val an: Expression? = a + assertEquals(Expressions.booleanOperation(Ops.NOT, a), !an) + assertNull(!nil()) + } + + @Test + fun `non-null BooleanExpression and resolves to the member method`() { + val result: BooleanExpression = a.and(b) // must compile as non-null (member wins) + assertEquals(a.and(b), result) + } +} diff --git a/querydsl-libraries/querydsl-kotlin/src/test/kotlin/com/querydsl/kotlin/NumberExpressionExtensionsTest.kt b/querydsl-libraries/querydsl-kotlin/src/test/kotlin/com/querydsl/kotlin/NumberExpressionExtensionsTest.kt new file mode 100644 index 0000000000..e59a755e47 --- /dev/null +++ b/querydsl-libraries/querydsl-kotlin/src/test/kotlin/com/querydsl/kotlin/NumberExpressionExtensionsTest.kt @@ -0,0 +1,107 @@ +/* + * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.querydsl.kotlin + +import com.querydsl.core.types.Expression +import com.querydsl.core.types.dsl.Expressions +import com.querydsl.core.types.dsl.NumberExpression +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class NumberExpressionExtensionsTest { + + private val age: NumberExpression = Expressions.numberPath(Int::class.javaObjectType, "age") + private val other: NumberExpression = Expressions.numberPath(Int::class.javaObjectType, "other") + private val nil: NumberExpression? = null + private val nilValue: Int? = null + private val nilExpr: Expression? = null + + @Test + fun `gt against a value covers the four null combinations`() { + val n: NumberExpression? = age + assertEquals(age.gt(20), n.gt(20)) + assertNull(n.gt(nilValue)) + assertNull(nil.gt(20)) + assertNull(nil.gt(nilValue)) + } + + @Test + fun `gt against an expression covers the four null combinations`() { + val n: NumberExpression? = age + val e: Expression? = other + assertEquals(age.gt(other), n.gt(e)) + assertNull(n.gt(nilExpr)) + assertNull(nil.gt(e)) + assertNull(nil.gt(nilExpr)) + } + + @Test + fun `goe against a value covers the four null combinations`() { + val n: NumberExpression? = age + assertEquals(age.goe(20), n.goe(20)) + assertNull(n.goe(nilValue)) + assertNull(nil.goe(20)) + assertNull(nil.goe(nilValue)) + } + + @Test + fun `goe against an expression covers the four null combinations`() { + val n: NumberExpression? = age + val e: Expression? = other + assertEquals(age.goe(other), n.goe(e)) + assertNull(n.goe(nilExpr)) + assertNull(nil.goe(e)) + assertNull(nil.goe(nilExpr)) + } + + @Test + fun `lt against a value covers the four null combinations`() { + val n: NumberExpression? = age + assertEquals(age.lt(20), n.lt(20)) + assertNull(n.lt(nilValue)) + assertNull(nil.lt(20)) + assertNull(nil.lt(nilValue)) + } + + @Test + fun `lt against an expression covers the four null combinations`() { + val n: NumberExpression? = age + val e: Expression? = other + assertEquals(age.lt(other), n.lt(e)) + assertNull(n.lt(nilExpr)) + assertNull(nil.lt(e)) + assertNull(nil.lt(nilExpr)) + } + + @Test + fun `loe against a value covers the four null combinations`() { + val n: NumberExpression? = age + assertEquals(age.loe(20), n.loe(20)) + assertNull(n.loe(nilValue)) + assertNull(nil.loe(20)) + assertNull(nil.loe(nilValue)) + } + + @Test + fun `loe against an expression covers the four null combinations`() { + val n: NumberExpression? = age + val e: Expression? = other + assertEquals(age.loe(other), n.loe(e)) + assertNull(n.loe(nilExpr)) + assertNull(nil.loe(e)) + assertNull(nil.loe(nilExpr)) + } +} diff --git a/querydsl-libraries/querydsl-kotlin/src/test/kotlin/com/querydsl/kotlin/SimpleExpressionExtensionsTest.kt b/querydsl-libraries/querydsl-kotlin/src/test/kotlin/com/querydsl/kotlin/SimpleExpressionExtensionsTest.kt new file mode 100644 index 0000000000..4fc049e7f5 --- /dev/null +++ b/querydsl-libraries/querydsl-kotlin/src/test/kotlin/com/querydsl/kotlin/SimpleExpressionExtensionsTest.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.querydsl.kotlin + +import com.querydsl.core.types.Expression +import com.querydsl.core.types.dsl.Expressions +import com.querydsl.core.types.dsl.SimpleExpression +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class SimpleExpressionExtensionsTest { + + private val name: SimpleExpression = Expressions.stringPath("name") + private val other: SimpleExpression = Expressions.stringPath("other") + private val nil: SimpleExpression? = null + private val nilValue: String? = null + private val nilExpr: Expression? = null + + @Test + fun `eq against a value covers the four null combinations`() { + val s: SimpleExpression? = name + assertEquals(name.eq("a"), s.eq("a")) + assertNull(s.eq(nilValue)) + assertNull(nil.eq("a")) + assertNull(nil.eq(nilValue)) + } + + @Test + fun `eq against an expression covers the four null combinations`() { + val s: SimpleExpression? = name + val e: Expression? = other + assertEquals(name.eq(other), s.eq(e)) + assertNull(s.eq(nilExpr)) + assertNull(nil.eq(e)) + assertNull(nil.eq(nilExpr)) + } + + @Test + fun `ne against a value covers the four null combinations`() { + val s: SimpleExpression? = name + assertEquals(name.ne("a"), s.ne("a")) + assertNull(s.ne(nilValue)) + assertNull(nil.ne("a")) + assertNull(nil.ne(nilValue)) + } + + @Test + fun `ne against an expression covers the four null combinations`() { + val s: SimpleExpression? = name + val e: Expression? = other + assertEquals(name.ne(other), s.ne(e)) + assertNull(s.ne(nilExpr)) + assertNull(nil.ne(e)) + assertNull(nil.ne(nilExpr)) + } + + @Test + fun `in covers the four null combinations plus empty`() { + val s: SimpleExpression? = name + assertEquals(name.`in`(listOf("a", "b")), s.`in`(listOf("a", "b"))) + assertNull(s.`in`(null)) + assertNull(s.`in`(emptyList())) + assertNull(nil.`in`(listOf("a"))) + assertNull(nil.`in`(null)) + } +} diff --git a/querydsl-libraries/querydsl-kotlin/src/test/kotlin/com/querydsl/kotlin/StringExpressionExtensionsTest.kt b/querydsl-libraries/querydsl-kotlin/src/test/kotlin/com/querydsl/kotlin/StringExpressionExtensionsTest.kt new file mode 100644 index 0000000000..0d3d01bd3b --- /dev/null +++ b/querydsl-libraries/querydsl-kotlin/src/test/kotlin/com/querydsl/kotlin/StringExpressionExtensionsTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.querydsl.kotlin + +import com.querydsl.core.types.dsl.Expressions +import com.querydsl.core.types.dsl.StringExpression +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class StringExpressionExtensionsTest { + + private val name: StringExpression = Expressions.stringPath("name") + private val nil: StringExpression? = null + + @Test + fun `like covers the four null combinations`() { + val s: StringExpression? = name + assertEquals(name.like("%a%"), s.like("%a%")) + assertNull(s.like(null)) + assertNull(nil.like("%a%")) + assertNull(nil.like(null)) + } + + @Test + fun `contains covers the four null combinations`() { + val s: StringExpression? = name + assertEquals(name.contains("a"), s.contains("a")) + assertNull(s.contains(null)) + assertNull(nil.contains("a")) + assertNull(nil.contains(null)) + } + + @Test + fun `startsWith covers the four null combinations`() { + val s: StringExpression? = name + assertEquals(name.startsWith("a"), s.startsWith("a")) + assertNull(s.startsWith(null)) + assertNull(nil.startsWith("a")) + assertNull(nil.startsWith(null)) + } + + @Test + fun `endsWith covers the four null combinations`() { + val s: StringExpression? = name + assertEquals(name.endsWith("z"), s.endsWith("z")) + assertNull(s.endsWith(null)) + assertNull(nil.endsWith("z")) + assertNull(nil.endsWith(null)) + } +}