diff --git a/docs/en/jpql-with-kotlin-jdsl/expressions.md b/docs/en/jpql-with-kotlin-jdsl/expressions.md index 287f4e195..4ff982357 100644 --- a/docs/en/jpql-with-kotlin-jdsl/expressions.md +++ b/docs/en/jpql-with-kotlin-jdsl/expressions.md @@ -227,6 +227,7 @@ Use the following functions to build arithmetic functions: * CEILING (ceiling) * FLOOR (floor) * ROUND (round) +* SIGN (sign) * SQRT (sqrt) ```kotlin @@ -238,6 +239,8 @@ floor(path(Book::price)) round(path(Book::price), 2) +sign(path(Book::price)) + sqrt(path(Book::price)) ``` @@ -248,7 +251,6 @@ sqrt(path(Book::price)) | LN | not yet | | MOD | not yet | | POWER | not yet | -| SIGN | not yet | | SIZE | not yet | | INDEX | not yet | diff --git a/docs/ko/jpql-with-kotlin-jdsl/expressions.md b/docs/ko/jpql-with-kotlin-jdsl/expressions.md index 9c13e1c98..fef9616ba 100644 --- a/docs/ko/jpql-with-kotlin-jdsl/expressions.md +++ b/docs/ko/jpql-with-kotlin-jdsl/expressions.md @@ -223,6 +223,7 @@ locate("Book", path(Book::title)) * CEILING (ceiling) * FLOOR (floor) * ROUND (round) +* SIGN (sign) * SQRT (sqrt) ```kotlin @@ -234,6 +235,8 @@ floor(path(Book::price)) round(path(Book::price), 2) +sign(path(Book::price)) + sqrt(path(Book::price)) ``` @@ -244,7 +247,6 @@ sqrt(path(Book::price)) | LN | not yet | | MOD | not yet | | POWER | not yet | -| SIGN | not yet | | SIZE | not yet | | INDEX | not yet | diff --git a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/Jpql.kt b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/Jpql.kt index 75b37025d..dcfbfd111 100644 --- a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/Jpql.kt +++ b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/Jpql.kt @@ -616,6 +616,30 @@ open class Jpql : JpqlDsl { return Expressions.round(value.toExpression(), scale.toExpression()) } + /** + * Creates an expression that represents the sign of value. + * + * - If value is positive, it returns 1. + * - If value is negative, it returns -1. + * - If value is zero, it returns 0. + */ + @SinceJdsl("3.4.0") + fun sign(expr: KProperty1): Expression { + return Expressions.sign(Paths.path(expr)) + } + + /** + * Creates an expression that represents the sign of value. + * + * - If value is positive, it returns 1. + * - If value is negative, it returns -1. + * - If value is zero, it returns 0. + */ + @SinceJdsl("3.4.0") + fun sign(value: Expressionable): Expression { + return Expressions.sign(value.toExpression()) + } + /** * Creates an expression that represents the square root of value. */ diff --git a/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/SignDslTest.kt b/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/SignDslTest.kt new file mode 100644 index 000000000..0e3ad933c --- /dev/null +++ b/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/SignDslTest.kt @@ -0,0 +1,47 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression + +import com.linecorp.kotlinjdsl.dsl.jpql.entity.book.Book +import com.linecorp.kotlinjdsl.dsl.jpql.queryPart +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions +import com.linecorp.kotlinjdsl.querymodel.jpql.path.Paths +import org.assertj.core.api.WithAssertions +import org.junit.jupiter.api.Test + +class SignDslTest : WithAssertions { + private val expression1 = Paths.path(Book::salePrice) + + @Test + fun `sign() with a property`() { + // when + val expression = queryPart { + sign(Book::price) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.sign( + value = Paths.path(Book::price), + ) + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun `sign() with a expression`() { + // when + val expression = queryPart { + sign(expression1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.sign( + value = expression1, + ) + + assertThat(actual).isEqualTo(expected) + } +} diff --git a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/Expressions.kt b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/Expressions.kt index 045d93ea7..b443d073b 100644 --- a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/Expressions.kt +++ b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/Expressions.kt @@ -32,6 +32,7 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlParam import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlPathType import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlPlus import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlRound +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlSign import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlSqrt import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlSubquery import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlSubstring @@ -243,6 +244,14 @@ object Expressions { return JpqlRound(value, scale) } + /** + * Creates an expression that represents the sign of a numeric value. + */ + @SinceJdsl("3.4.0") + fun sign(value: Expression): Expression { + return JpqlSign(value) + } + /** * Creates an expression that represents the square root of the value. */ diff --git a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlSign.kt b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlSign.kt new file mode 100644 index 000000000..25160d693 --- /dev/null +++ b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlSign.kt @@ -0,0 +1,12 @@ +package com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl + +import com.linecorp.kotlinjdsl.Internal +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression + +/** + * Expression that calculates the sign of a numeric [value]. + */ +@Internal +data class JpqlSign internal constructor( + val value: Expression, +) : Expression diff --git a/query-model/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/ExpressionsTest.kt b/query-model/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/ExpressionsTest.kt index 9fb1f6b87..6b0246d44 100644 --- a/query-model/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/ExpressionsTest.kt +++ b/query-model/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/ExpressionsTest.kt @@ -34,6 +34,7 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlParam import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlPathType import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlPlus import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlRound +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlSign import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlSqrt import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlSubquery import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlSubstring @@ -390,6 +391,19 @@ class ExpressionsTest : WithAssertions { assertThat(actual).isEqualTo(expected) } + @Test + fun sign() { + // when + val actual = Expressions.sign(intExpression1) + + // then + val expected = JpqlSign( + intExpression1, + ) + + assertThat(actual).isEqualTo(expected) + } + @Test fun sqrt() { // when diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/JpqlRenderContext.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/JpqlRenderContext.kt index 094735378..3872d5ee9 100644 --- a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/JpqlRenderContext.kt +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/JpqlRenderContext.kt @@ -98,6 +98,7 @@ import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlPlusSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlPredicateParenthesesSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlRoundSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlSelectQuerySerializer +import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlSignSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlSortSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlSqrtSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlSubquerySerializer @@ -348,6 +349,7 @@ private class DefaultModule : JpqlRenderModule { JpqlPredicateParenthesesSerializer(), JpqlRoundSerializer(), JpqlSelectQuerySerializer(), + JpqlSignSerializer(), JpqlSortSerializer(), JpqlSqrtSerializer(), JpqlSubquerySerializer(), diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlSignSerializer.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlSignSerializer.kt new file mode 100644 index 000000000..bc88ba206 --- /dev/null +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlSignSerializer.kt @@ -0,0 +1,26 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.Internal +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlSign +import com.linecorp.kotlinjdsl.render.RenderContext +import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlRenderSerializer +import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlSerializer +import com.linecorp.kotlinjdsl.render.jpql.writer.JpqlWriter +import kotlin.reflect.KClass + +@Internal +class JpqlSignSerializer : JpqlSerializer> { + override fun handledType(): KClass> { + return JpqlSign::class + } + + override fun serialize(part: JpqlSign<*>, writer: JpqlWriter, context: RenderContext) { + val delegate = context.getValue(JpqlRenderSerializer) + + writer.write("SIGN") + + writer.writeParentheses { + delegate.serialize(part.value, writer, context) + } + } +} diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlSignSerializerTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlSignSerializerTest.kt new file mode 100644 index 000000000..c8b56251c --- /dev/null +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlSignSerializerTest.kt @@ -0,0 +1,55 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlSign +import com.linecorp.kotlinjdsl.querymodel.jpql.path.Paths +import com.linecorp.kotlinjdsl.render.TestRenderContext +import com.linecorp.kotlinjdsl.render.jpql.entity.book.Book +import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlRenderSerializer +import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlSerializerTest +import com.linecorp.kotlinjdsl.render.jpql.writer.JpqlWriter +import io.mockk.impl.annotations.MockK +import io.mockk.verifySequence +import org.assertj.core.api.WithAssertions +import org.junit.jupiter.api.Test + +@JpqlSerializerTest +class JpqlSignSerializerTest : WithAssertions { + private val sut = JpqlSignSerializer() + + @MockK + private lateinit var writer: JpqlWriter + + @MockK + private lateinit var serializer: JpqlRenderSerializer + + private val expression1 = Paths.path(Book::price) + + @Test + fun handledType() { + // when + val actual = sut.handledType() + + // then + assertThat(actual).isEqualTo(JpqlSign::class) + } + + @Test + fun serialize() { + // given + val part = Expressions.sign( + value = expression1, + ) + val context = TestRenderContext(serializer) + + // when + sut.serialize(part as JpqlSign<*>, writer, context) + + // then + verifySequence { + writer.write("SIGN") + writer.writeParentheses(any()) + serializer.serialize(expression1, writer, context) + } + } +}