Skip to content

Commit

Permalink
Merge pull request #521 from jbl428/feat/kfunction-dsl
Browse files Browse the repository at this point in the history
Add path method for `KFunction1`
  • Loading branch information
shouwn authored Nov 12, 2023
2 parents f99c624 + 74ab44d commit 6970af2
Show file tree
Hide file tree
Showing 16 changed files with 521 additions and 18 deletions.
71 changes: 71 additions & 0 deletions docs/en/jpql-with-kotlin-jdsl/paths.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,77 @@ entity(Book::class, "b").path(Book::isbn).path(Isbn::value)
entity(Book::class, "b")(Book::isbn)(Isbn::value)
```

## Java entity

`path()` and `invoke()` can take `KProperty1` or `KFuction1` as an argument.
`KFunction1` is useful when you use Java entity with private property and public getter.

```java
@Entity
public class Book {
@Id
private Long id;

private String title;

public String getTitle() {
return title;
}
}
```

```kotlin
// compile error
path(Book::title)

// Book.title
path(Book::getTitle)
```

Kotlin JDSL infers the property name from the getter with the following rules:

- If the name starts with `is`, use the name as it is.
- If the name starts with `get`, remove `get` and change the first letter to lowercase.
- Otherwise, use the name as it is.

```kotlin
// Book.isAvailable
path(Book::isAvailable)

// Book.available
path(Book::getAvailable)
```

If you want to use your own rule instead of the above rules, you can implement `JpqlPropertyIntrospector` and provide it to `RenderContext`.
See [Custom DSL](./custom-dsl.md) for more details.
If you are using Spring, see [Spring supports](./spring-supports.md) also.

```kotlin
class MyIntrospector : JpqlPropertyIntrospector() {
override fun introspect(property: KCallable<*>): JpqlPropertyDescription? {
if (property is KFunction1<*, *>) {
// resolve a name with your own rule
val name = ...
return MyProperty(name)
}

return null
}

private data class MyProperty(
override val name: String,
) : JpqlPropertyDescription
}

val myModule = object : JpqlRenderModule {
override fun setupModule(context: JpqlRenderModule.SetupContext) {
context.prependIntrospector(MyIntrospector())
}
}

val myContext = JpqlRenderContext().registerModule(myModule)
```

## Expression

`Path` can be used as [`Expression`](expressions.md), such as in a [select clause](statements.md#select-clause) or [predicate](predicates.md).
Expand Down
2 changes: 1 addition & 1 deletion docs/en/jpql-with-kotlin-jdsl/spring-supports.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Kotlin JDSL supports Spring Boot AutoConfigure.
If you have Spring Boot and `com.linecorp.kotlin-jdsl:spring-data-jpa-support` or `com.linecorp.kotlin-jdsl:spring-batch-support` dependency together, the `JpqlRenderContext` bean is created by AutoConfiguration.

If you declare your `JpqlSerializer` as a bean, it will be included with the `JpqlRenderContext` bean.
If you declare your `JpqlSerializer` or `JpqlIntrospector` as a bean, it will be included with the `JpqlRenderContext` bean.

## Spring Data Repository

Expand Down
71 changes: 71 additions & 0 deletions docs/ko/jpql-with-kotlin-jdsl/paths.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,77 @@ entity(Book::class, "b").path(Book::isbn).path(Isbn::value)
entity(Book::class, "b")(Book::isbn)(Isbn::value)
```

## Java entity

`path()``invoke()``KProperty1` 또는 `KFuction1`를 인자로 받습니다.
`KFunction1`의 경우, getter만 public인 Java로 선언한 entity를 사용할 때 유용합니다.

```java
@Entity
public class Book {
@Id
private Long id;

private String title;

public String getTitle() {
return title;
}
}
```

```kotlin
// compile error
path(Book::title)

// Book.title
path(Book::getTitle)
```

Kotlin JDSL은 getter 이름에서 프로퍼티 이름을 추론하기 위해 다음 규칙을 따릅니다.

- `is`로 시작하는 경우 이름 그대로 사용합니다.
- `get`으로 시작하는 경우 `get`을 제거하고 이후 첫 글자를 소문자로 변경합니다.
- 그 외의 경우, 이름 그대로 사용합니다.

```kotlin
// Book.isAvailable
path(Book::isAvailable)

// Book.available
path(Book::getAvailable)
```

위 규칙 대신 나만의 규칙을 사용하고 싶다면, `JpqlPropertyIntrospector`를 구현하고 이를 이를 `RenderContext`에 추가해야 합니다.
더 자세한 내용은 [Custom DSL](./custom-dsl.md)을 참고하세요.
Spring을 사용하고 있다면 [Spring supports](./spring-supports.md)도 참고하세요.

```kotlin
class MyIntrospector : JpqlPropertyIntrospector() {
override fun introspect(property: KCallable<*>): JpqlPropertyDescription? {
if (property is KFunction1<*, *>) {
// 나만의 규칙으로 이름을 추론합니다
val name = ...
return MyProperty(name)
}

return null
}

private data class MyProperty(
override val name: String,
) : JpqlPropertyDescription
}

val myModule = object : JpqlRenderModule {
override fun setupModule(context: JpqlRenderModule.SetupContext) {
context.prependIntrospector(MyIntrospector())
}
}

val myContext = JpqlRenderContext().registerModule(myModule)
```

## Expression

`Path`[select clause](statements.md#select-clause)[predicate](predicates.md) 등에서 [`Expression`](expressions.md)으로 사용될 수 있습니다.
Expand Down
2 changes: 1 addition & 1 deletion docs/ko/jpql-with-kotlin-jdsl/spring-supports.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Kotlin JDSL은 Spring Boot AutoConfigure를 지원합니다.
만약 프로젝트가 Spring Boot와 `com.linecorp.kotlin-jdsl:spring-data-jpa-support` dependency를 같이 포함하고 있다면, `JpqlRenderContext` bean이 `KotlinJdslAutoConfiguration`를 통해 자동 생성 됩니다.

만약 `JpqlSerializer`를 bean으로 선언했다면, 자동으로 `JpqlRenderContext`에 해당 bean이 포함됩니다.
만약 `JpqlSerializer` 또는 `JpqlIntrospector`를 bean으로 선언했다면, 자동으로 `JpqlRenderContext`에 해당 bean이 포함됩니다.

## Spring Data Repository

Expand Down
42 changes: 42 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 @@ -41,6 +41,7 @@ import java.math.BigDecimal
import java.math.BigInteger
import kotlin.internal.Exact
import kotlin.reflect.KClass
import kotlin.reflect.KFunction1
import kotlin.reflect.KProperty1

/**
Expand Down Expand Up @@ -194,6 +195,15 @@ open class Jpql : JpqlDsl {
return Paths.path(property)
}

/**
* Creates a path expression with the property.
* The path starts from the entity which is the owner of the property.
*/
@SinceJdsl("3.1.0")
fun <T : Any, V> path(getter: KFunction1<T, @Exact V>): Path<V & Any> {
return Paths.path(getter)
}

/**
* Creates a path expression with the entity and property.
*/
Expand All @@ -202,6 +212,14 @@ open class Jpql : JpqlDsl {
return Paths.path(this.toEntity(), property)
}

/**
* Creates a path expression with the entity and property.
*/
@SinceJdsl("3.1.0")
fun <T : Any, V> Entityable<T>.path(getter: KFunction1<T, @Exact V>): Path<V & Any> {
return Paths.path(this.toEntity(), getter)
}

/**
* Creates a path expression with the path and property.
*/
Expand All @@ -210,6 +228,14 @@ open class Jpql : JpqlDsl {
return Paths.path(this.toPath(), property)
}

/**
* Creates a path expression with the path and property.
*/
@SinceJdsl("3.1.0")
fun <T : Any, V> Pathable<T>.path(getter: KFunction1<T, @Exact V>): Path<V & Any> {
return Paths.path(this.toPath(), getter)
}

/**
* Creates a path expression with the entity and property.
*/
Expand All @@ -218,6 +244,14 @@ open class Jpql : JpqlDsl {
return Paths.path(this.toEntity(), property)
}

/**
* Creates a path expression with the entity and property.
*/
@SinceJdsl("3.1.0")
operator fun <T : Any, V> Entityable<T>.invoke(getter: KFunction1<T, @Exact V>): Path<V & Any> {
return Paths.path(this.toEntity(), getter)
}

/**
* Creates a path expression with the path and property.
*/
Expand All @@ -226,6 +260,14 @@ open class Jpql : JpqlDsl {
return Paths.path(this.toPath(), property)
}

/**
* Creates a path expression with the path and property.
*/
@SinceJdsl("3.1.0")
operator fun <T : Any, V> Pathable<T>.invoke(getter: KFunction1<T, @Exact V>): Path<V & Any> {
return Paths.path(this.toPath(), getter)
}

/**
* Creates an aliased expression with the alias expression.
* The aliased expression can be referenced by the alias expression.
Expand Down
Loading

0 comments on commit 6970af2

Please sign in to comment.