Skip to content

Commit

Permalink
feat: Support 'mod' arithmetic operator
Browse files Browse the repository at this point in the history
  • Loading branch information
ddaakk committed Feb 20, 2024
1 parent 755c8d5 commit b158e32
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/en/jpql-with-kotlin-jdsl/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Use the following functions to build arithmetic operations:
* \- (minus)
* \* (times)
* / (div)
* % (mod)

```kotlin
path(Book::price).plus(path(Book::salePrice))
Expand Down
1 change: 1 addition & 0 deletions docs/ko/jpql-with-kotlin-jdsl/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ avg(path(FullTimeEmployee::annualSalary)(EmployeeSalary::value)).`as`(BigDecimal
* \- (minus)
* \* (times)
* / (div)
* % (mod)

```kotlin
path(Book::price).plus(path(Book::salePrice))
Expand Down
52 changes: 52 additions & 0 deletions dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/Jpql.kt
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,54 @@ open class Jpql : JpqlDsl {
return Expressions.div(this.toExpression(), value.toExpression())
}

/**
* Creates an expression that represents the modulo of values.
*
* This is the same as ```(value1) % (value2)```.
*/
@JvmName("modWithParentheses")
@SinceJdsl("3.4.0")
fun <T : Number, S : T?> mod(value1: Expressionable<@Exact T>, value2: S): Expression<T> {
return Expressions.mod(
Expressions.parentheses(value1.toExpression()),
Expressions.parentheses(Expressions.value(value2)),
)
}

/**
* Creates an expression that represents the modulo of values.
*
* This is the same as ```(value1) % (value2)```.
*/
@JvmName("modWithParentheses")
@SinceJdsl("3.4.0")
fun <T : Number, S : T> mod(value1: Expressionable<@Exact T>, value2: Expressionable<S>): Expression<T> {
return Expressions.mod(
Expressions.parentheses(value1.toExpression()),
Expressions.parentheses(value2.toExpression()),
)
}

/**
* Creates an expression that represents the modulo of values.
*
* This is the same as ```(value1) % (value2)```.
*/
@SinceJdsl("3.4.0")
fun <T : Number, S : T?> Expressionable<@Exact T>.mod(value: S): Expression<T> {
return Expressions.mod(this.toExpression(), Expressions.value(value))
}

/**
* Creates an expression that represents the modulo of values.
*
* This is the same as ```(value1) % (value2)```.
*/
@SinceJdsl("3.4.0")
fun <T : Number, S : T> Expressionable<@Exact T>.mod(value: Expressionable<S>): Expression<T> {
return Expressions.mod(this.toExpression(), value.toExpression())
}

/**
* Creates an expression that represents the count of non-null values.
*
Expand Down Expand Up @@ -2896,4 +2944,8 @@ open class Jpql : JpqlDsl {
fun <T : Any> deleteFrom(entity: Entityable<T>): DeleteQueryWhereStep<T> {
return DeleteQueryDsl(entity.toEntity())
}

/**
*
*/
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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
import java.math.BigDecimal

class ModDslTest : WithAssertions {
private val bigDecimal1 = BigDecimal.valueOf(100)

private val bigDecimalExpression1 = Expressions.value(bigDecimal1)

@Test
fun `mod() with a property and a bigDecimal`() {
// when
val expression1 = queryPart {
path(Book::price).mod(bigDecimal1)
}

val expression2 = queryPart {
mod(path(Book::price), bigDecimal1)
}

val actual1: Expression<BigDecimal> = expression1 // for type check
val actual2: Expression<BigDecimal> = expression2 // for type check

// then
val expected1 = Expressions.mod(
value1 = Paths.path(Book::price),
value2 = Expressions.value(bigDecimal1),
)

val expected2 = Expressions.mod(
value1 = Expressions.parentheses(Paths.path(Book::price)),
value2 = Expressions.parentheses(Expressions.value(bigDecimal1)),
)

assertThat(actual1).isEqualTo(expected1)
assertThat(actual2).isEqualTo(expected2)
}

@Test
fun `mod() with a property and a bigDecimal expression`() {
// when
val expression1 = queryPart {
path(Book::price).mod(bigDecimalExpression1)
}

val expression2 = queryPart {
mod(path(Book::price), bigDecimalExpression1)
}

val actual1: Expression<BigDecimal> = expression1 // for type check
val actual2: Expression<BigDecimal> = expression2 // for type check

// then
val expected1 = Expressions.mod(
value1 = Paths.path(Book::price),
value2 = bigDecimalExpression1,
)

val expected2 = Expressions.mod(
value1 = Expressions.parentheses(Paths.path(Book::price)),
value2 = Expressions.parentheses(bigDecimalExpression1),
)

assertThat(actual1).isEqualTo(expected1)
assertThat(actual2).isEqualTo(expected2)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlLower
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlMax
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlMin
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlMinus
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlModulo
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlNew
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlNull
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlNullIf
Expand Down Expand Up @@ -206,6 +207,16 @@ object Expressions {
return JpqlDivide(value1, value2)
}

/**
* Creates an expression that represents the modulo of values.
*
* This is the same as ```(value1) % (value2)```.
*/
@SinceJdsl("3.0.0")
fun <T : Number, S : T> mod(value1: Expression<T>, value2: Expression<S>): Expression<T> {
return JpqlModulo(value1, value2)
}

/**
* Creates an expression that represents the count of non-null values.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl

import com.linecorp.kotlinjdsl.Internal
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression

/**
* Expression that mods [value1] by [value2].
*/
@Internal
data class JpqlModulo<T : Number> internal constructor(
val value1: Expression<*>,
val value2: Expression<*>,
) : Expression<T>
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlLower
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlMax
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlMin
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlMinus
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlModulo
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlNew
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlNull
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlNullIf
Expand Down Expand Up @@ -332,6 +333,20 @@ class ExpressionsTest : WithAssertions {
assertThat(actual).isEqualTo(expected)
}

@Test
fun mod() {
// when
val actual = Expressions.mod(intExpression1, intExpression2)

// then
val expected = JpqlModulo<Int>(
intExpression1,
intExpression2,
)

assertThat(actual).isEqualTo(expected)
}

@ParameterizedTest
@ValueSource(booleans = [true, false])
fun count(distinct: Boolean) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.linecorp.kotlinjdsl.render.jpql.serializer.impl

import com.linecorp.kotlinjdsl.Internal
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlModulo
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 JpqlModuloSerializer : JpqlSerializer<JpqlModulo<*>> {
override fun handledType(): KClass<JpqlModulo<*>> {
return JpqlModulo::class
}

override fun serialize(part: JpqlModulo<*>, writer: JpqlWriter, context: RenderContext) {
val delegate = context.getValue(JpqlRenderSerializer)

delegate.serialize(part.value1, writer, context)

writer.write(" ")
writer.write("%")
writer.write(" ")

delegate.serialize(part.value2, writer, context)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.linecorp.kotlinjdsl.render.jpql.serializer.impl

import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlModulo
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 JpqlModuloSerializerTest : WithAssertions {
private val sut = JpqlModuloSerializer()

@MockK
private lateinit var writer: JpqlWriter

@MockK
private lateinit var serializer: JpqlRenderSerializer

private val expression1 = Paths.path(Book::price)
private val expression2 = Paths.path(Book::salePrice)

@Test
fun handledType() {
// when
val actual = sut.handledType()

// then
assertThat(actual).isEqualTo(JpqlModulo::class)
}

@Test
fun serialize() {
// given
val part = Expressions.mod(
expression1,
expression2,
)
val context = TestRenderContext(serializer)

// when
sut.serialize(part as JpqlModulo<*>, writer, context)

// then
verifySequence {
serializer.serialize(expression1, writer, context)
writer.write(" ")
writer.write("%")
writer.write(" ")
serializer.serialize(expression2, writer, context)
}
}
}

0 comments on commit b158e32

Please sign in to comment.