Skip to content

Commit

Permalink
feat: custom predicate
Browse files Browse the repository at this point in the history
  • Loading branch information
shouwn committed Jan 15, 2024
1 parent 7c3070f commit 6cd8b89
Show file tree
Hide file tree
Showing 30 changed files with 852 additions and 84 deletions.
11 changes: 11 additions & 0 deletions benchmark/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion

plugins {
alias(exampleLibs.plugins.kotlin.benchmark)
alias(exampleLibs.plugins.kotlin.allopen)
Expand All @@ -13,6 +15,15 @@ dependencies {
implementation(projects.jpqlRender)
}

kotlin {
jvmToolchain(17)

compilerOptions {
apiVersion = KotlinVersion.KOTLIN_1_9
languageVersion = KotlinVersion.KOTLIN_1_9
}
}

allOpen {
annotation("org.openjdk.jmh.annotations.State")
annotation("jakarta.persistence.Entity")
Expand Down
5 changes: 5 additions & 0 deletions docs/en/jpql-with-kotlin-jdsl/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,11 @@ Call `function()` to create predefined database functions and user-defined datab
function(String::class, "myFunction", path(Book::isbn))
```

{% hint style="info" %}
You may need to register information about the function you want to use with the JPA Provider.
For example, if you are using Hibernate, you need to register a `FunctionContributor`.
{% endhint %}

## Cases

Use `caseWhen()` and `caseValue()` to build cases.
Expand Down
23 changes: 23 additions & 0 deletions docs/en/jpql-with-kotlin-jdsl/predicates.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,26 @@ path(Employee::departments).isEmpty()

path(Employee::departments).isNotEmpty()
```

## Database function

Call `function()` with `KClass<Boolean>` to create predefined database functions and user-defined database functions.

```kotlin
function(Boolean::class, "myFunction", path(Book::isbn))
```

{% hint style="info" %}
You may need to register information about the function you want to use with the JPA Provider.
For example, if you are using Hibernate, you need to register a `FunctionContributor`.
{% endhint %}

## Custom predicate

Call `customPredicate()` to build a custom predicate.

```kotlin
customPredicate("{0} MEMBER OF {1}", value(author), path(Book::authors))
```

If you frequently use `customPredicate()`, you can create [your own DSL](custom-dsl.md).
5 changes: 5 additions & 0 deletions docs/ko/jpql-with-kotlin-jdsl/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@ DB 함수나 사용자 정의 함수를 만들기 위해, `function()`을 사용
function(String::class, "myFunction", path(Book::isbn))
```

{% hint style="info" %}
사용할 함수의 정보를 JPA 제공자에 등록할 필요가 있을 수 있습니다.
예를 들어 Hibernate를 사용하고 있다면 `FunctionContributor`를 반드시 등록해야 합니다.
{% endhint %}

## Cases

case를 만들기 위해, `caseWhen()``caseValue()`를 사용할 수 있습니다.
Expand Down
23 changes: 23 additions & 0 deletions docs/ko/jpql-with-kotlin-jdsl/predicates.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,26 @@ path(Employee::departments).isEmpty()

path(Employee::departments).isNotEmpty()
```

## Database function

DB 함수나 사용자 정의 함수를 만들기 위해, `KClass<Boolean>`과 함께 `function()`을 사용할 수 있습니다.

```kotlin
function(Boolean::class, "myFunction", path(Book::isbn))
```

{% hint style="info" %}
사용할 함수의 정보를 JPA 제공자에 등록할 필요가 있을 수 있습니다.
예를 들어 Hibernate를 사용하고 있다면 `FunctionContributor`를 반드시 등록해야 합니다.
{% endhint %}

## Custom predicate

커스텀 predicate를 만들기 위해, `customPredicate()`을 사용할 수 있습니다.

```kotlin
customPredicate("{0} MEMBER OF {1}", value(author), path(Book::authors))
```

만약 `customPredicate()`을 많이 사용한다면 [나만의 DSL](custom-dsl.md)을 만드는 것을 고려해보세요.
62 changes: 62 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 @@ -1383,6 +1383,7 @@ open class Jpql : JpqlDsl {
/**
* Creates an expression that represents predefined database functions and user-defined database functions.
*/
@LowPriorityInOverloadResolution
@SinceJdsl("3.0.0")
fun <T : Any> function(type: KClass<T>, name: String, vararg args: Any): Expression<T> {
return Expressions.function(type, name, args.map { Expressions.value(it) })
Expand Down Expand Up @@ -1411,6 +1412,7 @@ open class Jpql : JpqlDsl {
* customExpression(String::class, "CAST({0} AS VARCHAR)", 100)
* ```
*/
@LowPriorityInOverloadResolution
@SinceJdsl("3.0.0")
fun <T : Any> customExpression(type: KClass<T>, template: String, vararg args: Any): Expression<T> {
return Expressions.customExpression(type, template, args.map { Expressions.value(it) })
Expand Down Expand Up @@ -2705,6 +2707,66 @@ open class Jpql : JpqlDsl {
return Predicates.notExists(subquery)
}

/**
* Creates a predicate that represents predefined database functions and user-defined database functions.
*/
@Suppress("UNUSED_PARAMETER")
@LowPriorityInOverloadResolution
@SinceJdsl("3.0.0")
fun function(type: KClass<Boolean>, name: String, vararg args: Any): Predicate {
return Predicates.function(name, args.map { Expressions.value(it) })
}

/**
* Creates a predicate that represents predefined database functions and user-defined database functions.
*/
@Suppress("UNUSED_PARAMETER")
@SinceJdsl("3.0.0")
fun function(type: KClass<Boolean>, name: String, vararg args: Expressionable<*>): Predicate {
return Predicates.function(name, args.map { it.toExpression() })
}

/**
* Creates a predicate that represents the user-defined predicate.
*
* The template for the user-defined predicate can have placeholders.
* Placeholders in template are replaced with the expression in args, matching with index.
*
* ```
* Placeholder: { ArgumentIndex }
* ```
*
* Examples:
* ```
* customPredicate("{0} MEMBER OF {1}", value(author), path(Book::authors))
* ```
*/
@LowPriorityInOverloadResolution
@SinceJdsl("3.3.0")
fun customPredicate(template: String, vararg args: Any): Predicate {
return Predicates.customPredicate(template, args.map { Expressions.value(it) })
}

/**
* Creates a predicate that represents the user-defined predicate.
*
* The template for the user-defined predicate can have placeholders.
* Placeholders in template are replaced with the expression in args, matching with index.
*
* ```
* Placeholder: { ArgumentIndex }
* ```
*
* Examples:
* ```
* customPredicate("{0} MEMBER OF {1}", value(author), path(Book::authors))
* ```
*/
@SinceJdsl("3.3.0")
fun customPredicate(template: String, vararg args: Expressionable<*>): Predicate {
return Predicates.customPredicate(template, args.map { it.toExpression() })
}

/**
* Creates a sort that sorts the expression in ascending order.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.linecorp.kotlinjdsl.dsl.jpql.predicate

import com.linecorp.kotlinjdsl.dsl.jpql.queryPart
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions
import com.linecorp.kotlinjdsl.querymodel.jpql.predicate.Predicate
import com.linecorp.kotlinjdsl.querymodel.jpql.predicate.Predicates
import org.assertj.core.api.WithAssertions
import org.junit.jupiter.api.Test

class CustomPredicateDslTest : WithAssertions {
private val template1: String = "template1"

private val stringExpression1 = Expressions.value("string1")
private val stringExpression2 = Expressions.value("string2")

private val string1 = "string1"
private val string2 = "string2"

@Test
fun `customPredicate() with strings`() {
// when
val predicate = queryPart {
customPredicate(template1, string1, string2)
}

val actual: Predicate = predicate // for type check

// then
val expected = Predicates.customPredicate(
template1,
listOf(
Expressions.value(string1),
Expressions.value(string2),
),
)

assertThat(actual).isEqualTo(expected)
}

@Test
fun `customPredicate() with string expressions`() {
// when
val predicate = queryPart {
customPredicate(template1, stringExpression1, stringExpression2)
}

val actual: Predicate = predicate // for type check

// then
val expected = Predicates.customPredicate(
template1,
listOf(
stringExpression1,
stringExpression2,
),
)

assertThat(actual).isEqualTo(expected)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.linecorp.kotlinjdsl.dsl.jpql.predicate

import com.linecorp.kotlinjdsl.dsl.jpql.queryPart
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions
import com.linecorp.kotlinjdsl.querymodel.jpql.predicate.Predicate
import com.linecorp.kotlinjdsl.querymodel.jpql.predicate.Predicates
import org.assertj.core.api.WithAssertions
import org.junit.jupiter.api.Test

class FunctionDslTest : WithAssertions {
private val name1: String = "name1"

private val stringExpression1 = Expressions.value("string1")
private val stringExpression2 = Expressions.value("string2")

private val string1 = "string1"
private val string2 = "string2"

@Test
fun `function() with strings`() {
// when
val predicate = queryPart {
function(Boolean::class, name1, string1, string2)
}

val actual: Predicate = predicate // for type check

// then
val expected = Predicates.function(
name1,
listOf(
Expressions.value(string1),
Expressions.value(string2),
),
)

assertThat(actual).isEqualTo(expected)
}

@Test
fun `function() with string expressions`() {
// when
val predicate = queryPart {
function(Boolean::class, name1, stringExpression1, stringExpression2)
}

val actual: Predicate = predicate // for type check

// then
val expected = Predicates.function(
name1,
listOf(
stringExpression1,
stringExpression2,
),
)

assertThat(actual).isEqualTo(expected)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ class H2DBConnectionPoolConfiguration : DefaultSqlClientPoolConfiguration() {
private lateinit var password: String

override fun configure(configuration: MutableMap<Any?, Any?>) {
user = ConfigurationHelper.getString(Settings.USER, configuration)
password = ConfigurationHelper.getString(Settings.PASS, configuration)
user = ConfigurationHelper.getString(Settings.JAKARTA_JDBC_USER, configuration)
password = ConfigurationHelper.getString(Settings.JAKARTA_JDBC_PASSWORD, configuration)

super.configure(configuration)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlDivide
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlEntityType
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlExpression
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlExpressionParentheses
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlFunction
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlFunctionExpression
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlLength
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlLiteral
import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlLocate
Expand Down Expand Up @@ -548,7 +548,7 @@ object Expressions {
*/
@SinceJdsl("3.0.0")
fun <T : Any> function(type: KClass<T>, name: String, args: Iterable<Expression<*>>): Expression<T> {
return JpqlFunction(type, name, args)
return JpqlFunctionExpression(type, name, args)
}

/**
Expand All @@ -563,7 +563,7 @@ object Expressions {
*
* Examples:
* ```
* customExpression(String::class, "CAST({0} AS VARCHAR)", 100)
* Expressions.customExpression(String::class, "CAST({0} AS VARCHAR)", listOf(Paths.path(User::age)))
* ```
*/
@SinceJdsl("3.0.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression
import kotlin.reflect.KClass

@Internal
data class JpqlFunction<T : Any> internal constructor(
data class JpqlFunctionExpression<T : Any> internal constructor(
val type: KClass<T>,
val name: String,
val args: Iterable<Expression<*>>,
Expand Down
Loading

0 comments on commit 6cd8b89

Please sign in to comment.