Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement power operator #651

Merged
merged 11 commits into from
Mar 12, 2024
8 changes: 4 additions & 4 deletions docs/en/jpql-with-kotlin-jdsl/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ locate("Book", path(Book::title))
* FLOOR (floor)
* INDEX (index)
* LN (ln)
* MOD (mod)
* POWER (power)
* SIGN (sign)
* SQRT (sqrt)
* ROUND (round)
Expand All @@ -247,6 +249,8 @@ ln(path(Book::price))

mod(path(Employee::age), 3)

power(path(Book::verticalLength), 2)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the Book::verticalLength property new?
It would be better to use an existing property (e.g. Employee::age)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, right, I thought about adding that field, but it's not currently there.
I know someone added an age field recently haha I'll replace it with that.


sign(path(Book::price))

sqrt(path(Book::price))
Expand All @@ -256,10 +260,6 @@ round(path(Book::price), 2)
size(path(Book::authors))
```

| Function | DSL function |
|----------|--------------|
| POWER | not yet |

### Datetime functions

* CURRENT\_DATE (currentDate)
Expand Down
8 changes: 4 additions & 4 deletions docs/ko/jpql-with-kotlin-jdsl/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ locate("Book", path(Book::title))
* FLOOR (floor)
* INDEX (index)
* LN (ln)
* MOD (mod)
* POWER (power)
* SIGN (sign)
* SQRT (sqrt)
* ROUND (round)
Expand All @@ -243,6 +245,8 @@ ln(path(Book::price))

mod(path(Employee::age), 3)

power(path(Book::verticalLength), 2)

sign(path(Book::price))

sqrt(path(Book::price))
Expand All @@ -252,10 +256,6 @@ round(path(Book::price), 2)
size(path(Book::authors))
```

| Function | DSL function |
|----------|--------------|
| POWER | not yet |

### Datetime functions

* CURRENT\_DATE (currentDate)
Expand Down
96 changes: 64 additions & 32 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 @@ -646,6 +646,70 @@ open class Jpql : JpqlDsl {
return Expressions.ln(value.toExpression())
}

/**
* Creates an expression that represents the mod of values.
*/
@SinceJdsl("3.4.0")
fun <T : Any> mod(expr: KProperty1<T, Int>, value: Int): Expression<Int> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

It appears that the mod has not been implemented, but the commit appears to have been distorted.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

image

Oops, I had them in the order they appear in the official documentation, which seems to have caused some confusion. I've now fixed them back to their original position.

return Expressions.mod(Paths.path(expr), Expressions.value(value))
}

/**
* Creates an expression that represents the mod of values.
*/
@SinceJdsl("3.4.0")
fun mod(value1: Expressionable<Int>, value2: Int): Expression<Int> {
return Expressions.mod(value1.toExpression(), Expressions.value(value2))
}

/**
* Creates an expression that represents the mod of values.
*/
@SinceJdsl("3.4.0")
fun <T : Any> mod(expr: KProperty1<T, Int>, value: Expression<Int>): Expression<Int> {
return Expressions.mod(Paths.path(expr), value.toExpression())
}

/**
* Creates an expression that represents the mod of values.
*/
@SinceJdsl("3.4.0")
fun mod(value1: Expressionable<Int>, value2: Expressionable<Int>): Expression<Int> {
return Expressions.mod(value1.toExpression(), value2.toExpression())
}

/**
* Create expression that calculates the powering of a numeric [base] to a specified [exponent].
Copy link
Member

Choose a reason for hiding this comment

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

How about writing something like this?

Suggested change
* Create expression that calculates the powering of a numeric [base] to a specified [exponent].
* Create an expression that represents the power of [base] and [exponent].

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thank you. The sentence below looks easier to read.

*/
@SinceJdsl("3.4.0")
fun <T : Any, V : Number> power(base: KProperty1<T, @Exact V>, exponent: Int): Expression<V> {
return Expressions.power(Paths.path(base), Expressions.value(exponent))
}

/**
* Create expression that calculates the powering of a numeric [base] to a specified [exponent].
*/
@SinceJdsl("3.4.0")
fun <T : Number> power(base: Expressionable<T>, exponent: Int): Expression<T> {
Copy link
Contributor

Choose a reason for hiding this comment

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

powerfunction

Could you please write a method based on the specification in jakarta specification?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wow, I love doing reviews based on such solid knowledge. lol Thank you :)

I feel like this allows me to get up close and personal with the Hibernate spec.

Copy link
Member

Choose a reason for hiding this comment

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

I would recommend following the jakarta spec unless you have a special use case. This is because if Hibernate and EclipseLink have different specifications, you may not be able to decide which one to follow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh... Thank you.
I gained a lot of knowledge while implementing this power.
I also understand now why the package was changed from javax to jakrta.

References

return Expressions.power(base.toExpression(), Expressions.value(exponent))
}

/**
* Create expression that calculates the powering of a numeric [base] to a specified [exponent].
*/
@SinceJdsl("3.4.0")
fun <T : Any, V : Number> power(base: KProperty1<T, @Exact V>, exponent: Expression<Int>): Expression<V> {
Copy link
Contributor

Choose a reason for hiding this comment

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

When used as a parameter to a dsl method, it is more appropriate to write it with the Expressionable<> type instead of the Expression<> type

e.g.

Suggested change
fun <T : Any, V : Number> power(base: KProperty1<T, @Exact V>, exponent: Expression<Int>): Expression<V> {
fun <T : Any, V : Number> power(base: KProperty1<T, @Exact V>, exponent: Expressionable<Int>): Expression<V> {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it to accept a wider range of material types?? reflected ! :)

Copy link
Member

Choose a reason for hiding this comment

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

I also see this in other code that I haven't checked. I'll fix it in one shot later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for checking it out.
I'm actually pretty much done with it, but I've had a little bit of a break, so I haven't had a chance to do a final review and PR.
I'll probably PR it really soon.
Thanks.

return Expressions.power(Paths.path(base), exponent.toExpression())
}

/**
* Create expression that calculates the powering of a numeric [base] to a specified [exponent].
*/
@SinceJdsl("3.4.0")
fun <T : Number> power(base: Expressionable<T>, exponent: Expressionable<Int>): Expression<T> {
return Expressions.power(base.toExpression(), exponent.toExpression())
}

/**
* Creates an expression that represents the sign of value.
*
Expand Down Expand Up @@ -718,38 +782,6 @@ open class Jpql : JpqlDsl {
return Expressions.round(value.toExpression(), scale.toExpression())
}

/**
* Creates an expression that represents the mod of values.
*/
@SinceJdsl("3.4.0")
fun <T : Any> mod(expr: KProperty1<T, Int>, value: Int): Expression<Int> {
return Expressions.mod(Paths.path(expr), Expressions.value(value))
}

/**
* Creates an expression that represents the mod of values.
*/
@SinceJdsl("3.4.0")
fun mod(value1: Expressionable<Int>, value2: Int): Expression<Int> {
return Expressions.mod(value1.toExpression(), Expressions.value(value2))
}

/**
* Creates an expression that represents the mod of values.
*/
@SinceJdsl("3.4.0")
fun <T : Any> mod(expr: KProperty1<T, Int>, value: Expression<Int>): Expression<Int> {
return Expressions.mod(Paths.path(expr), value.toExpression())
}

/**
* Creates an expression that represents the mod of values.
*/
@SinceJdsl("3.4.0")
fun mod(value1: Expressionable<Int>, value2: Expressionable<Int>): Expression<Int> {
return Expressions.mod(value1.toExpression(), value2.toExpression())
}

/**
* Creates an expression that the number of elements of the collection.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.linecorp.kotlinjdsl.dsl.jpql.expression

import com.linecorp.kotlinjdsl.dsl.jpql.entity.employee.Employee
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 PowerDslTest : WithAssertions {
private val baseExpression = Paths.path(Employee::age)
private val exponentExpression = Expressions.value(2)
private val exponentPrimitive = 2
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems to be done to make the semantics of the variables clearer, but it would be nice to have a consistent style with other existing test code (e.g. ModDslTest.kt)

Suggested change
private val baseExpression = Paths.path(Employee::age)
private val exponentExpression = Expressions.value(2)
private val exponentPrimitive = 2
private val intExpression1 = Paths.path(Employee::age)
private val intExpression2 = Expressions.value(2)
private val int1 = 2

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Consistency rather than semantics, I see :)


@Test
fun `power() with a property`() {
// when
val expression1 = queryPart {
power(Employee::age, exponentPrimitive)
}.toExpression()

val expression2 = queryPart {
power(Employee::age, exponentExpression)
}.toExpression()

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

// then
val expected1 = Expressions.power(
base = Paths.path(Employee::age),
exponent = Expressions.value(exponentPrimitive),
)

val expected2 = Expressions.power(
base = Paths.path(Employee::age),
exponent = exponentExpression,
)

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

@Test
fun `power() with a expression`() {
// when
val expression1 = queryPart {
power(baseExpression, exponentPrimitive)
}.toExpression()

val expression2 = queryPart {
power(baseExpression, exponentExpression)
}.toExpression()

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

// then
val expected1 = Expressions.power(
base = baseExpression,
exponent = Expressions.value(exponentPrimitive),
)

val expected2 = Expressions.power(
base = baseExpression,
exponent = exponentExpression,
)

assertThat(actual1).isEqualTo(expected1)
assertThat(actual2).isEqualTo(expected2)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlNullIf
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.JpqlPower
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.JpqlSize
Expand Down Expand Up @@ -277,6 +278,14 @@ object Expressions {
return JpqlLn(value)
}

/**
* Creates expression that calculates the powering of a numeric [base] to a specified [exponent].
*/
@SinceJdsl("3.4.0")
fun <T : Number> power(base: Expression<T>, exponent: Expression<Int>): Expression<T> {
return JpqlPower(base, exponent)
}

/**
* Creates an expression that represents the sign of a numeric value.
*/
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 calculates the powering of a numeric [base] to a specified [exponent].
*/
@Internal
data class JpqlPower<T : Number> internal constructor(
val base: Expression<T>,
val exponent: Expression<Int>,
) : Expression<T>
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlNullIf
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.JpqlPower
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.JpqlSize
Expand Down Expand Up @@ -428,6 +429,20 @@ class ExpressionsTest : WithAssertions {
assertThat(actual).isEqualTo(expected)
}

@Test
fun power() {
// when
val actual = Expressions.power(doubleExpression1, intExpression1)

// then
val expected = JpqlPower(
doubleExpression1,
intExpression1,
)

assertThat(actual).isEqualTo(expected)
}

@Test
fun sign() {
// when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlPathPropertySeria
import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlPathTreatSerializer
import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlPathTypeSerializer
import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlPlusSerializer
import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlPowerSerializer
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
Expand Down Expand Up @@ -370,6 +371,7 @@ private class DefaultModule : JpqlRenderModule {
JpqlPredicateParenthesesSerializer(),
JpqlRoundSerializer(),
JpqlSelectQuerySerializer(),
JpqlPowerSerializer(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please move the position of JpqlPowerSerializer() below JpqlPlusSerializer()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You mean alphabetical order! Got it :)

JpqlSignSerializer(),
JpqlSizeSerializer(),
JpqlSortSerializer(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.linecorp.kotlinjdsl.render.jpql.serializer.impl

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

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

writer.write("POWER")

writer.writeParentheses {
delegate.serialize(part.base, writer, context)

writer.write(",")
writer.write(" ")

delegate.serialize(part.exponent, writer, context)
}
}
}
Loading