diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/CaseInsensitiveVisitableCondition.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/CaseInsensitiveRenderableCondition.java similarity index 93% rename from src/main/java/org/mybatis/dynamic/sql/where/condition/CaseInsensitiveVisitableCondition.java rename to src/main/java/org/mybatis/dynamic/sql/where/condition/CaseInsensitiveRenderableCondition.java index 9224ad594..2c837c4c7 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/CaseInsensitiveVisitableCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/CaseInsensitiveRenderableCondition.java @@ -20,7 +20,7 @@ import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.FragmentAndParameters; -public interface CaseInsensitiveVisitableCondition extends RenderableCondition { +public interface CaseInsensitiveRenderableCondition extends RenderableCondition { @Override default FragmentAndParameters renderLeftColumn(RenderingContext renderingContext, diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitive.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitive.java index 6f14406fa..67f37951e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitive.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitive.java @@ -27,7 +27,7 @@ import org.mybatis.dynamic.sql.util.Validator; public class IsInCaseInsensitive extends AbstractListValueCondition - implements CaseInsensitiveVisitableCondition { + implements CaseInsensitiveRenderableCondition { private static final IsInCaseInsensitive EMPTY = new IsInCaseInsensitive(Collections.emptyList()); public static IsInCaseInsensitive empty() { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitiveWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitiveWhenPresent.java index d366f0857..cff58415d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitiveWhenPresent.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitiveWhenPresent.java @@ -27,7 +27,7 @@ import org.mybatis.dynamic.sql.util.Utilities; public class IsInCaseInsensitiveWhenPresent extends AbstractListValueCondition - implements CaseInsensitiveVisitableCondition { + implements CaseInsensitiveRenderableCondition { private static final IsInCaseInsensitiveWhenPresent EMPTY = new IsInCaseInsensitiveWhenPresent(Collections.emptyList()); diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java index ccf6699ee..4ebdb00fb 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java @@ -23,7 +23,7 @@ import org.mybatis.dynamic.sql.util.StringUtilities; public class IsLikeCaseInsensitive extends AbstractSingleValueCondition - implements CaseInsensitiveVisitableCondition { + implements CaseInsensitiveRenderableCondition { private static final IsLikeCaseInsensitive EMPTY = new IsLikeCaseInsensitive("") { //$NON-NLS-1$ @Override public String value() { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitive.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitive.java index b4c1e96a7..f5f970c45 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitive.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitive.java @@ -27,7 +27,7 @@ import org.mybatis.dynamic.sql.util.Validator; public class IsNotInCaseInsensitive extends AbstractListValueCondition - implements CaseInsensitiveVisitableCondition { + implements CaseInsensitiveRenderableCondition { private static final IsNotInCaseInsensitive EMPTY = new IsNotInCaseInsensitive(Collections.emptyList()); public static IsNotInCaseInsensitive empty() { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitiveWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitiveWhenPresent.java index 5562b0ff4..a8873a7e4 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitiveWhenPresent.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitiveWhenPresent.java @@ -27,7 +27,7 @@ import org.mybatis.dynamic.sql.util.Utilities; public class IsNotInCaseInsensitiveWhenPresent extends AbstractListValueCondition - implements CaseInsensitiveVisitableCondition { + implements CaseInsensitiveRenderableCondition { private static final IsNotInCaseInsensitiveWhenPresent EMPTY = new IsNotInCaseInsensitiveWhenPresent(Collections.emptyList()); diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java index 5d9d8c3af..1fb6847c0 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java @@ -23,7 +23,7 @@ import org.mybatis.dynamic.sql.util.StringUtilities; public class IsNotLikeCaseInsensitive extends AbstractSingleValueCondition - implements CaseInsensitiveVisitableCondition { + implements CaseInsensitiveRenderableCondition { private static final IsNotLikeCaseInsensitive EMPTY = new IsNotLikeCaseInsensitive("") { //$NON-NLS-1$ @Override public String value() { diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/GroupingCriteriaCollector.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/GroupingCriteriaCollector.kt index 0aaaff460..be4ba72af 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/GroupingCriteriaCollector.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/GroupingCriteriaCollector.kt @@ -235,8 +235,8 @@ open class GroupingCriteriaCollector : SubCriteriaCollector() { .build() } - // infix functions...we may be able to rewrite these as extension functions once Kotlin solves the multiple - // receivers problem (https://youtrack.jetbrains.com/issue/KT-42435) + // infix functions...we may be able to rewrite these as extension functions once Kotlin implements the context + // parameters proposal (https://github.com/Kotlin/KEEP/issues/367) // conditions for all data types fun BindableColumn<*>.isNull() = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNull()) diff --git a/src/test/kotlin/examples/kotlin/mybatis3/mariadb/KIsLikeEscape.kt b/src/test/kotlin/examples/kotlin/mybatis3/mariadb/KIsLikeEscape.kt new file mode 100644 index 000000000..e018a8329 --- /dev/null +++ b/src/test/kotlin/examples/kotlin/mybatis3/mariadb/KIsLikeEscape.kt @@ -0,0 +1,54 @@ +package examples.kotlin.mybatis3.mariadb + +import java.util.function.Predicate +import java.util.function.Function +import org.mybatis.dynamic.sql.AbstractSingleValueCondition +import org.mybatis.dynamic.sql.BindableColumn +import org.mybatis.dynamic.sql.render.RenderingContext +import org.mybatis.dynamic.sql.util.FragmentAndParameters + +sealed class KIsLikeEscape( + value: T, + private val escapeCharacter: Char? = null +) : AbstractSingleValueCondition(value) { + + override fun operator(): String = "like" + + override fun renderCondition( + renderingContext: RenderingContext, + leftColumn: BindableColumn + ): FragmentAndParameters = with(super.renderCondition(renderingContext, leftColumn)) { + escapeCharacter?.let { mapFragment { "$it ESCAPE '$escapeCharacter'" } } ?: this + } + + override fun filter(predicate: Predicate): KIsLikeEscape = + filterSupport(predicate, EmptyIsLikeEscape::empty, this) + + fun map(mapper : Function): KIsLikeEscape = + mapSupport(mapper, { r -> ConcreteIsLikeEscape(r, escapeCharacter) }, EmptyIsLikeEscape::empty) + + companion object { + fun isLike(value: T, escapeCharacter: Char? = null) : KIsLikeEscape = + ConcreteIsLikeEscape(value, escapeCharacter) + } +} + +private class ConcreteIsLikeEscape( + value: T, + escapeCharacter: Char? = null +) : KIsLikeEscape(value, escapeCharacter) + +private class EmptyIsLikeEscape : KIsLikeEscape(-1) { + override fun isEmpty(): Boolean = true + + override fun value(): Any { + throw NoSuchElementException("No value present") + } + + companion object { + private val EMPTY: KIsLikeEscape = EmptyIsLikeEscape() + + @Suppress("UNCHECKED_CAST") + internal fun empty(): KIsLikeEscape = EMPTY as KIsLikeEscape + } +} diff --git a/src/test/kotlin/examples/kotlin/mybatis3/mariadb/KMariaDBTest.kt b/src/test/kotlin/examples/kotlin/mybatis3/mariadb/KMariaDBTest.kt index 8ce870c01..28bf98a3b 100644 --- a/src/test/kotlin/examples/kotlin/mybatis3/mariadb/KMariaDBTest.kt +++ b/src/test/kotlin/examples/kotlin/mybatis3/mariadb/KMariaDBTest.kt @@ -38,6 +38,7 @@ import org.mybatis.dynamic.sql.util.mybatis3.CommonUpdateMapper import org.testcontainers.containers.MariaDBContainer import org.testcontainers.junit.jupiter.Container import org.testcontainers.junit.jupiter.Testcontainers +import java.util.Locale @Testcontainers @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -142,6 +143,92 @@ class KMariaDBTest { } } + + /** + * Shortcut function for KIsLikeEscape + * + * Note that the following example uses of this function are a bit awkward and don't look as natural as the + * built-in conditions. We should be able to improve this once Kotlin implements the context parameters + * proposal (https://github.com/Kotlin/KEEP/issues/367) + */ + fun isLike(value: T, escapeCharacter: Char? = null) = KIsLikeEscape.isLike(value, escapeCharacter) + + @Test + fun testIsLikeEscape() { + sqlSessionFactory.openSession().use { session -> + val mapper = session.getMapper(CommonSelectMapper::class.java) + val selectStatement = select(id, description) { + from(items) + where { + description(isLike("Item 1%", '#')) + } + } + + assertThat(selectStatement.selectStatement).isEqualTo("select id, description from items where description like #{parameters.p1,jdbcType=VARCHAR} ESCAPE '#'") + assertThat(selectStatement.parameters).containsEntry("p1", "Item 1%") + + val rows = mapper.selectManyMappedRows(selectStatement) + assertThat(rows).hasSize(11) + } + } + + @Test + fun testIsLikeEscapeNoEscapeCharacter() { + val selectStatement = select(id, description) { + from(items) + where { + description(isLike("%fred%")) + } + } + + assertThat(selectStatement.selectStatement).isEqualTo("select id, description from items where description like #{parameters.p1,jdbcType=VARCHAR}") + assertThat(selectStatement.parameters).containsEntry("p1", "%fred%") + } + + @Test + fun testIsLikeEscapeMap() { + val selectStatement = select(id, description) { + from(items) + where { + description(isLike("%fred%", '#').map { s -> s.uppercase(Locale.getDefault()) }) + } + } + + assertThat(selectStatement.selectStatement).isEqualTo("select id, description from items where description like #{parameters.p1,jdbcType=VARCHAR} ESCAPE '#'") + assertThat(selectStatement.parameters).containsEntry("p1", "%FRED%") + } + + @Test + fun testIsLikeEscapeFilter() { + val selectStatement = select(id, description) { + from(items) + where { + description(isLike("%fred%", '#').filter { _ -> false }) + } + configureStatement { isNonRenderingWhereClauseAllowed = true } + } + + assertThat(selectStatement.selectStatement).isEqualTo("select id, description from items") + assertThat(selectStatement.parameters).isEmpty() + } + + @Test + fun testIsLikeEscapeFilterMapFilter() { + val selectStatement = select(id, description) { + from(items) + where { + description(isLike("%fred%", '#') + .filter { _ -> true } + .map { s -> s.uppercase(Locale.getDefault()) } + .filter{_ -> false }) + } + configureStatement { isNonRenderingWhereClauseAllowed = true } + } + + assertThat(selectStatement.selectStatement).isEqualTo("select id, description from items") + assertThat(selectStatement.parameters).isEmpty() + } + companion object { @Container private val mariadb = MariaDBContainer(TestContainersConfiguration.MARIADB_LATEST)