diff --git a/README.md b/README.md index 89b4e05a5..746029895 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -Visit [the gitbook](https://kotlin-jdsl.gitbook.io/docs/) for more information. +Visit [the gitbook](https://kotlin-jdsl.gitbook.io/snapshot-docs/) for more information. # Kotlin JDSL - + diff --git a/build.gradle.kts b/build.gradle.kts index 96f33fe3d..bb5a8cd1b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,7 +24,7 @@ allprojects { apply(plugin = "signing") group = "com.linecorp.kotlin-jdsl" - version = "3.0.2" + version = "3.1.0-SNAPSHOT" repositories { mavenCentral() diff --git a/docs/en/jpql-with-kotlin-jdsl/README.md b/docs/en/jpql-with-kotlin-jdsl/README.md index 9acee4b53..41f2b21ff 100644 --- a/docs/en/jpql-with-kotlin-jdsl/README.md +++ b/docs/en/jpql-with-kotlin-jdsl/README.md @@ -99,8 +99,8 @@ Every Kotlin JDSL application requires at least the following dependencies: ```kotlin dependencies { - implementation("com.linecorp.kotlin-jdsl:jpql-dsl:3.0.2") - implementation("com.linecorp.kotlin-jdsl:jpql-render:3.0.2") + implementation("com.linecorp.kotlin-jdsl:jpql-dsl:3.1.0-SNAPSHOT") + implementation("com.linecorp.kotlin-jdsl:jpql-render:3.1.0-SNAPSHOT") } ``` @@ -110,8 +110,8 @@ dependencies { ```groovy dependencies { - implementation 'com.linecorp.kotlin-jdsl:jpql-dsl:3.0.2' - implementation 'com.linecorp.kotlin-jdsl:jpql-render:3.0.2' + implementation 'com.linecorp.kotlin-jdsl:jpql-dsl:3.1.0-SNAPSHOT' + implementation 'com.linecorp.kotlin-jdsl:jpql-render:3.1.0-SNAPSHOT' } ``` @@ -125,12 +125,12 @@ dependencies { com.linecorp.kotlin-jdsl jpql-dsl - 3.0.2 + 3.1.0-SNAPSHOT com.linecorp.kotlin-jdsl jpql-render - 3.0.2 + 3.1.0-SNAPSHOT ``` diff --git a/docs/en/jpql-with-kotlin-jdsl/expressions.md b/docs/en/jpql-with-kotlin-jdsl/expressions.md index 8aaba5a37..c9bc26219 100644 --- a/docs/en/jpql-with-kotlin-jdsl/expressions.md +++ b/docs/en/jpql-with-kotlin-jdsl/expressions.md @@ -195,7 +195,7 @@ Kotlin JDSL provides functions to support built-in functions in JPA. |-----------|--------------| | CONCAT | not yet | | SUBSTRING | not yet | -| TRIM | not yet | +| TRIM | support | | LOWER | support | | UPPER | support | | LENGTH | support | diff --git a/docs/en/jpql-with-kotlin-jdsl/paths.md b/docs/en/jpql-with-kotlin-jdsl/paths.md index 8d1828479..76e145985 100644 --- a/docs/en/jpql-with-kotlin-jdsl/paths.md +++ b/docs/en/jpql-with-kotlin-jdsl/paths.md @@ -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). diff --git a/docs/en/jpql-with-kotlin-jdsl/spring-supports.md b/docs/en/jpql-with-kotlin-jdsl/spring-supports.md index cbef7f07d..52389caed 100644 --- a/docs/en/jpql-with-kotlin-jdsl/spring-supports.md +++ b/docs/en/jpql-with-kotlin-jdsl/spring-supports.md @@ -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 @@ -47,7 +47,7 @@ So if you want to use the features of Kotlin JDSL in `@DataJpaTest`, you need to ## Spring Batch Spring Batch provides `JpaPagingItemReader` and `JpaCursorItemReader` for querying data with JPQL. -Kotlin JDSL provides `KotlinJdslQueryProvider` so that a JPQL query created in DSL can be executed on it. +Kotlin JDSL provides `KotlinJdslQueryProvider` so that a JPQL query created in DSL can be executed on them. ```kotlin @Auwoired diff --git a/docs/ko/jpql-with-kotlin-jdsl/README.md b/docs/ko/jpql-with-kotlin-jdsl/README.md index 633898f1c..95c16bd1f 100644 --- a/docs/ko/jpql-with-kotlin-jdsl/README.md +++ b/docs/ko/jpql-with-kotlin-jdsl/README.md @@ -100,8 +100,8 @@ Kotlin JDSL을 실행시키기 위해서는 다음 dependency들이 필수로 ```kotlin dependencies { - implementation("com.linecorp.kotlin-jdsl:jpql-dsl:3.0.2") - implementation("com.linecorp.kotlin-jdsl:jpql-render:3.0.2") + implementation("com.linecorp.kotlin-jdsl:jpql-dsl:3.1.0-SNAPSHOT") + implementation("com.linecorp.kotlin-jdsl:jpql-render:3.1.0-SNAPSHOT") } ``` @@ -111,8 +111,8 @@ dependencies { ```groovy dependencies { - implementation 'com.linecorp.kotlin-jdsl:jpql-dsl:3.0.2' - implementation 'com.linecorp.kotlin-jdsl:jpql-render:3.0.2' + implementation 'com.linecorp.kotlin-jdsl:jpql-dsl:3.1.0-SNAPSHOT' + implementation 'com.linecorp.kotlin-jdsl:jpql-render:3.1.0-SNAPSHOT' } ``` @@ -126,12 +126,12 @@ dependencies { com.linecorp.kotlin-jdsl jpql-dsl - 3.0.2 + 3.1.0-SNAPSHOT com.linecorp.kotlin-jdsl jpql-render - 3.0.2 + 3.1.0-SNAPSHOT ``` diff --git a/docs/ko/jpql-with-kotlin-jdsl/expressions.md b/docs/ko/jpql-with-kotlin-jdsl/expressions.md index 6b3da9f8e..5cf9a99d7 100644 --- a/docs/ko/jpql-with-kotlin-jdsl/expressions.md +++ b/docs/ko/jpql-with-kotlin-jdsl/expressions.md @@ -194,7 +194,7 @@ Kotlin JDSL은 JPA에서 제공하는 여러 함수들을 지원하기 위함 |-----------|--------------| | CONCAT | not yet | | SUBSTRING | not yet | -| TRIM | not yet | +| TRIM | support | | LOWER | support | | UPPER | support | | LENGTH | support | diff --git a/docs/ko/jpql-with-kotlin-jdsl/paths.md b/docs/ko/jpql-with-kotlin-jdsl/paths.md index 53d651403..a55a7d1d7 100644 --- a/docs/ko/jpql-with-kotlin-jdsl/paths.md +++ b/docs/ko/jpql-with-kotlin-jdsl/paths.md @@ -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)으로 사용될 수 있습니다. diff --git a/docs/ko/jpql-with-kotlin-jdsl/spring-supports.md b/docs/ko/jpql-with-kotlin-jdsl/spring-supports.md index 1121eb159..b33b4930d 100644 --- a/docs/ko/jpql-with-kotlin-jdsl/spring-supports.md +++ b/docs/ko/jpql-with-kotlin-jdsl/spring-supports.md @@ -3,9 +3,9 @@ ## Spring Boot AutoConfigure Kotlin JDSL은 Spring Boot AutoConfigure를 지원합니다. -만약 프로젝트가 Spring Boot와 `com.linecorp.kotlin-jdsl:spring-data-jpa-support` dependency를 같이 포함하고 있다면, `JpqlRenderContext` bean이 `KotlinJdslAutoConfiguration`를 통해 자동 생성 됩니다. +만약 프로젝트가 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 @@ -46,7 +46,7 @@ bookRepository.findPage(pageable) { ## Spring Batch SpringBatch는 JPQL로 쿼리를 할 수 있도록 `JpaPagingItemReader`와 `JpaCursorItemReader`를 제공합니다. -Kotlin JDSL은 DSL로 생성된 JPQL 쿼리가 JpqReader들에서 실행될 수 있도록 `KotlinJdslQueryProvider`을 제공합니다. +Kotlin JDSL은 DSL로 생성된 JPQL 쿼리가 위 ItemReader들에서 실행될 수 있도록 `KotlinJdslQueryProvider`를 제공합니다. ```kotlin @Auwoired diff --git a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/Jpql.kt b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/Jpql.kt index af755fd75..1f60f1fe8 100644 --- a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/Jpql.kt +++ b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/Jpql.kt @@ -5,8 +5,13 @@ import com.linecorp.kotlinjdsl.dsl.jpql.delete.DeleteQueryWhereStep import com.linecorp.kotlinjdsl.dsl.jpql.delete.impl.DeleteQueryDsl import com.linecorp.kotlinjdsl.dsl.jpql.expression.CaseThenFirstStep import com.linecorp.kotlinjdsl.dsl.jpql.expression.CaseValueWhenFirstStep +import com.linecorp.kotlinjdsl.dsl.jpql.expression.TrimFromStep import com.linecorp.kotlinjdsl.dsl.jpql.expression.impl.CaseThenFirstStepDsl import com.linecorp.kotlinjdsl.dsl.jpql.expression.impl.CaseValueWhenFirstStepDsl +import com.linecorp.kotlinjdsl.dsl.jpql.expression.impl.TrimBothFromStepDsl +import com.linecorp.kotlinjdsl.dsl.jpql.expression.impl.TrimFromStepDsl +import com.linecorp.kotlinjdsl.dsl.jpql.expression.impl.TrimLeadingFromStepDsl +import com.linecorp.kotlinjdsl.dsl.jpql.expression.impl.TrimTrailingFromStepDsl import com.linecorp.kotlinjdsl.dsl.jpql.join.AssociationJoinOnStep import com.linecorp.kotlinjdsl.dsl.jpql.join.JoinOnStep import com.linecorp.kotlinjdsl.dsl.jpql.join.impl.AssociationFetchJoinDsl @@ -40,7 +45,9 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.sort.Sort import java.math.BigDecimal import java.math.BigInteger import kotlin.internal.Exact +import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KClass +import kotlin.reflect.KFunction1 import kotlin.reflect.KProperty1 /** @@ -194,6 +201,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 path(getter: KFunction1): Path { + return Paths.path(getter) + } + /** * Creates a path expression with the entity and property. */ @@ -202,6 +218,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 Entityable.path(getter: KFunction1): Path { + return Paths.path(this.toEntity(), getter) + } + /** * Creates a path expression with the path and property. */ @@ -210,6 +234,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 Pathable.path(getter: KFunction1): Path { + return Paths.path(this.toPath(), getter) + } + /** * Creates a path expression with the entity and property. */ @@ -218,6 +250,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 Entityable.invoke(getter: KFunction1): Path { + return Paths.path(this.toEntity(), getter) + } + /** * Creates a path expression with the path and property. */ @@ -226,6 +266,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 Pathable.invoke(getter: KFunction1): Path { + return Paths.path(this.toPath(), getter) + } + /** * Creates an aliased expression with the alias expression. * The aliased expression can be referenced by the alias expression. @@ -1042,6 +1090,108 @@ open class Jpql : JpqlDsl { return Expressions.type(path.toPath()) } + /** + * Creates an expression that represents a string with the whitespaces all trimmed + * from the both sides of the string. + */ + @SinceJdsl("3.1.0") + fun trim(value: String): Expression { + return Expressions.trim(value = Expressions.value(value)) + } + + /** + * Creates an expression that represents a string with the whitespaces all trimmed + * from the both sides of the string. + */ + @SinceJdsl("3.1.0") + fun trim(value: Expressionable): Expression { + return Expressions.trim(value = value.toExpression()) + } + + /** + * Creates an expression that represents a string with the specified characters all trimmed + * from the both sides of the string. + * If the character is not specified, it will be assumed to be whitespace. + */ + @LowPriorityInOverloadResolution + @SinceJdsl("3.1.0") + fun trim(character: Char? = null): TrimFromStep { + return TrimFromStepDsl(character?.let { Expressions.value(it) }) + } + + /** + * Creates an expression that represents a string with the specified characters all trimmed + * from the both sides of the string. + * If the character is not specified, it will be assumed to be whitespace. + */ + @SinceJdsl("3.1.0") + fun trim(character: Expressionable? = null): TrimFromStep { + return TrimFromStepDsl(character?.toExpression()) + } + + /** + * Creates an expression that represents a string with the specified characters all trimmed + * from the leading side of the string. + * If the character is not specified, it will be assumed to be whitespace. + */ + @LowPriorityInOverloadResolution + @SinceJdsl("3.1.0") + fun trimLeading(character: Char? = null): TrimFromStep { + return TrimLeadingFromStepDsl(character?.let { Expressions.value(it) }) + } + + /** + * Creates an expression that represents a string with the specified characters all trimmed + * from the leading side of the string. + * If the character is not specified, it will be assumed to be whitespace. + */ + @SinceJdsl("3.1.0") + fun trimLeading(character: Expressionable? = null): TrimFromStep { + return TrimLeadingFromStepDsl(character?.toExpression()) + } + + /** + * Creates an expression that represents a string with the specified characters all trimmed + * from the trailing side of the string. + * If the character is not specified, it will be assumed to be whitespace. + */ + @LowPriorityInOverloadResolution + @SinceJdsl("3.1.0") + fun trimTrailing(character: Char? = null): TrimFromStep { + return TrimTrailingFromStepDsl(character?.let { Expressions.value(it) }) + } + + /** + * Creates an expression that represents a string with the specified characters all trimmed + * from the trailing side of the string. + * If the character is not specified, it will be assumed to be whitespace. + */ + @SinceJdsl("3.1.0") + fun trimTrailing(character: Expressionable? = null): TrimFromStep { + return TrimTrailingFromStepDsl(character?.toExpression()) + } + + /** + * Creates an expression that represents a string with the specified characters all trimmed + * from the both sides of the string. + * If the character is not specified, it will be assumed to be whitespace. + */ + @LowPriorityInOverloadResolution + @SinceJdsl("3.1.0") + fun trimBoth(character: Char? = null): TrimFromStep { + return TrimBothFromStepDsl(character?.let { Expressions.value(it) }) + } + + /** + * Creates an expression that represents a string with the specified characters all trimmed + * from the both sides of the string. + * If the character is not specified, it will be assumed to be whitespace. + */ + @SinceJdsl("3.1.0") + fun trimBoth(character: Expressionable? = null): TrimFromStep { + return TrimBothFromStepDsl(character?.toExpression()) + } + /** * Creates an expression that represents the string in uppercase. */ diff --git a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/TrimFromStep.kt b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/TrimFromStep.kt new file mode 100644 index 000000000..9403cac00 --- /dev/null +++ b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/TrimFromStep.kt @@ -0,0 +1,19 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression + +import com.linecorp.kotlinjdsl.SinceJdsl +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressionable + +@SinceJdsl("3.1.0") +interface TrimFromStep { + /** + * Creates a from in a trim expression. + */ + @SinceJdsl("3.1.0") + fun from(value: String): Expressionable + + /** + * Creates a from in a trim expression. + */ + @SinceJdsl("3.1.0") + fun from(value: Expressionable): Expressionable +} diff --git a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimBothDsl.kt b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimBothDsl.kt new file mode 100644 index 000000000..94e68c35a --- /dev/null +++ b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimBothDsl.kt @@ -0,0 +1,18 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression.impl + +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressionable +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions + +@PublishedApi +internal data class TrimBothDsl( + private val character: Expression?, + private val value: Expression, +) : Expressionable { + override fun toExpression(): Expression { + return Expressions.trimBoth( + character = character, + value = value, + ) + } +} diff --git a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimBothFromStepDsl.kt b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimBothFromStepDsl.kt new file mode 100644 index 000000000..78786e086 --- /dev/null +++ b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimBothFromStepDsl.kt @@ -0,0 +1,19 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression.impl + +import com.linecorp.kotlinjdsl.dsl.jpql.expression.TrimFromStep +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressionable +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions + +@PublishedApi +internal data class TrimBothFromStepDsl( + private val character: Expression?, +) : TrimFromStep { + override fun from(value: String): Expressionable { + return TrimBothDsl(character, Expressions.value(value)) + } + + override fun from(value: Expressionable): Expressionable { + return TrimBothDsl(character, value.toExpression()) + } +} diff --git a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimDsl.kt b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimDsl.kt new file mode 100644 index 000000000..776a2ea78 --- /dev/null +++ b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimDsl.kt @@ -0,0 +1,18 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression.impl + +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressionable +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions + +@PublishedApi +internal data class TrimDsl( + private val character: Expression?, + private val value: Expression, +) : Expressionable { + override fun toExpression(): Expression { + return Expressions.trim( + character = character, + value = value, + ) + } +} diff --git a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimFromStepDsl.kt b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimFromStepDsl.kt new file mode 100644 index 000000000..a7b12b6d0 --- /dev/null +++ b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimFromStepDsl.kt @@ -0,0 +1,19 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression.impl + +import com.linecorp.kotlinjdsl.dsl.jpql.expression.TrimFromStep +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressionable +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions + +@PublishedApi +internal data class TrimFromStepDsl( + private val character: Expression?, +) : TrimFromStep { + override fun from(value: String): Expressionable { + return TrimDsl(character, Expressions.value(value)) + } + + override fun from(value: Expressionable): Expressionable { + return TrimDsl(character, value.toExpression()) + } +} diff --git a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimLeadingDsl.kt b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimLeadingDsl.kt new file mode 100644 index 000000000..a7af98094 --- /dev/null +++ b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimLeadingDsl.kt @@ -0,0 +1,18 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression.impl + +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressionable +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions + +@PublishedApi +internal data class TrimLeadingDsl( + private val character: Expression?, + private val value: Expression, +) : Expressionable { + override fun toExpression(): Expression { + return Expressions.trimLeading( + character = character, + value = value, + ) + } +} diff --git a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimLeadingFromStepDsl.kt b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimLeadingFromStepDsl.kt new file mode 100644 index 000000000..f52e0caba --- /dev/null +++ b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimLeadingFromStepDsl.kt @@ -0,0 +1,19 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression.impl + +import com.linecorp.kotlinjdsl.dsl.jpql.expression.TrimFromStep +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressionable +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions + +@PublishedApi +internal data class TrimLeadingFromStepDsl( + private val character: Expression?, +) : TrimFromStep { + override fun from(value: String): Expressionable { + return TrimLeadingDsl(character, Expressions.value(value)) + } + + override fun from(value: Expressionable): Expressionable { + return TrimLeadingDsl(character, value.toExpression()) + } +} diff --git a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimTrailingDsl.kt b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimTrailingDsl.kt new file mode 100644 index 000000000..e16ff8381 --- /dev/null +++ b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimTrailingDsl.kt @@ -0,0 +1,18 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression.impl + +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressionable +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions + +@PublishedApi +internal data class TrimTrailingDsl( + private val character: Expression?, + private val value: Expression, +) : Expressionable { + override fun toExpression(): Expression { + return Expressions.trimTrailing( + character = character, + value = value, + ) + } +} diff --git a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimTrailingFromStepDsl.kt b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimTrailingFromStepDsl.kt new file mode 100644 index 000000000..aa214d1d0 --- /dev/null +++ b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/TrimTrailingFromStepDsl.kt @@ -0,0 +1,19 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression.impl + +import com.linecorp.kotlinjdsl.dsl.jpql.expression.TrimFromStep +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressionable +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions + +@PublishedApi +internal data class TrimTrailingFromStepDsl( + private val character: Expression?, +) : TrimFromStep { + override fun from(value: String): Expressionable { + return TrimTrailingDsl(character, Expressions.value(value)) + } + + override fun from(value: Expressionable): Expressionable { + return TrimTrailingDsl(character, value.toExpression()) + } +} diff --git a/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/TrimBothDslTest.kt b/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/TrimBothDslTest.kt new file mode 100644 index 000000000..2c3c27f36 --- /dev/null +++ b/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/TrimBothDslTest.kt @@ -0,0 +1,121 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression + +import com.linecorp.kotlinjdsl.dsl.jpql.queryPart +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions +import org.assertj.core.api.WithAssertions +import org.junit.jupiter.api.Test + +class TrimBothDslTest : WithAssertions { + private val char1 = 'c' + private val string1 = "string1" + + private val charExpression1 = Expressions.value(char1) + private val stringExpression1 = Expressions.value(string1) + + @Test + fun `trimBoth() without a char, from() with a string`() { + // when + val expression = queryPart { + trimBoth().from(string1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimBoth( + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trimBoth() without a char, from() with a string expression`() { + // when + val expression = queryPart { + trimBoth().from(stringExpression1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimBoth( + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trimBoth() with a char, from() with a string`() { + // when + val expression = queryPart { + trimBoth(char1).from(string1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimBoth( + character = charExpression1, + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trimBoth() with a char, from() with a string expression`() { + // when + val expression = queryPart { + trimBoth(char1).from(stringExpression1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimBoth( + character = charExpression1, + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trimBoth() with a char expression, from() with a string`() { + // when + val expression = queryPart { + trimBoth(charExpression1).from(string1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimBoth( + character = charExpression1, + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trimBoth() with a char expression, from() with a string expression`() { + // when + val expression = queryPart { + trimBoth(charExpression1).from(stringExpression1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimBoth( + character = charExpression1, + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } +} diff --git a/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/TrimDslTest.kt b/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/TrimDslTest.kt new file mode 100644 index 000000000..c632862ea --- /dev/null +++ b/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/TrimDslTest.kt @@ -0,0 +1,155 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression + +import com.linecorp.kotlinjdsl.dsl.jpql.queryPart +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions +import org.assertj.core.api.WithAssertions +import org.junit.jupiter.api.Test + +class TrimDslTest : WithAssertions { + private val char1 = 'c' + private val string1 = "string1" + + private val charExpression1 = Expressions.value(char1) + private val stringExpression1 = Expressions.value(string1) + + @Test + fun `trim() with a string`() { + // when + val expression = queryPart { + trim(string1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trim( + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trim() with a string expression`() { + // when + val expression = queryPart { + trim(stringExpression1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trim( + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trim() without a char, from() with a string`() { + // when + val expression = queryPart { + trim().from(string1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trim( + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trim() without a char, from() with a string expression`() { + // when + val expression = queryPart { + trim().from(stringExpression1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trim( + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trim() with a char, from() with a string`() { + // when + val expression = queryPart { + trim(char1).from(string1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trim( + character = charExpression1, + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trim() with a char, from() with a string expression`() { + // when + val expression = queryPart { + trim(char1).from(stringExpression1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trim( + character = charExpression1, + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trim() with a char expression, from() with a string`() { + // when + val expression = queryPart { + trim(charExpression1).from(string1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trim( + character = charExpression1, + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trim() with a char expression, from() with a string expression`() { + // when + val expression = queryPart { + trim(charExpression1).from(stringExpression1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trim( + character = charExpression1, + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } +} diff --git a/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/TrimLeadingDslTest.kt b/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/TrimLeadingDslTest.kt new file mode 100644 index 000000000..be0eb2ee9 --- /dev/null +++ b/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/TrimLeadingDslTest.kt @@ -0,0 +1,121 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression + +import com.linecorp.kotlinjdsl.dsl.jpql.queryPart +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions +import org.assertj.core.api.WithAssertions +import org.junit.jupiter.api.Test + +class TrimLeadingDslTest : WithAssertions { + private val char1 = 'c' + private val string1 = "string1" + + private val charExpression1 = Expressions.value(char1) + private val stringExpression1 = Expressions.value(string1) + + @Test + fun `trimLeading() without a char, from() with a string`() { + // when + val expression = queryPart { + trimLeading().from(string1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimLeading( + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trimLeading() without a char, from() with a string expression`() { + // when + val expression = queryPart { + trimLeading().from(stringExpression1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimLeading( + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trimLeading() with a char, from() with a string`() { + // when + val expression = queryPart { + trimLeading(char1).from(string1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimLeading( + character = charExpression1, + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trimLeading() with a char, from() with a string expression`() { + // when + val expression = queryPart { + trimLeading(char1).from(stringExpression1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimLeading( + character = charExpression1, + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trimLeading() with a char expression, from() with a string`() { + // when + val expression = queryPart { + trimLeading(charExpression1).from(string1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimLeading( + character = charExpression1, + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trimLeading() with a char expression, from() with a string expression`() { + // when + val expression = queryPart { + trimLeading(charExpression1).from(stringExpression1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimLeading( + character = charExpression1, + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } +} diff --git a/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/TrimTrailingDslTest.kt b/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/TrimTrailingDslTest.kt new file mode 100644 index 000000000..d152e738b --- /dev/null +++ b/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/TrimTrailingDslTest.kt @@ -0,0 +1,121 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression + +import com.linecorp.kotlinjdsl.dsl.jpql.queryPart +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions +import org.assertj.core.api.WithAssertions +import org.junit.jupiter.api.Test + +class TrimTrailingDslTest : WithAssertions { + private val char1 = 'c' + private val string1 = "string1" + + private val charExpression1 = Expressions.value(char1) + private val stringExpression1 = Expressions.value(string1) + + @Test + fun `trimTrailing() without a char, from() with a string`() { + // when + val expression = queryPart { + trimTrailing().from(string1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimTrailing( + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trimTrailing() without a char, from() with a string expression`() { + // when + val expression = queryPart { + trimTrailing().from(stringExpression1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimTrailing( + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trimTrailing() with a char, from() with a string`() { + // when + val expression = queryPart { + trimTrailing(char1).from(string1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimTrailing( + character = charExpression1, + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trimTrailing() with a char, from() with a string expression`() { + // when + val expression = queryPart { + trimTrailing(char1).from(stringExpression1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimTrailing( + character = charExpression1, + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trimTrailing() with a char expression, from() with a string`() { + // when + val expression = queryPart { + trimTrailing(charExpression1).from(string1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimTrailing( + character = charExpression1, + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } + + @Test + fun `trimTrailing() with a char expression, from() with a string expression`() { + // when + val expression = queryPart { + trimTrailing(charExpression1).from(stringExpression1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.trimTrailing( + character = charExpression1, + value = stringExpression1, + ) + + assertThat(actual.toExpression()).isEqualTo(expected) + } +} diff --git a/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/path/PathDslTest.kt b/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/path/PathDslTest.kt index bb9d6e9e7..4f4181793 100644 --- a/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/path/PathDslTest.kt +++ b/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/path/PathDslTest.kt @@ -10,6 +10,7 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.path.Path 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 PathDslTest : WithAssertions { private val entity1 = Entities.entity(FullTimeEmployee::class) @@ -33,6 +34,23 @@ class PathDslTest : WithAssertions { assertThat(actual).isEqualTo(excepted) } + @Test + fun `path() with a getter`() { + // when + val path = queryPart { + path(FullTimeEmployee::getMonthlySalary) + } + + val actual: Path = path // for type check + + // then + val excepted = Paths.path( + FullTimeEmployee::getMonthlySalary, + ) + + assertThat(actual).isEqualTo(excepted) + } + @Test fun `path() with a entity and a property`() { // when @@ -51,6 +69,24 @@ class PathDslTest : WithAssertions { assertThat(actual).isEqualTo(excepted) } + @Test + fun `path() with a entity and a getter`() { + // when + val path = queryPart { + entity1.path(FullTimeEmployee::getMonthlySalary) + } + + val actual: Path = path // for type check + + // then + val excepted = Paths.path( + entity1, + FullTimeEmployee::getMonthlySalary, + ) + + assertThat(actual).isEqualTo(excepted) + } + @Test fun `path() with a path and a property`() { // when @@ -69,6 +105,24 @@ class PathDslTest : WithAssertions { assertThat(actual).isEqualTo(excepted) } + @Test + fun `path() with a path and a getter`() { + // when + val path = queryPart { + path1.path(FullTimeEmployee::getMonthlySalary) + } + + val actual: Path = path // for type check + + // then + val excepted = Paths.path( + path1, + FullTimeEmployee::getMonthlySalary, + ) + + assertThat(actual).isEqualTo(excepted) + } + @Test fun `path() with a entity and a property of super class`() { // when @@ -87,6 +141,24 @@ class PathDslTest : WithAssertions { assertThat(actual).isEqualTo(excepted) } + @Test + fun `path() with a entity and a getter of super class`() { + // when + val path = queryPart { + entity1.path(Employee::getDisplayName) + } + + val actual: Path = path // for type check + + // then + val excepted = Paths.path( + entity1, + Employee::getDisplayName, + ) + + assertThat(actual).isEqualTo(excepted) + } + @Test fun `path() with a path and a property of super class`() { // when @@ -105,6 +177,24 @@ class PathDslTest : WithAssertions { assertThat(actual).isEqualTo(excepted) } + @Test + fun `path() with a path and a getter of super class`() { + // when + val path = queryPart { + path1.path(Employee::getDisplayName) + } + + val actual: Path = path // for type check + + // then + val excepted = Paths.path( + path1, + Employee::getDisplayName, + ) + + assertThat(actual).isEqualTo(excepted) + } + @Test fun `invoke() with a entity and a property`() { // when @@ -123,6 +213,24 @@ class PathDslTest : WithAssertions { assertThat(actual).isEqualTo(excepted) } + @Test + fun `invoke() with a entity and a getter`() { + // when + val path = queryPart { + entity1(FullTimeEmployee::getMonthlySalary) + } + + val actual: Path = path // for type check + + // then + val excepted = Paths.path( + entity1, + FullTimeEmployee::getMonthlySalary, + ) + + assertThat(actual).isEqualTo(excepted) + } + @Test fun `invoke() with a path and a property`() { // when @@ -141,6 +249,24 @@ class PathDslTest : WithAssertions { assertThat(actual).isEqualTo(excepted) } + @Test + fun `invoke() with a path and a getter`() { + // when + val path = queryPart { + path1(FullTimeEmployee::getMonthlySalary) + } + + val actual: Path = path // for type check + + // then + val excepted = Paths.path( + path1, + FullTimeEmployee::getMonthlySalary, + ) + + assertThat(actual).isEqualTo(excepted) + } + @Test fun `invoke() with a entity and a property of super class`() { // when @@ -159,6 +285,24 @@ class PathDslTest : WithAssertions { assertThat(actual).isEqualTo(excepted) } + @Test + fun `invoke() with a entity and a getter of super class`() { + // when + val path = queryPart { + entity1(Employee::getDisplayName) + } + + val actual: Path = path // for type check + + // then + val excepted = Paths.path( + entity1, + Employee::getDisplayName, + ) + + assertThat(actual).isEqualTo(excepted) + } + @Test fun `invoke() with a path and a property of super class`() { // when @@ -176,4 +320,22 @@ class PathDslTest : WithAssertions { assertThat(actual).isEqualTo(excepted) } + + @Test + fun `invoke() with a path and a getter of super class`() { + // when + val path = queryPart { + path1(Employee::getDisplayName) + } + + val actual: Path = path // for type check + + // then + val excepted = Paths.path( + path1, + Employee::getDisplayName, + ) + + assertThat(actual).isEqualTo(excepted) + } } diff --git a/dsl/jpql/src/testFixtures/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/entity/employee/Employee.kt b/dsl/jpql/src/testFixtures/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/entity/employee/Employee.kt index 3d1c808c3..293ce31bb 100644 --- a/dsl/jpql/src/testFixtures/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/entity/employee/Employee.kt +++ b/dsl/jpql/src/testFixtures/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/entity/employee/Employee.kt @@ -7,4 +7,6 @@ abstract class Employee( val phone: String, val address: EmployeeAddress, val departments: MutableSet, -) +) { + fun getDisplayName() = nickname ?: name +} diff --git a/dsl/jpql/src/testFixtures/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/entity/employee/FullTimeEmployee.kt b/dsl/jpql/src/testFixtures/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/entity/employee/FullTimeEmployee.kt index 09bb8b9fc..6eb177bb8 100644 --- a/dsl/jpql/src/testFixtures/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/entity/employee/FullTimeEmployee.kt +++ b/dsl/jpql/src/testFixtures/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/entity/employee/FullTimeEmployee.kt @@ -17,4 +17,6 @@ class FullTimeEmployee( phone = phone, address = address, departments = departments, -) +) { + fun getMonthlySalary() = annualSalary.div(BigDecimal.valueOf(12)) +} diff --git a/libs.example.versions.toml b/libs.example.versions.toml index 373179865..0973d1a17 100644 --- a/libs.example.versions.toml +++ b/libs.example.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "1.9.10" +kotlin = "1.9.20" spring-boot3 = "3.1.5" spring-boot2 = "2.7.17" @@ -44,7 +44,7 @@ eclipselink2 = { module = "org.eclipse.persistence:org.eclipse.persistence.jpa", eclipselink4 = { module = "org.eclipse.persistence:org.eclipse.persistence.jpa", version = "4.0.2" } # vertx -vertx-jdbc-client = { module = "io.vertx:vertx-jdbc-client", version = "4.4.6" } +vertx-jdbc-client = { module = "io.vertx:vertx-jdbc-client", version = "4.5.0" } agroal-pool = { module = "io.agroal:agroal-pool", version = "2.2" } h2 = { module = "com.h2database:h2", version = "2.2.224" } diff --git a/package.json b/package.json index e8b39db52..146ceb62c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "jdsl-commitlint-node-package", "devDependencies": { - "@commitlint/cli": "18.0.0", - "@commitlint/config-conventional": "18.0.0" + "@commitlint/cli": "18.4.3", + "@commitlint/config-conventional": "18.4.3" } } diff --git a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/Expressions.kt b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/Expressions.kt index d8ce36e44..4d165a1c1 100644 --- a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/Expressions.kt +++ b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/Expressions.kt @@ -30,6 +30,10 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlPlus import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlSubquery import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlSum import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTimes +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTrim +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTrimBoth +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTrimLeading +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTrimTrailing import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlUpper import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlValue import com.linecorp.kotlinjdsl.querymodel.jpql.path.Path @@ -416,6 +420,58 @@ object Expressions { return JpqlPathType(path) } + /** + * Creates an expression that represents a string with the specified characters all trimmed + * from the both sides of the string. + * If the character is not specified, it will be assumed to be whitespace. + */ + @SinceJdsl("3.1.0") + fun trim( + character: Expression? = null, + value: Expression, + ): Expression { + return JpqlTrim(character, value) + } + + /** + * Creates an expression that represents a string with the specified characters all trimmed + * from the leading side of the string. + * If the character is not specified, it will be assumed to be whitespace. + */ + @SinceJdsl("3.1.0") + fun trimLeading( + character: Expression? = null, + value: Expression, + ): Expression { + return JpqlTrimLeading(character, value) + } + + /** + * Creates an expression that represents a string with the specified characters all trimmed + * from the trailing side of the string. + * If the character is not specified, it will be assumed to be whitespace. + */ + @SinceJdsl("3.1.0") + fun trimTrailing( + character: Expression? = null, + value: Expression, + ): Expression { + return JpqlTrimTrailing(character, value) + } + + /** + * Creates an expression that represents a string with the specified characters all trimmed + * from the both sides of the string. + * If the character is not specified, it will be assumed to be whitespace. + */ + @SinceJdsl("3.1.0") + fun trimBoth( + character: Expression? = null, + value: Expression, + ): Expression { + return JpqlTrimBoth(character, value) + } + /** * Creates an expression that represents the string in uppercase. */ diff --git a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlTrim.kt b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlTrim.kt new file mode 100644 index 000000000..7fd8ee417 --- /dev/null +++ b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlTrim.kt @@ -0,0 +1,10 @@ +package com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl + +import com.linecorp.kotlinjdsl.Internal +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression + +@Internal +data class JpqlTrim internal constructor( + val character: Expression?, + val value: Expression, +) : Expression diff --git a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlTrimBoth.kt b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlTrimBoth.kt new file mode 100644 index 000000000..04fa1873b --- /dev/null +++ b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlTrimBoth.kt @@ -0,0 +1,10 @@ +package com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl + +import com.linecorp.kotlinjdsl.Internal +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression + +@Internal +data class JpqlTrimBoth internal constructor( + val character: Expression?, + val value: Expression, +) : Expression diff --git a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlTrimLeading.kt b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlTrimLeading.kt new file mode 100644 index 000000000..563b50e58 --- /dev/null +++ b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlTrimLeading.kt @@ -0,0 +1,10 @@ +package com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl + +import com.linecorp.kotlinjdsl.Internal +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression + +@Internal +data class JpqlTrimLeading internal constructor( + val character: Expression?, + val value: Expression, +) : Expression diff --git a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlTrimTrailing.kt b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlTrimTrailing.kt new file mode 100644 index 000000000..4966508cc --- /dev/null +++ b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlTrimTrailing.kt @@ -0,0 +1,10 @@ +package com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl + +import com.linecorp.kotlinjdsl.Internal +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression + +@Internal +data class JpqlTrimTrailing internal constructor( + val character: Expression?, + val value: Expression, +) : Expression diff --git a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/path/Paths.kt b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/path/Paths.kt index b97a13840..90d5d6b81 100644 --- a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/path/Paths.kt +++ b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/path/Paths.kt @@ -9,6 +9,7 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.path.impl.JpqlPathProperty import com.linecorp.kotlinjdsl.querymodel.jpql.path.impl.JpqlPathTreat import kotlin.internal.Exact import kotlin.reflect.KClass +import kotlin.reflect.KFunction1 import kotlin.reflect.KProperty1 /** @@ -26,6 +27,16 @@ object Paths { return JpqlEntityProperty(Entities.entity(owner), property) } + /** + * Creates a path with the property. + */ + @SinceJdsl("3.1.0") + fun path(getter: KFunction1): Path { + val owner = PropertyUtils.getOwner(getter) + + return JpqlEntityProperty(Entities.entity(owner), getter) + } + /** * Creates a path with the entity and property. */ @@ -34,6 +45,14 @@ object Paths { return JpqlEntityProperty(entity, property) } + /** + * Creates a path with the entity and property. + */ + @SinceJdsl("3.1.0") + fun path(entity: Entity, getter: KFunction1): Path { + return JpqlEntityProperty(entity, getter) + } + /** * Creates a path with the path and property. */ @@ -42,6 +61,14 @@ object Paths { return JpqlPathProperty(path, property) } + /** + * Creates a path with the path and property. + */ + @SinceJdsl("3.1.0") + fun path(path: Path, getter: KFunction1): Path { + return JpqlPathProperty(path, getter) + } + /** * Creates a path with downcasting. */ diff --git a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/path/impl/JpqlEntityProperty.kt b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/path/impl/JpqlEntityProperty.kt index 0297c5a75..f7ce0e330 100644 --- a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/path/impl/JpqlEntityProperty.kt +++ b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/path/impl/JpqlEntityProperty.kt @@ -3,10 +3,10 @@ package com.linecorp.kotlinjdsl.querymodel.jpql.path.impl import com.linecorp.kotlinjdsl.Internal import com.linecorp.kotlinjdsl.querymodel.jpql.entity.Entity import com.linecorp.kotlinjdsl.querymodel.jpql.path.Path -import kotlin.reflect.KProperty1 +import kotlin.reflect.KCallable @Internal data class JpqlEntityProperty internal constructor( - val entity: Entity<*>, - val property: KProperty1, + val entity: Entity, + val property: KCallable, ) : Path diff --git a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/path/impl/JpqlPathProperty.kt b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/path/impl/JpqlPathProperty.kt index 1adb203d5..556efd5cf 100644 --- a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/path/impl/JpqlPathProperty.kt +++ b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/path/impl/JpqlPathProperty.kt @@ -2,10 +2,10 @@ package com.linecorp.kotlinjdsl.querymodel.jpql.path.impl import com.linecorp.kotlinjdsl.Internal import com.linecorp.kotlinjdsl.querymodel.jpql.path.Path -import kotlin.reflect.KProperty1 +import kotlin.reflect.KCallable @Internal data class JpqlPathProperty internal constructor( val path: Path, - val property: KProperty1, + val property: KCallable, ) : Path diff --git a/query-model/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/ExpressionsTest.kt b/query-model/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/ExpressionsTest.kt index 980d7c71a..6f611ecf8 100644 --- a/query-model/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/ExpressionsTest.kt +++ b/query-model/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/ExpressionsTest.kt @@ -32,6 +32,10 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlPlus import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlSubquery import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlSum import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTimes +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTrim +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTrimBoth +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTrimLeading +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTrimTrailing import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlUpper import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlValue import com.linecorp.kotlinjdsl.querymodel.jpql.path.Paths @@ -58,6 +62,7 @@ class ExpressionsTest : WithAssertions { private val char1: Char = 'a' private val string1: String = "string1" + private val charExpression1: Expression = Expressions.value('c') private val stringExpression1: Expression = Expressions.value("string1") private val stringExpression2: Expression = Expressions.value("string2") private val intExpression1: Expression = Expressions.value(100) @@ -600,6 +605,74 @@ class ExpressionsTest : WithAssertions { assertThat(actual).isEqualTo(expected) } + @Test + fun trim() { + // when + val actual = Expressions.trim( + charExpression1, + stringExpression1, + ) + + // then + val expected = JpqlTrim( + charExpression1, + stringExpression1, + ) + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun trimLeading() { + // when + val actual = Expressions.trimLeading( + charExpression1, + stringExpression1, + ) + + // then + val expected = JpqlTrimLeading( + charExpression1, + stringExpression1, + ) + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun trimTrailing() { + // when + val actual = Expressions.trimTrailing( + charExpression1, + stringExpression1, + ) + + // then + val expected = JpqlTrimTrailing( + charExpression1, + stringExpression1, + ) + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun trimBoth() { + // when + val actual = Expressions.trimBoth( + charExpression1, + stringExpression1, + ) + + // then + val expected = JpqlTrimBoth( + charExpression1, + stringExpression1, + ) + + assertThat(actual).isEqualTo(expected) + } + @Test fun upper() { // when diff --git a/query-model/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/path/PathsTest.kt b/query-model/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/path/PathsTest.kt index d68e002bc..060338a64 100644 --- a/query-model/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/path/PathsTest.kt +++ b/query-model/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/path/PathsTest.kt @@ -18,7 +18,7 @@ class PathsTest : WithAssertions { private val path2 = Paths.treat(Paths.path(EmployeeDepartment::employee), FullTimeEmployee::class) @Test - fun path() { + fun `path() with a property`() { // when val actual = Paths.path( FullTimeEmployee::address, @@ -34,7 +34,23 @@ class PathsTest : WithAssertions { } @Test - fun `path() with an entity`() { + fun `path() with a getter`() { + // when + val actual = Paths.path( + FullTimeEmployee::getDisplayName, + ) + + // then + val expected = JpqlEntityProperty( + entity = Entities.entity(FullTimeEmployee::class), + property = FullTimeEmployee::getDisplayName, + ) + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun `path() with an entity and a property`() { // when val actual = Paths.path( entity1, @@ -51,7 +67,24 @@ class PathsTest : WithAssertions { } @Test - fun `path() with a path`() { + fun `path() with an entity and a getter`() { + // when + val actual = Paths.path( + entity1, + Employee::getDisplayName, + ) + + // then + val expected = JpqlEntityProperty( + entity = entity1, + property = Employee::getDisplayName, + ) + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun `path() with a path and a property`() { // when val actual = Paths.path( path1, @@ -67,6 +100,23 @@ class PathsTest : WithAssertions { assertThat(actual).isEqualTo(expected) } + @Test + fun `path() with a path and a getter`() { + // when + val actual = Paths.path( + path1, + getter = Employee::getDisplayName, + ) + + // then + val expected = JpqlPathProperty( + path = path1, + property = Employee::getDisplayName, + ) + + assertThat(actual).isEqualTo(expected) + } + @Test fun `path() with an entity and a property of super class`() { // when @@ -84,6 +134,23 @@ class PathsTest : WithAssertions { assertThat(actual).isEqualTo(expected) } + @Test + fun `path() with an entity and a getter of super class`() { + // when + val actual = Paths.path( + entity2, + Employee::getDisplayName, + ) + + // then + val expected = JpqlEntityProperty( + entity = entity2, + property = Employee::getDisplayName, + ) + + assertThat(actual).isEqualTo(expected) + } + @Test fun `path() with a path and a property of super class`() { // when @@ -101,6 +168,23 @@ class PathsTest : WithAssertions { assertThat(actual).isEqualTo(expected) } + @Test + fun `path() with a path and a getter of super class`() { + // when + val actual = Paths.path( + path2, + getter = Employee::getDisplayName, + ) + + // then + val expected = JpqlPathProperty( + path = path2, + property = Employee::getDisplayName, + ) + + assertThat(actual).isEqualTo(expected) + } + @Test fun treat() { // when diff --git a/query-model/jpql/src/testFixtures/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/entity/employee/Employee.kt b/query-model/jpql/src/testFixtures/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/entity/employee/Employee.kt index 534c41239..2eae684e0 100644 --- a/query-model/jpql/src/testFixtures/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/entity/employee/Employee.kt +++ b/query-model/jpql/src/testFixtures/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/entity/employee/Employee.kt @@ -9,4 +9,6 @@ abstract class Employee( val phone: String, val address: EmployeeAddress, val departments: MutableSet, -) +) { + fun getDisplayName() = nickname ?: name +} diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/JpqlRenderContext.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/JpqlRenderContext.kt index 47380d5b0..fa93b175c 100644 --- a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/JpqlRenderContext.kt +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/JpqlRenderContext.kt @@ -9,6 +9,7 @@ import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlIntrospectorModifier import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlRenderIntrospector import com.linecorp.kotlinjdsl.render.jpql.introspector.impl.JakartaJpqlIntrospector import com.linecorp.kotlinjdsl.render.jpql.introspector.impl.JavaxJpqlIntrospector +import com.linecorp.kotlinjdsl.render.jpql.introspector.impl.KotlinStyleJpqlPropertyIntrospector import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlRenderSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlSerializerModifier @@ -94,6 +95,10 @@ import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlSortSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlSubquerySerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlSumSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlTimesSerializer +import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlTrimBothSerializer +import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlTrimLeadingSerializer +import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlTrimSerializer +import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlTrimTrailingSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlUpdateQuerySerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlUpperSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlValueSerializer @@ -246,6 +251,8 @@ private class DefaultModule : JpqlRenderModule { context.appendIntrospector(JakartaJpqlIntrospector()) } + context.appendIntrospector(KotlinStyleJpqlPropertyIntrospector()) + context.addAllSerializer( JpqlAliasedExpressionSerializer(), JpqlAndSerializer(), @@ -329,6 +336,10 @@ private class DefaultModule : JpqlRenderModule { JpqlSubquerySerializer(), JpqlSumSerializer(), JpqlTimesSerializer(), + JpqlTrimBothSerializer(), + JpqlTrimLeadingSerializer(), + JpqlTrimSerializer(), + JpqlTrimTrailingSerializer(), JpqlUpdateQuerySerializer(), JpqlUpperSerializer(), JpqlValueSerializer(), diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/CombinedJpqlIntrospector.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/CombinedJpqlIntrospector.kt index b69a43a4a..88b1f5965 100644 --- a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/CombinedJpqlIntrospector.kt +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/CombinedJpqlIntrospector.kt @@ -1,6 +1,7 @@ package com.linecorp.kotlinjdsl.render.jpql.introspector import com.linecorp.kotlinjdsl.SinceJdsl +import kotlin.reflect.KCallable import kotlin.reflect.KClass /** @@ -20,4 +21,14 @@ class CombinedJpqlIntrospector( override fun introspect(type: KClass<*>): JpqlEntityDescription? { return primary.introspect(type) ?: secondary.introspect(type) } + + /** + * Get the entity information by introspecting KCallable. + * + * If the primary introspector introspects this KCallable, it returns the result of the primary introspector. + * Otherwise, it returns the result of the secondary introspector. + */ + override fun introspect(property: KCallable<*>): JpqlPropertyDescription? { + return primary.introspect(property) ?: secondary.introspect(property) + } } diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlEntityIntrospector.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlEntityIntrospector.kt new file mode 100644 index 000000000..30024b3b4 --- /dev/null +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlEntityIntrospector.kt @@ -0,0 +1,12 @@ +package com.linecorp.kotlinjdsl.render.jpql.introspector + +import com.linecorp.kotlinjdsl.SinceJdsl +import kotlin.reflect.KCallable + +/** + * Abstract class to get the entity information by introspecting KClass. + */ +@SinceJdsl("3.1.0") +abstract class JpqlEntityIntrospector : JpqlIntrospector { + override fun introspect(property: KCallable<*>): JpqlPropertyDescription? = null +} diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlIntrospector.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlIntrospector.kt index 564800d74..c4a5ae032 100644 --- a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlIntrospector.kt +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlIntrospector.kt @@ -1,6 +1,7 @@ package com.linecorp.kotlinjdsl.render.jpql.introspector import com.linecorp.kotlinjdsl.SinceJdsl +import kotlin.reflect.KCallable import kotlin.reflect.KClass /** @@ -14,4 +15,11 @@ interface JpqlIntrospector { */ @SinceJdsl("3.0.0") fun introspect(type: KClass<*>): JpqlEntityDescription? + + /** + * Introspects the KCallable to get the entity information. + * If it cannot introspect this KCallable, it returns null. + */ + @SinceJdsl("3.1.0") + fun introspect(property: KCallable<*>): JpqlPropertyDescription? } diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlPropertyDescription.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlPropertyDescription.kt new file mode 100644 index 000000000..917d73ab9 --- /dev/null +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlPropertyDescription.kt @@ -0,0 +1,12 @@ +package com.linecorp.kotlinjdsl.render.jpql.introspector + +import com.linecorp.kotlinjdsl.SinceJdsl + +/** + * Interface to represent the property information. + */ +@SinceJdsl("3.1.0") +interface JpqlPropertyDescription { + @SinceJdsl("3.1.0") + val name: String +} diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlPropertyIntrospector.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlPropertyIntrospector.kt new file mode 100644 index 000000000..99347270e --- /dev/null +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlPropertyIntrospector.kt @@ -0,0 +1,12 @@ +package com.linecorp.kotlinjdsl.render.jpql.introspector + +import com.linecorp.kotlinjdsl.SinceJdsl +import kotlin.reflect.KClass + +/** + * Abstract class to get the entity information by introspecting KCallable. + */ +@SinceJdsl("3.1.0") +abstract class JpqlPropertyIntrospector : JpqlIntrospector { + override fun introspect(type: KClass<*>): JpqlEntityDescription? = null +} diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlRenderIntrospector.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlRenderIntrospector.kt index 0c2c412f2..c012aade9 100644 --- a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlRenderIntrospector.kt +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/JpqlRenderIntrospector.kt @@ -4,6 +4,7 @@ import com.linecorp.kotlinjdsl.SinceJdsl import com.linecorp.kotlinjdsl.render.AbstractRenderContextElement import com.linecorp.kotlinjdsl.render.RenderContext import java.util.concurrent.ConcurrentHashMap +import kotlin.reflect.KCallable import kotlin.reflect.KClass /** @@ -15,7 +16,8 @@ class JpqlRenderIntrospector( ) : AbstractRenderContextElement(Key) { companion object Key : RenderContext.Key - private val tableLookupCache: MutableMap, JpqlEntityDescription> = ConcurrentHashMap() + private val entityLookupCache: MutableMap, JpqlEntityDescription> = ConcurrentHashMap() + private val propertyLookupCache: MutableMap, JpqlPropertyDescription> = ConcurrentHashMap() /** * Creates a new introspector by combining this introspector and the introspector. @@ -38,8 +40,22 @@ class JpqlRenderIntrospector( return getCachedDescription(clazz) } + /** + * Introspects the KCallable to get the property information. + */ + @SinceJdsl("3.1.0") + fun introspect(property: KCallable<*>): JpqlPropertyDescription { + return getCachedDescription(property) + } + private fun getCachedDescription(clazz: KClass<*>): JpqlEntityDescription { - return tableLookupCache.computeIfAbsent(clazz) { + return entityLookupCache.computeIfAbsent(clazz) { + getDescription(it) + } + } + + private fun getCachedDescription(property: KCallable<*>): JpqlPropertyDescription { + return propertyLookupCache.computeIfAbsent(property) { getDescription(it) } } @@ -48,4 +64,9 @@ class JpqlRenderIntrospector( return introspector.introspect(clazz) ?: throw IllegalStateException("There is no description for ${clazz.java.name}") } + + private fun getDescription(property: KCallable<*>): JpqlPropertyDescription { + return introspector.introspect(property) + ?: throw IllegalStateException("There is no description for ${property.name}") + } } diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/JakartaJpqlIntrospector.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/JakartaJpqlIntrospector.kt index eb6f67d27..b7f75b54b 100644 --- a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/JakartaJpqlIntrospector.kt +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/JakartaJpqlIntrospector.kt @@ -2,7 +2,7 @@ package com.linecorp.kotlinjdsl.render.jpql.introspector.impl import com.linecorp.kotlinjdsl.Internal import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlEntityDescription -import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlIntrospector +import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlEntityIntrospector import kotlin.reflect.KClass import kotlin.reflect.full.findAnnotations @@ -10,7 +10,7 @@ import kotlin.reflect.full.findAnnotations * Introspector that introspects KClass using [jakarta.persistence.Entity]. */ @Internal -class JakartaJpqlIntrospector : JpqlIntrospector { +class JakartaJpqlIntrospector : JpqlEntityIntrospector() { override fun introspect(type: KClass<*>): JpqlEntityDescription? { val entity = type.findAnnotations(jakarta.persistence.Entity::class).firstOrNull() diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/JavaxJpqlIntrospector.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/JavaxJpqlIntrospector.kt index a60bf18c6..3effe861d 100644 --- a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/JavaxJpqlIntrospector.kt +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/JavaxJpqlIntrospector.kt @@ -2,7 +2,7 @@ package com.linecorp.kotlinjdsl.render.jpql.introspector.impl import com.linecorp.kotlinjdsl.Internal import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlEntityDescription -import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlIntrospector +import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlEntityIntrospector import kotlin.reflect.KClass import kotlin.reflect.full.findAnnotations @@ -10,7 +10,7 @@ import kotlin.reflect.full.findAnnotations * Introspector that introspects KClass using [javax.persistence.Entity]. */ @Internal -class JavaxJpqlIntrospector : JpqlIntrospector { +class JavaxJpqlIntrospector : JpqlEntityIntrospector() { override fun introspect(type: KClass<*>): JpqlEntityDescription? { val entity = type.findAnnotations(javax.persistence.Entity::class).firstOrNull() diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/KotlinStyleJpqlPropertyIntrospector.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/KotlinStyleJpqlPropertyIntrospector.kt new file mode 100644 index 000000000..fc60d4db0 --- /dev/null +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/KotlinStyleJpqlPropertyIntrospector.kt @@ -0,0 +1,33 @@ +package com.linecorp.kotlinjdsl.render.jpql.introspector.impl + +import com.linecorp.kotlinjdsl.Internal +import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlPropertyDescription +import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlPropertyIntrospector +import kotlin.reflect.KCallable +import kotlin.reflect.KFunction1 +import kotlin.reflect.KProperty1 + +/** + * Introspector that introspects a property name in KCallable using Kotlin style. + */ +@Internal +class KotlinStyleJpqlPropertyIntrospector : JpqlPropertyIntrospector() { + override fun introspect(property: KCallable<*>): JpqlPropertyDescription? { + return when (property) { + is KProperty1<*, *> -> KotlinStyleProperty(property.name) + is KFunction1<*, *> -> KotlinStyleProperty(resolvePropertyName(property)) + else -> null + } + } + + private fun resolvePropertyName(getter: KFunction1<*, *>): String = + if (getter.name.startsWith("is")) { + getter.name + } else { + getter.name.removePrefix("get").replaceFirstChar { it.lowercase() } + } +} + +private data class KotlinStyleProperty( + override val name: String, +) : JpqlPropertyDescription diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlEntityPropertySerializer.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlEntityPropertySerializer.kt index d3d89c177..87f599c37 100644 --- a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlEntityPropertySerializer.kt +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlEntityPropertySerializer.kt @@ -3,6 +3,7 @@ package com.linecorp.kotlinjdsl.render.jpql.serializer.impl import com.linecorp.kotlinjdsl.Internal import com.linecorp.kotlinjdsl.querymodel.jpql.path.impl.JpqlEntityProperty import com.linecorp.kotlinjdsl.render.RenderContext +import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlRenderIntrospector import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlSerializer import com.linecorp.kotlinjdsl.render.jpql.writer.JpqlWriter import kotlin.reflect.KClass @@ -14,8 +15,11 @@ class JpqlEntityPropertySerializer : JpqlSerializer> { } override fun serialize(part: JpqlEntityProperty<*, *>, writer: JpqlWriter, context: RenderContext) { + val introspector = context.getValue(JpqlRenderIntrospector) + val property = introspector.introspect(part.property) + writer.write(part.entity.alias) writer.write(".") - writer.write(part.property.name) + writer.write(property.name) } } diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlFunctionSerializer.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlFunctionSerializer.kt index 0c16d99f2..c7d7d7a62 100644 --- a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlFunctionSerializer.kt +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlFunctionSerializer.kt @@ -21,7 +21,9 @@ class JpqlFunctionSerializer : JpqlSerializer> { writer.write("FUNCTION") writer.writeParentheses { + writer.write("'") writer.write(part.name) + writer.write("'") if (IterableUtils.isNotEmpty(part.args)) { writer.write(",") diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlPathPropertySerializer.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlPathPropertySerializer.kt index c2939a3b7..954376b2e 100644 --- a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlPathPropertySerializer.kt +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlPathPropertySerializer.kt @@ -3,6 +3,7 @@ package com.linecorp.kotlinjdsl.render.jpql.serializer.impl import com.linecorp.kotlinjdsl.Internal import com.linecorp.kotlinjdsl.querymodel.jpql.path.impl.JpqlPathProperty import com.linecorp.kotlinjdsl.render.RenderContext +import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlRenderIntrospector import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlRenderSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlSerializer import com.linecorp.kotlinjdsl.render.jpql.writer.JpqlWriter @@ -16,9 +17,11 @@ class JpqlPathPropertySerializer : JpqlSerializer> { override fun serialize(part: JpqlPathProperty<*, *>, writer: JpqlWriter, context: RenderContext) { val delegate = context.getValue(JpqlRenderSerializer) + val introspector = context.getValue(JpqlRenderIntrospector) + val property = introspector.introspect(part.property) delegate.serialize(part.path, writer, context) writer.write(".") - writer.write(part.property.name) + writer.write(property.name) } } diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimBothSerializer.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimBothSerializer.kt new file mode 100644 index 000000000..ce130277a --- /dev/null +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimBothSerializer.kt @@ -0,0 +1,37 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.Internal +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTrimBoth +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 JpqlTrimBothSerializer : JpqlSerializer { + override fun handledType(): KClass { + return JpqlTrimBoth::class + } + + override fun serialize(part: JpqlTrimBoth, writer: JpqlWriter, context: RenderContext) { + val delegate = context.getValue(JpqlRenderSerializer) + + writer.write("TRIM") + + writer.writeParentheses { + writer.write("BOTH") + writer.write(" ") + + part.character?.let { + delegate.serialize(it, writer, context) + writer.write(" ") + } + + writer.write("FROM") + writer.write(" ") + + delegate.serialize(part.value, writer, context) + } + } +} diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimLeadingSerializer.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimLeadingSerializer.kt new file mode 100644 index 000000000..086f047e4 --- /dev/null +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimLeadingSerializer.kt @@ -0,0 +1,37 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.Internal +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTrimLeading +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 JpqlTrimLeadingSerializer : JpqlSerializer { + override fun handledType(): KClass { + return JpqlTrimLeading::class + } + + override fun serialize(part: JpqlTrimLeading, writer: JpqlWriter, context: RenderContext) { + val delegate = context.getValue(JpqlRenderSerializer) + + writer.write("TRIM") + + writer.writeParentheses { + writer.write("LEADING") + writer.write(" ") + + part.character?.let { + delegate.serialize(it, writer, context) + writer.write(" ") + } + + writer.write("FROM") + writer.write(" ") + + delegate.serialize(part.value, writer, context) + } + } +} diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimSerializer.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimSerializer.kt new file mode 100644 index 000000000..4d044d586 --- /dev/null +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimSerializer.kt @@ -0,0 +1,33 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.Internal +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTrim +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 JpqlTrimSerializer : JpqlSerializer { + override fun handledType(): KClass { + return JpqlTrim::class + } + + override fun serialize(part: JpqlTrim, writer: JpqlWriter, context: RenderContext) { + val delegate = context.getValue(JpqlRenderSerializer) + + writer.write("TRIM") + + writer.writeParentheses { + part.character?.let { + delegate.serialize(it, writer, context) + writer.write(" ") + writer.write("FROM") + writer.write(" ") + } + + delegate.serialize(part.value, writer, context) + } + } +} diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimTrailingSerializer.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimTrailingSerializer.kt new file mode 100644 index 000000000..65ddeaf61 --- /dev/null +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimTrailingSerializer.kt @@ -0,0 +1,37 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.Internal +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTrimTrailing +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 JpqlTrimTrailingSerializer : JpqlSerializer { + override fun handledType(): KClass { + return JpqlTrimTrailing::class + } + + override fun serialize(part: JpqlTrimTrailing, writer: JpqlWriter, context: RenderContext) { + val delegate = context.getValue(JpqlRenderSerializer) + + writer.write("TRIM") + + writer.writeParentheses { + writer.write("TRAILING") + writer.write(" ") + + part.character?.let { + delegate.serialize(it, writer, context) + writer.write(" ") + } + + writer.write("FROM") + writer.write(" ") + + delegate.serialize(part.value, writer, context) + } + } +} diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/CombinedJpqlIntrospectorTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/CombinedJpqlIntrospectorTest.kt index 3ff8d2366..e8d529949 100644 --- a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/CombinedJpqlIntrospectorTest.kt +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/CombinedJpqlIntrospectorTest.kt @@ -9,6 +9,8 @@ import org.assertj.core.api.WithAssertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith +import kotlin.reflect.KCallable +import kotlin.reflect.KClass @ExtendWith(MockKExtension::class) class CombinedJpqlIntrospectorTest : WithAssertions { @@ -24,6 +26,10 @@ class CombinedJpqlIntrospectorTest : WithAssertions { override val name: String = "entityName1" } + private val propertyDescription1 = object : JpqlPropertyDescription { + override val name: String = "propertyName1" + } + @BeforeEach fun setUp() { sut = CombinedJpqlIntrospector( @@ -33,9 +39,9 @@ class CombinedJpqlIntrospectorTest : WithAssertions { } @Test - fun `introspect() return the description of the primary, when the primary returns non null`() { + fun `introspect(type) return the description of the primary, when the primary returns non null`() { // given - every { introspector1.introspect(any()) } returns entityDescription1 + every { introspector1.introspect(any>()) } returns entityDescription1 // when val actual = sut.introspect(Book::class) @@ -49,10 +55,10 @@ class CombinedJpqlIntrospectorTest : WithAssertions { } @Test - fun `introspect() return the description of the secondary, when the primary returns null`() { + fun `introspect(type) return the description of the secondary, when the primary returns null`() { // given - every { introspector1.introspect(any()) } returns null - every { introspector2.introspect(any()) } returns entityDescription1 + every { introspector1.introspect(any>()) } returns null + every { introspector2.introspect(any>()) } returns entityDescription1 // when val actual = sut.introspect(Book::class) @@ -65,4 +71,38 @@ class CombinedJpqlIntrospectorTest : WithAssertions { introspector2.introspect(Book::class) } } + + @Test + fun `introspect(property) return the description of the primary, when the primary returns non null`() { + // given + every { introspector1.introspect(any>()) } returns propertyDescription1 + + // when + val actual = sut.introspect(Book::title) + + // then + assertThat(actual).isEqualTo(propertyDescription1) + + verifySequence { + introspector1.introspect(Book::title) + } + } + + @Test + fun `introspect(property) return the description of the secondary, when the primary returns null`() { + // given + every { introspector1.introspect(any>()) } returns null + every { introspector2.introspect(any>()) } returns propertyDescription1 + + // when + val actual = sut.introspect(Book::title) + + // then + assertThat(actual).isEqualTo(propertyDescription1) + + verifySequence { + introspector1.introspect(Book::title) + introspector2.introspect(Book::title) + } + } } diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/JakartaJpqlIntrospectorTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/JakartaJpqlIntrospectorTest.kt index 151b67866..182fac484 100644 --- a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/JakartaJpqlIntrospectorTest.kt +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/JakartaJpqlIntrospectorTest.kt @@ -11,7 +11,7 @@ class JakartaJpqlIntrospectorTest : WithAssertions { } @Test - fun introspect() { + fun `introspect() returns the name of the entity annotation, when the entity annotation has name`() { // given val type = EntityClass1::class @@ -23,7 +23,7 @@ class JakartaJpqlIntrospectorTest : WithAssertions { } @Test - fun `introspect() returns name of class, when entity annotation does not have name`() { + fun `introspect() returns the name of the class, when the entity annotation does not have name`() { // given val type = EntityClass2::class diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/JavaxJpqlIntrospectorTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/JavaxJpqlIntrospectorTest.kt index 46e8b1dac..4e8546060 100644 --- a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/JavaxJpqlIntrospectorTest.kt +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/JavaxJpqlIntrospectorTest.kt @@ -11,7 +11,7 @@ class JavaxJpqlIntrospectorTest : WithAssertions { } @Test - fun introspect() { + fun `introspect() returns the name of the entity annotation, when the entity annotation has name`() { // given val type = EntityClass1::class @@ -23,7 +23,7 @@ class JavaxJpqlIntrospectorTest : WithAssertions { } @Test - fun `introspect() returns name of class, when entity annotation does not have name`() { + fun `introspect() returns the name of the class, when the entity annotation does not have name`() { // given val type = EntityClass2::class diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/KotlinStyleJpqlPropertyIntrospectorTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/KotlinStyleJpqlPropertyIntrospectorTest.kt new file mode 100644 index 000000000..2a36fd455 --- /dev/null +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/introspector/impl/KotlinStyleJpqlPropertyIntrospectorTest.kt @@ -0,0 +1,80 @@ +package com.linecorp.kotlinjdsl.render.jpql.introspector.impl + +import io.mockk.mockkClass +import org.assertj.core.api.WithAssertions +import org.junit.jupiter.api.Test +import kotlin.reflect.KProperty0 + +class KotlinStyleJpqlPropertyIntrospectorTest : WithAssertions { + private val sut = KotlinStyleJpqlPropertyIntrospector() + + @Test + fun `introspect() returns the property name, when the property is KProperty1`() { + // given + val property = EntityClass1::property1 + + // when + val actual = sut.introspect(property) + + // then + assertThat(actual?.name).isEqualTo("property1") + } + + @Test + fun `introspect() returns the property name without prefix, when the getter name starts with get`() { + // given + val property = EntityClass1::getProperty2 + + // when + val actual = sut.introspect(property) + + // then + assertThat(actual?.name).isEqualTo("property2") + } + + @Test + fun `introspect() returns the property name with is, when the getter name starts with is`() { + // given + val property = EntityClass1::isProperty3 + + // when + val actual = sut.introspect(property) + + // then + assertThat(actual?.name).isEqualTo("isProperty3") + } + + @Test + fun `introspect() returns the getter name, when the getter name does not start with get or is`() { + // given + val property = EntityClass1::someProperty + + // when + val actual = sut.introspect(property) + + // then + assertThat(actual?.name).isEqualTo("someProperty") + } + + @Test + fun `introspect() returns null, when the property is not KProperty1 or KFunction1`() { + // given + val property = mockkClass(KProperty0::class) + + // when + val actual = sut.introspect(property) + + // then + assertThat(actual?.name).isNull() + } + + private class EntityClass1 { + val property1: Long = 0 + + fun getProperty2(): Long = 100 + + fun isProperty3(): Boolean = true + + fun someProperty(): String = "someProperty" + } +} diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlEntityPropertySerializerTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlEntityPropertySerializerTest.kt index 2dcaa0736..d89a63203 100644 --- a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlEntityPropertySerializerTest.kt +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlEntityPropertySerializerTest.kt @@ -5,13 +5,17 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.path.Paths import com.linecorp.kotlinjdsl.querymodel.jpql.path.impl.JpqlEntityProperty import com.linecorp.kotlinjdsl.render.TestRenderContext import com.linecorp.kotlinjdsl.render.jpql.entity.book.Book +import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlPropertyDescription +import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlRenderIntrospector 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.every import io.mockk.impl.annotations.MockK import io.mockk.verifySequence import org.assertj.core.api.WithAssertions import org.junit.jupiter.api.Test +import kotlin.reflect.KCallable @JpqlSerializerTest class JpqlEntityPropertySerializerTest : WithAssertions { @@ -20,11 +24,17 @@ class JpqlEntityPropertySerializerTest : WithAssertions { @MockK private lateinit var writer: JpqlWriter + @MockK + private lateinit var introspector: JpqlRenderIntrospector + @MockK private lateinit var serializer: JpqlRenderSerializer private val entity1 = Entities.entity(Book::class, "book01") private val property1 = Book::price + private val propertyDescription1 = object : JpqlPropertyDescription { + override val name = property1.name + } @Test fun handledType() { @@ -38,11 +48,13 @@ class JpqlEntityPropertySerializerTest : WithAssertions { @Test fun serialize() { // given + every { introspector.introspect(any>()) } returns propertyDescription1 + val part = Paths.path( entity1, property1, ) - val context = TestRenderContext(serializer) + val context = TestRenderContext(introspector, serializer) // when sut.serialize(part as JpqlEntityProperty<*, *>, writer, context) @@ -51,7 +63,7 @@ class JpqlEntityPropertySerializerTest : WithAssertions { verifySequence { writer.write(entity1.alias) writer.write(".") - writer.write(property1.name) + writer.write(propertyDescription1.name) } } } diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlEntitySerializerTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlEntitySerializerTest.kt index 9835dd9c8..c022a7bad 100644 --- a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlEntitySerializerTest.kt +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlEntitySerializerTest.kt @@ -18,6 +18,7 @@ import io.mockk.verifySequence import org.assertj.core.api.WithAssertions import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest +import kotlin.reflect.KClass @JpqlSerializerTest internal class JpqlEntitySerializerTest : WithAssertions { @@ -57,7 +58,7 @@ internal class JpqlEntitySerializerTest : WithAssertions { clause: JpqlRenderClause, ) { // given - every { introspector.introspect(any()) } returns entityDescription1 + every { introspector.introspect(any>()) } returns entityDescription1 val part = Entities.entity(Book::class, alias1) val context = TestRenderContext(introspector, statement, clause) diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlEntityTreatSerializerTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlEntityTreatSerializerTest.kt index 53c04122c..d4c98a7bb 100644 --- a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlEntityTreatSerializerTest.kt +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlEntityTreatSerializerTest.kt @@ -19,6 +19,7 @@ import io.mockk.verifySequence import org.assertj.core.api.WithAssertions import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest +import kotlin.reflect.KClass @JpqlSerializerTest class JpqlEntityTreatSerializerTest : WithAssertions { @@ -58,7 +59,7 @@ class JpqlEntityTreatSerializerTest : WithAssertions { clause: JpqlRenderClause, ) { // given - every { introspector.introspect(any()) } returns entityDescription1 + every { introspector.introspect(any>()) } returns entityDescription1 val part = Entities.treat( entity1, @@ -93,7 +94,7 @@ class JpqlEntityTreatSerializerTest : WithAssertions { clause: JpqlRenderClause, ) { // given - every { introspector.introspect(any()) } returns entityDescription1 + every { introspector.introspect(any>()) } returns entityDescription1 val part = Entities.treat( entity1, diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlFunctionSerializerTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlFunctionSerializerTest.kt index 7b6f19a73..aa5f4a65e 100644 --- a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlFunctionSerializerTest.kt +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlFunctionSerializerTest.kt @@ -59,7 +59,9 @@ class JpqlFunctionSerializerTest : WithAssertions { verifySequence { writer.write("FUNCTION") writer.writeParentheses(any()) + writer.write("'") writer.write(functionName1) + writer.write("'") writer.write(",") writer.write(" ") writer.writeEach(expressions, ", ", any()) @@ -86,7 +88,9 @@ class JpqlFunctionSerializerTest : WithAssertions { verifySequence { writer.write("FUNCTION") writer.writeParentheses(any()) + writer.write("'") writer.write(functionName1) + writer.write("'") } } } diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlPathPropertySerializerTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlPathPropertySerializerTest.kt index ee3eabc37..cc8cc1aeb 100644 --- a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlPathPropertySerializerTest.kt +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlPathPropertySerializerTest.kt @@ -5,13 +5,17 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.path.impl.JpqlPathProperty import com.linecorp.kotlinjdsl.render.TestRenderContext import com.linecorp.kotlinjdsl.render.jpql.entity.book.Book import com.linecorp.kotlinjdsl.render.jpql.entity.book.Isbn +import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlPropertyDescription +import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlRenderIntrospector 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.every import io.mockk.impl.annotations.MockK import io.mockk.verifySequence import org.assertj.core.api.WithAssertions import org.junit.jupiter.api.Test +import kotlin.reflect.KCallable @JpqlSerializerTest class JpqlPathPropertySerializerTest : WithAssertions { @@ -20,6 +24,9 @@ class JpqlPathPropertySerializerTest : WithAssertions { @MockK private lateinit var writer: JpqlWriter + @MockK + private lateinit var introspector: JpqlRenderIntrospector + @MockK private lateinit var serializer: JpqlRenderSerializer @@ -27,6 +34,10 @@ class JpqlPathPropertySerializerTest : WithAssertions { private val property1 = Isbn::value + private val propertyDescription1 = object : JpqlPropertyDescription { + override val name = property1.name + } + @Test fun handledType() { // when @@ -39,11 +50,13 @@ class JpqlPathPropertySerializerTest : WithAssertions { @Test fun serialize() { // given + every { introspector.introspect(any>()) } returns propertyDescription1 + val part = Paths.path( path1, property1, ) - val context = TestRenderContext(serializer) + val context = TestRenderContext(introspector, serializer) // when sut.serialize(part as JpqlPathProperty<*, *>, writer, context) @@ -52,7 +65,7 @@ class JpqlPathPropertySerializerTest : WithAssertions { verifySequence { serializer.serialize(path1, writer, context) writer.write(".") - writer.write(property1.name) + writer.write(propertyDescription1.name) } } } diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlPathTreatSerializerTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlPathTreatSerializerTest.kt index 6c42ec983..ebf723642 100644 --- a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlPathTreatSerializerTest.kt +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlPathTreatSerializerTest.kt @@ -15,6 +15,7 @@ import io.mockk.impl.annotations.MockK import io.mockk.verifySequence import org.assertj.core.api.WithAssertions import org.junit.jupiter.api.Test +import kotlin.reflect.KClass @JpqlSerializerTest class JpqlPathTreatSerializerTest : WithAssertions { @@ -47,7 +48,7 @@ class JpqlPathTreatSerializerTest : WithAssertions { @Test fun serialize() { // given - every { introspector.introspect(any()) } returns entityDescription1 + every { introspector.introspect(any>()) } returns entityDescription1 val part = Paths.treat( path1, diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimBothSerializerTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimBothSerializerTest.kt new file mode 100644 index 000000000..ccb1e230d --- /dev/null +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimBothSerializerTest.kt @@ -0,0 +1,84 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTrimBoth +import com.linecorp.kotlinjdsl.render.TestRenderContext +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 JpqlTrimBothSerializerTest : WithAssertions { + private val sut = JpqlTrimBothSerializer() + + @MockK + private lateinit var writer: JpqlWriter + + @MockK + private lateinit var serializer: JpqlRenderSerializer + + private val charExpression1 = Expressions.value('c') + private val stringExpression1 = Expressions.value("string1") + + @Test + fun handledType() { + // when + val actual = sut.handledType() + + // then + assertThat(actual).isEqualTo(JpqlTrimBoth::class) + } + + @Test + fun `serialize() draws the BOTH and the FROM, when the character is null`() { + // given + val part = Expressions.trimBoth( + value = stringExpression1, + ) + val context = TestRenderContext(serializer) + + // when + sut.serialize(part as JpqlTrimBoth, writer, context) + + // then + verifySequence { + writer.write("TRIM") + writer.writeParentheses(any()) + writer.write("BOTH") + writer.write(" ") + writer.write("FROM") + writer.write(" ") + serializer.serialize(stringExpression1, writer, context) + } + } + + @Test + fun `serialize() draws the BOTH, the character and the FROM, when the character is not null`() { + // given + val part = Expressions.trimBoth( + character = charExpression1, + value = stringExpression1, + ) + val context = TestRenderContext(serializer) + + // when + sut.serialize(part as JpqlTrimBoth, writer, context) + + // then + verifySequence { + writer.write("TRIM") + writer.writeParentheses(any()) + writer.write("BOTH") + writer.write(" ") + serializer.serialize(charExpression1, writer, context) + writer.write(" ") + writer.write("FROM") + writer.write(" ") + serializer.serialize(stringExpression1, writer, context) + } + } +} diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimLeadingSerializerTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimLeadingSerializerTest.kt new file mode 100644 index 000000000..f75065d92 --- /dev/null +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimLeadingSerializerTest.kt @@ -0,0 +1,84 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTrimLeading +import com.linecorp.kotlinjdsl.render.TestRenderContext +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 JpqlTrimLeadingSerializerTest : WithAssertions { + private val sut = JpqlTrimLeadingSerializer() + + @MockK + private lateinit var writer: JpqlWriter + + @MockK + private lateinit var serializer: JpqlRenderSerializer + + private val charExpression1 = Expressions.value('c') + private val stringExpression1 = Expressions.value("string1") + + @Test + fun handledType() { + // when + val actual = sut.handledType() + + // then + assertThat(actual).isEqualTo(JpqlTrimLeading::class) + } + + @Test + fun `serialize() draws the LEADING and the FROM, when the character is null`() { + // given + val part = Expressions.trimLeading( + value = stringExpression1, + ) + val context = TestRenderContext(serializer) + + // when + sut.serialize(part as JpqlTrimLeading, writer, context) + + // then + verifySequence { + writer.write("TRIM") + writer.writeParentheses(any()) + writer.write("LEADING") + writer.write(" ") + writer.write("FROM") + writer.write(" ") + serializer.serialize(stringExpression1, writer, context) + } + } + + @Test + fun `serialize() draws the LEADING, the character and the FROM, when the character is not null`() { + // given + val part = Expressions.trimLeading( + character = charExpression1, + value = stringExpression1, + ) + val context = TestRenderContext(serializer) + + // when + sut.serialize(part as JpqlTrimLeading, writer, context) + + // then + verifySequence { + writer.write("TRIM") + writer.writeParentheses(any()) + writer.write("LEADING") + writer.write(" ") + serializer.serialize(charExpression1, writer, context) + writer.write(" ") + writer.write("FROM") + writer.write(" ") + serializer.serialize(stringExpression1, writer, context) + } + } +} diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimSerializerTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimSerializerTest.kt new file mode 100644 index 000000000..236a8f4e5 --- /dev/null +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimSerializerTest.kt @@ -0,0 +1,78 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTrim +import com.linecorp.kotlinjdsl.render.TestRenderContext +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 JpqlTrimSerializerTest : WithAssertions { + private val sut = JpqlTrimSerializer() + + @MockK + private lateinit var writer: JpqlWriter + + @MockK + private lateinit var serializer: JpqlRenderSerializer + + private val charExpression1 = Expressions.value('c') + private val stringExpression1 = Expressions.value("string1") + + @Test + fun handledType() { + // when + val actual = sut.handledType() + + // then + assertThat(actual).isEqualTo(JpqlTrim::class) + } + + @Test + fun serialize() { + // given + val part = Expressions.trim( + value = stringExpression1, + ) + val context = TestRenderContext(serializer) + + // when + sut.serialize(part as JpqlTrim, writer, context) + + // then + verifySequence { + writer.write("TRIM") + writer.writeParentheses(any()) + serializer.serialize(stringExpression1, writer, context) + } + } + + @Test + fun `serialize() draws the character and the FROM, when the character is not null`() { + // given + val part = Expressions.trim( + character = charExpression1, + value = stringExpression1, + ) + val context = TestRenderContext(serializer) + + // when + sut.serialize(part as JpqlTrim, writer, context) + + // then + verifySequence { + writer.write("TRIM") + writer.writeParentheses(any()) + serializer.serialize(charExpression1, writer, context) + writer.write(" ") + writer.write("FROM") + writer.write(" ") + serializer.serialize(stringExpression1, writer, context) + } + } +} diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimTrailingSerializerTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimTrailingSerializerTest.kt new file mode 100644 index 000000000..a1a6dc34b --- /dev/null +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlTrimTrailingSerializerTest.kt @@ -0,0 +1,84 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlTrimTrailing +import com.linecorp.kotlinjdsl.render.TestRenderContext +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 JpqlTrimTrailingSerializerTest : WithAssertions { + private val sut = JpqlTrimTrailingSerializer() + + @MockK + private lateinit var writer: JpqlWriter + + @MockK + private lateinit var serializer: JpqlRenderSerializer + + private val charExpression1 = Expressions.value('c') + private val stringExpression1 = Expressions.value("string1") + + @Test + fun handledType() { + // when + val actual = sut.handledType() + + // then + assertThat(actual).isEqualTo(JpqlTrimTrailing::class) + } + + @Test + fun `serialize() draws the TRAILING and the FROM, when the character is null`() { + // given + val part = Expressions.trimTrailing( + value = stringExpression1, + ) + val context = TestRenderContext(serializer) + + // when + sut.serialize(part as JpqlTrimTrailing, writer, context) + + // then + verifySequence { + writer.write("TRIM") + writer.writeParentheses(any()) + writer.write("TRAILING") + writer.write(" ") + writer.write("FROM") + writer.write(" ") + serializer.serialize(stringExpression1, writer, context) + } + } + + @Test + fun `serialize() draws the TRAILING, the character and the FROM, when the character is not null`() { + // given + val part = Expressions.trimTrailing( + character = charExpression1, + value = stringExpression1, + ) + val context = TestRenderContext(serializer) + + // when + sut.serialize(part as JpqlTrimTrailing, writer, context) + + // then + verifySequence { + writer.write("TRIM") + writer.writeParentheses(any()) + writer.write("TRAILING") + writer.write(" ") + serializer.serialize(charExpression1, writer, context) + writer.write(" ") + writer.write("FROM") + writer.write(" ") + serializer.serialize(stringExpression1, writer, context) + } + } +} diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlValueSerializerTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlValueSerializerTest.kt index 67450a9bb..494539b2f 100644 --- a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlValueSerializerTest.kt +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlValueSerializerTest.kt @@ -13,6 +13,7 @@ import io.mockk.impl.annotations.MockK import io.mockk.verifySequence import org.assertj.core.api.WithAssertions import org.junit.jupiter.api.Test +import kotlin.reflect.KClass @JpqlSerializerTest class JpqlValueSerializerTest : WithAssertions { @@ -59,7 +60,7 @@ class JpqlValueSerializerTest : WithAssertions { @Test fun `serialize() draws entity name, when value is KClass`() { // given - every { introspector.introspect(any()) } returns entityDescription1 + every { introspector.introspect(any>()) } returns entityDescription1 val part = Expressions.value( Book::class, diff --git a/src/main/kotlin/com/linecorp/kotlinjdsl/property/PropertyUtils.kt b/src/main/kotlin/com/linecorp/kotlinjdsl/property/PropertyUtils.kt index ccbe84052..461adee71 100644 --- a/src/main/kotlin/com/linecorp/kotlinjdsl/property/PropertyUtils.kt +++ b/src/main/kotlin/com/linecorp/kotlinjdsl/property/PropertyUtils.kt @@ -2,6 +2,7 @@ package com.linecorp.kotlinjdsl.property import kotlin.jvm.internal.CallableReference import kotlin.reflect.KClass +import kotlin.reflect.KFunction1 import kotlin.reflect.KProperty1 object PropertyUtils { @@ -9,4 +10,9 @@ object PropertyUtils { @Suppress("UNCHECKED_CAST") return (property as CallableReference).owner as KClass } + + fun getOwner(property: KFunction1): KClass { + @Suppress("UNCHECKED_CAST") + return (property as CallableReference).owner as KClass + } } diff --git a/support/spring-batch-javax/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/batch/javax/autoconfigure/KotlinJdslAutoConfiguration.kt b/support/spring-batch-javax/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/batch/javax/autoconfigure/KotlinJdslAutoConfiguration.kt index 860dc230d..a11d7b2fc 100644 --- a/support/spring-batch-javax/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/batch/javax/autoconfigure/KotlinJdslAutoConfiguration.kt +++ b/support/spring-batch-javax/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/batch/javax/autoconfigure/KotlinJdslAutoConfiguration.kt @@ -3,6 +3,7 @@ package com.linecorp.kotlinjdsl.support.spring.batch.javax.autoconfigure import com.linecorp.kotlinjdsl.render.RenderContext import com.linecorp.kotlinjdsl.render.jpql.JpqlRenderContext import com.linecorp.kotlinjdsl.render.jpql.JpqlRenderModule +import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlIntrospector import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlSerializer import com.linecorp.kotlinjdsl.support.spring.batch.javax.item.database.orm.KotlinJdslQueryProviderFactory import org.springframework.boot.autoconfigure.AutoConfiguration @@ -15,14 +16,22 @@ import org.springframework.context.annotation.Bean open class KotlinJdslAutoConfiguration { @Bean @ConditionalOnMissingBean - open fun jpqlRenderContext(serializers: List>): JpqlRenderContext { - val userDefinedSerializers = object : JpqlRenderModule { + open fun jpqlRenderContext( + serializers: List>, + introspectors: List, + ): JpqlRenderContext { + val userDefinedModule = object : JpqlRenderModule { override fun setupModule(context: JpqlRenderModule.SetupContext) { context.addAllSerializer(serializers.reversed()) + + introspectors.reversed().forEach { + context.prependIntrospector(it) + } } } - return JpqlRenderContext().registerModules(userDefinedSerializers) + return JpqlRenderContext() + .registerModules(userDefinedModule) } @Bean diff --git a/support/spring-batch/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/batch/autoconfigure/KotlinJdslAutoConfiguration.kt b/support/spring-batch/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/batch/autoconfigure/KotlinJdslAutoConfiguration.kt index 7bf20121d..bb15084bb 100644 --- a/support/spring-batch/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/batch/autoconfigure/KotlinJdslAutoConfiguration.kt +++ b/support/spring-batch/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/batch/autoconfigure/KotlinJdslAutoConfiguration.kt @@ -3,6 +3,7 @@ package com.linecorp.kotlinjdsl.support.spring.batch.autoconfigure import com.linecorp.kotlinjdsl.render.RenderContext import com.linecorp.kotlinjdsl.render.jpql.JpqlRenderContext import com.linecorp.kotlinjdsl.render.jpql.JpqlRenderModule +import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlIntrospector import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlSerializer import com.linecorp.kotlinjdsl.support.spring.batch.item.database.orm.KotlinJdslQueryProviderFactory import org.springframework.boot.autoconfigure.AutoConfiguration @@ -15,14 +16,22 @@ import org.springframework.context.annotation.Bean open class KotlinJdslAutoConfiguration { @Bean @ConditionalOnMissingBean - open fun jpqlRenderContext(serializers: List>): JpqlRenderContext { - val userDefinedSerializers = object : JpqlRenderModule { + open fun jpqlRenderContext( + serializers: List>, + introspectors: List, + ): JpqlRenderContext { + val userDefinedModule = object : JpqlRenderModule { override fun setupModule(context: JpqlRenderModule.SetupContext) { context.addAllSerializer(serializers.reversed()) + + introspectors.reversed().forEach { + context.prependIntrospector(it) + } } } - return JpqlRenderContext().registerModules(userDefinedSerializers) + return JpqlRenderContext() + .registerModules(userDefinedModule) } @Bean diff --git a/support/spring-data-jpa-javax/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/javax/autoconfigure/KotlinJdslAutoConfiguration.kt b/support/spring-data-jpa-javax/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/javax/autoconfigure/KotlinJdslAutoConfiguration.kt index 3840e882d..144b98ad1 100644 --- a/support/spring-data-jpa-javax/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/javax/autoconfigure/KotlinJdslAutoConfiguration.kt +++ b/support/spring-data-jpa-javax/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/javax/autoconfigure/KotlinJdslAutoConfiguration.kt @@ -4,6 +4,7 @@ import com.linecorp.kotlinjdsl.SinceJdsl import com.linecorp.kotlinjdsl.render.RenderContext import com.linecorp.kotlinjdsl.render.jpql.JpqlRenderContext import com.linecorp.kotlinjdsl.render.jpql.JpqlRenderModule +import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlIntrospector import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlSerializer import com.linecorp.kotlinjdsl.support.spring.data.jpa.javax.repository.KotlinJdslJpqlExecutor import com.linecorp.kotlinjdsl.support.spring.data.jpa.javax.repository.KotlinJdslJpqlExecutorImpl @@ -20,15 +21,22 @@ open class KotlinJdslAutoConfiguration { @Bean @ConditionalOnMissingBean @SinceJdsl("3.0.0") - open fun jpqlRenderContext(serializers: List>): JpqlRenderContext { - val userDefinedSerializers = object : JpqlRenderModule { + open fun jpqlRenderContext( + serializers: List>, + introspectors: List, + ): JpqlRenderContext { + val userDefinedModule = object : JpqlRenderModule { override fun setupModule(context: JpqlRenderModule.SetupContext) { context.addAllSerializer(serializers.reversed()) + + introspectors.reversed().forEach { + context.prependIntrospector(it) + } } } return JpqlRenderContext() - .registerModules(userDefinedSerializers) + .registerModules(userDefinedModule) } @Bean diff --git a/support/spring-data-jpa/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/autoconfigure/KotlinJdslAutoConfiguration.kt b/support/spring-data-jpa/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/autoconfigure/KotlinJdslAutoConfiguration.kt index f5454b4cb..ee9415a7a 100644 --- a/support/spring-data-jpa/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/autoconfigure/KotlinJdslAutoConfiguration.kt +++ b/support/spring-data-jpa/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/autoconfigure/KotlinJdslAutoConfiguration.kt @@ -4,6 +4,7 @@ import com.linecorp.kotlinjdsl.SinceJdsl import com.linecorp.kotlinjdsl.render.RenderContext import com.linecorp.kotlinjdsl.render.jpql.JpqlRenderContext import com.linecorp.kotlinjdsl.render.jpql.JpqlRenderModule +import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlIntrospector import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlSerializer import com.linecorp.kotlinjdsl.support.spring.data.jpa.repository.KotlinJdslJpqlExecutor import com.linecorp.kotlinjdsl.support.spring.data.jpa.repository.KotlinJdslJpqlExecutorImpl @@ -20,15 +21,22 @@ open class KotlinJdslAutoConfiguration { @Bean @ConditionalOnMissingBean @SinceJdsl("3.0.0") - open fun jpqlRenderContext(serializers: List>): JpqlRenderContext { - val userDefinedSerializers = object : JpqlRenderModule { + open fun jpqlRenderContext( + serializers: List>, + introspectors: List, + ): JpqlRenderContext { + val userDefinedModule = object : JpqlRenderModule { override fun setupModule(context: JpqlRenderModule.SetupContext) { context.addAllSerializer(serializers.reversed()) + + introspectors.reversed().forEach { + context.prependIntrospector(it) + } } } return JpqlRenderContext() - .registerModules(userDefinedSerializers) + .registerModules(userDefinedModule) } @Bean