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

Add path method for KFunction1 #521

Merged
merged 9 commits into from
Nov 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

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

한국어까지 같이 작성해주셔서 정말 감사합니다. ❤️


`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
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> {
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to put in in the T generic type? I want to include in in something like KProperty1<in T, @Eact V> because an user might want to create a path with the parent type.

Copy link
Member

Choose a reason for hiding this comment

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

It works in the test, but it's probably because the receiver's T is cast to a supertype. This causes the type to be broken when chaining occurs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

when i first put in keyword. IDE show me the following warning.
It might that KFunction1 has in variance unlike KProperty1.

Projection is redundant: the corresponding type parameter of KFunction1 has the same variance

Copy link
Member

Choose a reason for hiding this comment

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

Aha, I see. Thank you for the confirmation.

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