diff --git a/docs/en/jpql-with-kotlin-jdsl/expressions.md b/docs/en/jpql-with-kotlin-jdsl/expressions.md index 93e0ee063..c54e39805 100644 --- a/docs/en/jpql-with-kotlin-jdsl/expressions.md +++ b/docs/en/jpql-with-kotlin-jdsl/expressions.md @@ -199,6 +199,10 @@ Kotlin JDSL provides a series of functions to support built-in functions in JPA. * UPPER (upper) * LENGTH (length) * LOCATE (locate) +* CAST (cast) - *Added in JPA 3.2* +* LEFT (left) - *Added in JPA 3.2* +* RIGHT (right) - *Added in JPA 3.2* +* REPLACE (replace) - *Added in JPA 3.2* ```kotlin concat(path(Book::title), literal(":"), path(Book::imageUrl)) @@ -215,6 +219,25 @@ upper(path(Book::title)) length(path(Book::title)) locate("Book", path(Book::title)) + +cast(path(Book::price)).asString() +cast(path(Book::authorId)).asInt() +cast(path(Book::authorId)).asLong() +cast(path(Book::authorId)).asDouble() +cast(path(Book::authorId)).asFloat() + +left(path(Book::title), 3) +left(path(Book::title), literal(3)) + +right(path(Book::title), 3) +right(path(Book::title), literal(3)) + +replace(path(Book::title), "old", "new") +replace(path(Book::title), stringLiteral("old"), "new") +replace(path(Book::title), path(Book::name), "new") +replace(path(Book::title), "old", stringLiteral("new")) +replace(path(Book::title), "old", path(Book::name)) +replace(path(Book::title), literal("old"), literal("new")) ``` ### Arithmetic functions diff --git a/docs/ko/jpql-with-kotlin-jdsl/expressions.md b/docs/ko/jpql-with-kotlin-jdsl/expressions.md index ecec3978f..6376a7717 100644 --- a/docs/ko/jpql-with-kotlin-jdsl/expressions.md +++ b/docs/ko/jpql-with-kotlin-jdsl/expressions.md @@ -197,6 +197,10 @@ Kotlin JDSL은 JPA에서 제공하는 여러 함수들을 지원하기 위한 * UPPER (upper) * LENGTH (length) * LOCATE (locate) +* CAST (cast) - *JPA 3.2에 추가됨* +* LEFT (left) - *JPA 3.2에 추가됨* +* RIGHT (right) - *JPA 3.2에 추가됨* +* REPLACE (replace) - *JPA 3.2에 추가됨* ```kotlin concat(path(Book::title), literal(":"), path(Book::imageUrl)) @@ -213,6 +217,25 @@ upper(path(Book::title)) length(path(Book::title)) locate("Book", path(Book::title)) + +cast(path(Book::price)).asString() +cast(path(Book::authorId)).asInt() +cast(path(Book::authorId)).asLong() +cast(path(Book::authorId)).asDouble() +cast(path(Book::authorId)).asFloat() + +left(path(Book::title), 3) +left(path(Book::title), literal(3)) + +right(path(Book::title), 3) +right(path(Book::title), literal(3)) + +replace(path(Book::title), "old", "new") +replace(path(Book::title), stringLiteral("old"), "new") +replace(path(Book::title), path(Book::name), "new") +replace(path(Book::title), "old", stringLiteral("new")) +replace(path(Book::title), "old", path(Book::name)) +replace(path(Book::title), literal("old"), literal("new")) ``` ### Arithmetic functions 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 ecaeb800a..86bbd3ac9 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,9 +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.CastStep +import com.linecorp.kotlinjdsl.dsl.jpql.expression.CastStepToString 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.JpqlCastStep +import com.linecorp.kotlinjdsl.dsl.jpql.expression.impl.JpqlCastStepToString 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 @@ -1737,6 +1741,102 @@ open class Jpql : JpqlDsl { ) } + /** + * Creates a step to cast a string expression to another type. + */ + @SinceJdsl("3.6.0") + fun cast(value: Expressionable): CastStep { + return JpqlCastStep(value.toExpression()) + } + + /** + * Creates a step to cast a scalar expression to a string. + */ + @SinceJdsl("3.6.0") + fun cast(value: Expressionable): CastStepToString { + return JpqlCastStepToString(value.toExpression()) + } + + /** + * Creates an expression that returns the leftmost count characters from a string. + */ + @SinceJdsl("3.6.0") + fun left(value: Expressionable, len: Expressionable): Expression { + return Expressions.left(value.toExpression(), len.toExpression()) + } + + /** + * Creates an expression that returns the leftmost count characters from a string. + */ + @SinceJdsl("3.6.0") + fun left(value: Expressionable, len: Int): Expression { + return left(value.toExpression(), intLiteral(len)) + } + + /** + * Creates an expression that returns the rightmost count characters from a string. + */ + @SinceJdsl("3.6.0") + fun right(value: Expressionable, len: Expressionable): Expression { + return Expressions.right(value.toExpression(), len.toExpression()) + } + + /** + * Creates an expression that returns the rightmost count characters from a string. + */ + @SinceJdsl("3.6.0") + fun right(value: Expressionable, len: Int): Expression { + return right(value.toExpression(), intLiteral(len)) + } + + /** + * Creates an expression that replaces all occurrences of a search string with a replacement string. + */ + @SinceJdsl("3.6.0") + fun replace( + value: Expressionable, + substring: Expressionable, + replacement: Expressionable, + ): Expression { + return Expressions.replace(value.toExpression(), substring.toExpression(), replacement.toExpression()) + } + + /** + * Creates an expression that replaces all occurrences of a search string with a replacement string. + */ + @SinceJdsl("3.6.0") + fun replace( + value: Expressionable, + substring: String, + replacement: String, + ): Expression { + return replace(value.toExpression(), stringLiteral(substring), stringLiteral(replacement)) + } + + /** + * Creates an expression that replaces all occurrences of a search string with a replacement string. + */ + @SinceJdsl("3.6.0") + fun replace( + value: Expressionable, + substring: Expressionable, + replacement: String, + ): Expression { + return replace(value.toExpression(), substring, stringLiteral(replacement)) + } + + /** + * Creates an expression that replaces all occurrences of a search string with a replacement string. + */ + @SinceJdsl("3.6.0") + fun replace( + value: Expressionable, + substring: String, + replacement: Expressionable, + ): Expression { + return replace(value.toExpression(), substring, replacement) + } + /** * Creates an expression that represents predefined database functions and user-defined database functions. */ diff --git a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/CastStep.kt b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/CastStep.kt new file mode 100644 index 000000000..ce6f8fdd1 --- /dev/null +++ b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/CastStep.kt @@ -0,0 +1,35 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression + +import com.linecorp.kotlinjdsl.SinceJdsl +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressionable + +/** + * A step to specify the target type for a cast from a string expression. + * This corresponds to the BNF: CAST(string_expression AS {type}) + */ +@SinceJdsl("3.6.0") +interface CastStep { + /** + * Casts the expression to an INTEGER. + */ + @SinceJdsl("3.6.0") + fun asInteger(): Expressionable + + /** + * Casts the expression to a LONG. + */ + @SinceJdsl("3.6.0") + fun asLong(): Expressionable + + /** + * Casts the expression to a FLOAT. + */ + @SinceJdsl("3.6.0") + fun asFloat(): Expressionable + + /** + * Casts the expression to a DOUBLE. + */ + @SinceJdsl("3.6.0") + fun asDouble(): Expressionable +} diff --git a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/CastStepToString.kt b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/CastStepToString.kt new file mode 100644 index 000000000..dcf25d4d5 --- /dev/null +++ b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/CastStepToString.kt @@ -0,0 +1,17 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression + +import com.linecorp.kotlinjdsl.SinceJdsl +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressionable + +/** + * A step to cast a scalar expression to a STRING. + * This corresponds to the BNF: CAST(scalar_expression AS STRING) + */ +@SinceJdsl("3.6.0") +interface CastStepToString { + /** + * Casts the expression to a STRING. + */ + @SinceJdsl("3.6.0") + fun asString(): Expressionable +} diff --git a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/JpqlCastStep.kt b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/JpqlCastStep.kt new file mode 100644 index 000000000..67939bf23 --- /dev/null +++ b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/JpqlCastStep.kt @@ -0,0 +1,27 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression.impl + +import com.linecorp.kotlinjdsl.dsl.jpql.expression.CastStep +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressionable +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlCast + +@PublishedApi +internal data class JpqlCastStep( + private val expression: Expression, +) : CastStep { + override fun asInteger(): Expressionable { + return JpqlCast(expression, Int::class) + } + + override fun asLong(): Expressionable { + return JpqlCast(expression, Long::class) + } + + override fun asFloat(): Expressionable { + return JpqlCast(expression, Float::class) + } + + override fun asDouble(): Expressionable { + return JpqlCast(expression, Double::class) + } +} diff --git a/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/JpqlCastStepToString.kt b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/JpqlCastStepToString.kt new file mode 100644 index 000000000..1812e689a --- /dev/null +++ b/dsl/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/impl/JpqlCastStepToString.kt @@ -0,0 +1,15 @@ +package com.linecorp.kotlinjdsl.dsl.jpql.expression.impl + +import com.linecorp.kotlinjdsl.dsl.jpql.expression.CastStepToString +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressionable +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlCast + +@PublishedApi +internal data class JpqlCastStepToString( + private val expression: Expression, +) : CastStepToString { + override fun asString(): Expressionable { + return JpqlCast(expression, String::class) + } +} diff --git a/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/ExpressionDslTest.kt b/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/ExpressionDslTest.kt index f7b18100a..0d13fe122 100644 --- a/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/ExpressionDslTest.kt +++ b/dsl/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/dsl/jpql/expression/ExpressionDslTest.kt @@ -44,4 +44,212 @@ class ExpressionDslTest : WithAssertions { assertThat(actual).isEqualTo(expected) } + + @Test + fun `cast() with a string expression as Integer`() { + // when + val expression = queryPart { + cast(expression(String::class, alias1)).asInteger() + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.cast( + Expressions.expression(String::class, alias1), + Int::class, + ) + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun `cast() with a string expression as Long`() { + // when + val expression = queryPart { + cast(expression(String::class, alias1)).asLong() + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.cast( + Expressions.expression(String::class, alias1), + Long::class, + ) + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun `cast() with a string expression as Float`() { + // when + val expression = queryPart { + cast(expression(String::class, alias1)).asFloat() + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.cast( + Expressions.expression(String::class, alias1), + Float::class, + ) + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun `cast() with a string expression as Double`() { + // when + val expression = queryPart { + cast(expression(String::class, alias1)).asDouble() + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.cast( + Expressions.expression(String::class, alias1), + Double::class, + ) + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun `cast() with scalar expression as String`() { + // when + val expression = queryPart { + cast(expression(Int::class, alias1)).asString() + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.cast( + Expressions.expression(Int::class, alias1), + String::class, + ) + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun `left() with two expressions`() { + // when + val expression = queryPart { + left(expression(String::class, alias1), expression(Int::class, "alias2")) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.left( + Expressions.expression(String::class, alias1), + Expressions.expression(Int::class, "alias2"), + ) + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun `left() literal with two expressions`() { + // when + val expression = queryPart { + left(expression(String::class, alias1), 1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.left( + Expressions.expression(String::class, alias1), + Expressions.intLiteral(1), + ) + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun `right() with two expressions`() { + // when + val expression = queryPart { + right(expression(String::class, alias1), expression(Int::class, "alias2")) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.right( + Expressions.expression(String::class, alias1), + Expressions.expression(Int::class, "alias2"), + ) + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun `right() literal with two expressions`() { + // when + val expression = queryPart { + right(expression(String::class, alias1), 1) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.right( + Expressions.expression(String::class, alias1), + Expressions.intLiteral(1), + ) + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun `replace() with three expressions`() { + // when + val expression = queryPart { + replace( + expression(String::class, alias1), + expression(String::class, "alias2"), + expression(String::class, "alias3"), + ) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.replace( + Expressions.expression(String::class, alias1), + Expressions.expression(String::class, "alias2"), + Expressions.expression(String::class, "alias3"), + ) + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun `replace() literal with three expressions`() { + // when + val expression = queryPart { + replace( + expression(String::class, alias1), + "string1", + "string2", + ) + }.toExpression() + + val actual: Expression = expression // for type check + + // then + val expected = Expressions.replace( + Expressions.expression(String::class, alias1), + Expressions.stringLiteral("string1"), + Expressions.stringLiteral("string2"), + ) + + assertThat(actual).isEqualTo(expected) + } } 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 d48a2f5cd..02396dd2b 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 @@ -7,6 +7,7 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlAliasedExpres import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlAvg import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlCaseValue import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlCaseWhen +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlCast import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlCeiling import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlCoalesce import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlConcat @@ -23,6 +24,7 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlExpressionPar import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlFloor import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlFunctionExpression import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlId +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlLeft import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlLength import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlLiteral import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlLn @@ -42,6 +44,8 @@ import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlParam import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlPathType import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlPlus import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlPower +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlReplace +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlRight import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlRound import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlSign import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlSize @@ -725,6 +729,50 @@ object Expressions { return JpqlLocate(substring, string, start) } + /** + * Creates an expression that represents the casting of a value to a different type. + */ + @SinceJdsl("3.6.0") + fun cast(value: Expression<*>, type: KClass): Expression { + return JpqlCast(value, type) + } + + /** + * Creates an expression that returns the leftmost count characters from a string. + */ + @SinceJdsl("3.6.0") + fun left(value: Expression, len: Expression): Expression { + return JpqlLeft(value, len) + } + + /** + * Creates an expression that returns the leftmost count characters from a string. + */ + @SinceJdsl("3.6.0") + fun left(value: Expression, len: Int): Expression { + return left(value, intLiteral(len)) + } + + /** + * Creates an expression that returns the rightmost count characters from a string. + */ + @SinceJdsl("3.6.0") + fun right(value: Expression, len: Expression): Expression { + return JpqlRight(value, len) + } + + /** + * Creates an expression that replaces all occurrences of a search string with a replacement string. + */ + @SinceJdsl("3.6.0") + fun replace( + value: Expression, + substring: Expression, + replacement: Expression, + ): Expression { + return JpqlReplace(value, substring, replacement) + } + /** * Creates an expression that represents predefined database functions and user-defined database functions. */ diff --git a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlCast.kt b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlCast.kt new file mode 100644 index 000000000..1127c5118 --- /dev/null +++ b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlCast.kt @@ -0,0 +1,11 @@ +package com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl + +import com.linecorp.kotlinjdsl.Internal +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression +import kotlin.reflect.KClass + +@Internal +data class JpqlCast( + val value: Expression<*>, + val type: KClass, +) : Expression diff --git a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlLeft.kt b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlLeft.kt new file mode 100644 index 000000000..4ddac9e23 --- /dev/null +++ b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlLeft.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 JpqlLeft internal constructor( + val value: Expression, + val length: Expression, +) : Expression diff --git a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlReplace.kt b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlReplace.kt new file mode 100644 index 000000000..332533d2c --- /dev/null +++ b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlReplace.kt @@ -0,0 +1,11 @@ +package com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl + +import com.linecorp.kotlinjdsl.Internal +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expression + +@Internal +data class JpqlReplace internal constructor( + val value: Expression, + val substring: Expression, + val replacement: Expression, +) : Expression diff --git a/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlRight.kt b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlRight.kt new file mode 100644 index 000000000..d92244bcc --- /dev/null +++ b/query-model/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/querymodel/jpql/expression/impl/JpqlRight.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 JpqlRight internal constructor( + val value: Expression, + val length: Expression, +) : Expression 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 880978f5e..3a669cf23 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 @@ -20,6 +20,7 @@ import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlAvgSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlBetweenSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlCaseValueSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlCaseWhenSerializer +import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlCastSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlCeilingSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlCoalesceSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlConcatSerializer @@ -69,6 +70,7 @@ import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlLeftAssociationFe import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlLeftAssociationJoinSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlLeftFetchJoinSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlLeftJoinSerializer +import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlLeftSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlLengthSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlLessThanAllSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlLessThanAnySerializer @@ -108,6 +110,8 @@ import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlPathTypeSerialize import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlPlusSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlPowerSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlPredicateParenthesesSerializer +import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlReplaceSerializer +import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlRightSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlRoundSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlSelectQueryExceptAllSerializer import com.linecorp.kotlinjdsl.render.jpql.serializer.impl.JpqlSelectQueryExceptSerializer @@ -289,6 +293,7 @@ private class DefaultModule : JpqlRenderModule { JpqlBetweenSerializer(), JpqlCaseValueSerializer(), JpqlCaseWhenSerializer(), + JpqlCastSerializer(), JpqlCeilingSerializer(), JpqlCoalesceSerializer(), JpqlConcatSerializer(), @@ -338,6 +343,7 @@ private class DefaultModule : JpqlRenderModule { JpqlLeftAssociationJoinSerializer(), JpqlLeftFetchJoinSerializer(), JpqlLeftJoinSerializer(), + JpqlLeftSerializer(), JpqlLengthSerializer(), JpqlLessThanAllSerializer(), JpqlLessThanAnySerializer(), @@ -377,6 +383,8 @@ private class DefaultModule : JpqlRenderModule { JpqlPlusSerializer(), JpqlPowerSerializer(), JpqlPredicateParenthesesSerializer(), + JpqlRightSerializer(), + JpqlReplaceSerializer(), JpqlRoundSerializer(), JpqlSelectQueryExceptAllSerializer(), JpqlSelectQueryExceptSerializer(), diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlCastSerializer.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlCastSerializer.kt new file mode 100644 index 000000000..f70d4f1af --- /dev/null +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlCastSerializer.kt @@ -0,0 +1,27 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.SinceJdsl +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlCast +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 + +@SinceJdsl("3.6.0") +class JpqlCastSerializer : JpqlSerializer> { + override fun handledType(): KClass> { + return JpqlCast::class + } + + override fun serialize(part: JpqlCast<*>, writer: JpqlWriter, context: RenderContext) { + val delegate = context.getValue(JpqlRenderSerializer) + + writer.write("CAST") + writer.writeParentheses { + delegate.serialize(part.value, writer, context) + writer.write(" AS ") + writer.write(part.type.simpleName!!) + } + } +} diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlLeftSerializer.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlLeftSerializer.kt new file mode 100644 index 000000000..c4a0110d7 --- /dev/null +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlLeftSerializer.kt @@ -0,0 +1,27 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.SinceJdsl +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlLeft +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 + +@SinceJdsl("3.6.0") +class JpqlLeftSerializer : JpqlSerializer { + override fun handledType(): KClass { + return JpqlLeft::class + } + + override fun serialize(part: JpqlLeft, writer: JpqlWriter, context: RenderContext) { + val delegate = context.getValue(JpqlRenderSerializer) + + writer.write("LEFT") + writer.writeParentheses { + delegate.serialize(part.value, writer, context) + writer.write(", ") + delegate.serialize(part.length, writer, context) + } + } +} diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlReplaceSerializer.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlReplaceSerializer.kt new file mode 100644 index 000000000..5a3aa9013 --- /dev/null +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlReplaceSerializer.kt @@ -0,0 +1,29 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.Internal +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlReplace +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 JpqlReplaceSerializer : JpqlSerializer { + override fun handledType(): KClass { + return JpqlReplace::class + } + + override fun serialize(part: JpqlReplace, writer: JpqlWriter, context: RenderContext) { + val delegate = context.getValue(JpqlRenderSerializer) + + writer.write("REPLACE") + writer.writeParentheses { + delegate.serialize(part.value, writer, context) + writer.write(", ") + delegate.serialize(part.substring, writer, context) + writer.write(", ") + delegate.serialize(part.replacement, writer, context) + } + } +} diff --git a/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlRightSerializer.kt b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlRightSerializer.kt new file mode 100644 index 000000000..887cd28d8 --- /dev/null +++ b/render/jpql/src/main/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlRightSerializer.kt @@ -0,0 +1,27 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.SinceJdsl +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlRight +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 + +@SinceJdsl("3.6.0") +class JpqlRightSerializer : JpqlSerializer { + override fun handledType(): KClass { + return JpqlRight::class + } + + override fun serialize(part: JpqlRight, writer: JpqlWriter, context: RenderContext) { + val delegate = context.getValue(JpqlRenderSerializer) + + writer.write("RIGHT") + writer.writeParentheses { + delegate.serialize(part.value, writer, context) + writer.write(", ") + delegate.serialize(part.length, writer, context) + } + } +} diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlCastSerializerTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlCastSerializerTest.kt new file mode 100644 index 000000000..749668b0b --- /dev/null +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlCastSerializerTest.kt @@ -0,0 +1,56 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlCast +import com.linecorp.kotlinjdsl.render.RenderContext +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.verify +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +@JpqlSerializerTest +internal class JpqlCastSerializerTest { + private val sut = JpqlCastSerializer() + + @MockK + private lateinit var writer: JpqlWriter + + @MockK + private lateinit var serializer: JpqlRenderSerializer + + @MockK + private lateinit var context: RenderContext + + @Test + fun `handledType should return Expression`() { + // when + val actual = sut.handledType() + + // then + assertThat(actual).isEqualTo(JpqlCast::class) + } + + @Test + fun `serialize should write CAST`() { + // given + val expression = Expressions.cast(Expressions.value(1), String::class) + + every { context.getValue(JpqlRenderSerializer) } returns serializer + + // when + sut.serialize(expression as JpqlCast<*>, writer, context) + + // then + verify { + writer.write("CAST") + writer.writeParentheses(any()) + serializer.serialize(expression.value, writer, context) + writer.write(" AS ") + writer.write(String::class.simpleName!!) + } + } +} diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlLeftSerializerTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlLeftSerializerTest.kt new file mode 100644 index 000000000..2c24a8ef8 --- /dev/null +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlLeftSerializerTest.kt @@ -0,0 +1,56 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlLeft +import com.linecorp.kotlinjdsl.render.RenderContext +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.verify +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +@JpqlSerializerTest +internal class JpqlLeftSerializerTest { + private val sut = JpqlLeftSerializer() + + @MockK + private lateinit var writer: JpqlWriter + + @MockK + private lateinit var serializer: JpqlRenderSerializer + + @MockK + private lateinit var context: RenderContext + + @Test + fun `handledType should return Expression`() { + // when + val actual = sut.handledType() + + // then + assertThat(actual).isEqualTo(JpqlLeft::class) + } + + @Test + fun `serialize should write LEFT`() { + // given + val expression = Expressions.left(Expressions.value("abc"), Expressions.value(1)) as JpqlLeft + + every { context.getValue(JpqlRenderSerializer) } returns serializer + + // when + sut.serialize(expression, writer, context) + + // then + verify { + writer.write("LEFT") + writer.writeParentheses(any()) + serializer.serialize(expression.value, writer, context) + writer.write(", ") + serializer.serialize(expression.length, writer, context) + } + } +} diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlReplaceSerializerTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlReplaceSerializerTest.kt new file mode 100644 index 000000000..39e715c20 --- /dev/null +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlReplaceSerializerTest.kt @@ -0,0 +1,62 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlReplace +import com.linecorp.kotlinjdsl.render.RenderContext +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.verify +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +@JpqlSerializerTest +internal class JpqlReplaceSerializerTest { + private val sut = JpqlReplaceSerializer() + + @MockK + private lateinit var writer: JpqlWriter + + @MockK + private lateinit var serializer: JpqlRenderSerializer + + @MockK + private lateinit var context: RenderContext + + @Test + fun `handledType should return Expression`() { + // when + val actual = sut.handledType() + + // then + assertThat(actual).isEqualTo(JpqlReplace::class) + } + + @Test + fun `serialize should write REPLACE`() { + // given + val expression = Expressions.replace( + Expressions.value("abc"), + Expressions.value("a"), + Expressions.value("b"), + ) as JpqlReplace + + every { context.getValue(JpqlRenderSerializer) } returns serializer + + // when + sut.serialize(expression, writer, context) + + // then + verify { + writer.write("REPLACE") + writer.writeParentheses(any()) + serializer.serialize(expression.value, writer, context) + writer.write(", ") + serializer.serialize(expression.substring, writer, context) + writer.write(", ") + serializer.serialize(expression.replacement, writer, context) + } + } +} diff --git a/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlRightSerializerTest.kt b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlRightSerializerTest.kt new file mode 100644 index 000000000..b9e5c1500 --- /dev/null +++ b/render/jpql/src/test/kotlin/com/linecorp/kotlinjdsl/render/jpql/serializer/impl/JpqlRightSerializerTest.kt @@ -0,0 +1,56 @@ +package com.linecorp.kotlinjdsl.render.jpql.serializer.impl + +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions +import com.linecorp.kotlinjdsl.querymodel.jpql.expression.impl.JpqlRight +import com.linecorp.kotlinjdsl.render.RenderContext +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.verify +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +@JpqlSerializerTest +internal class JpqlRightSerializerTest { + private val sut = JpqlRightSerializer() + + @MockK + private lateinit var writer: JpqlWriter + + @MockK + private lateinit var serializer: JpqlRenderSerializer + + @MockK + private lateinit var context: RenderContext + + @Test + fun `handledType should return Expression`() { + // when + val actual = sut.handledType() + + // then + assertThat(actual).isEqualTo(JpqlRight::class) + } + + @Test + fun `serialize should write RIGHT`() { + // given + val expression = Expressions.right(Expressions.value("abc"), Expressions.value(1)) as JpqlRight + + every { context.getValue(JpqlRenderSerializer) } returns serializer + + // when + sut.serialize(expression, writer, context) + + // then + verify { + writer.write("RIGHT") + writer.writeParentheses(any()) + serializer.serialize(expression.value, writer, context) + writer.write(", ") + serializer.serialize(expression.length, writer, context) + } + } +}