From e31ca9bcfce75bf1c24312de502d03c474c21b03 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Wed, 15 Jan 2025 12:41:13 -0500 Subject: [PATCH] Add example of implementing the MySQL "member of" operator --- pom.xml | 16 ++- .../config/TestContainersConfiguration.java | 1 + .../examples/mysql/MemberOfCondition.java | 39 ++++++ .../java/examples/mysql/MemberOfFunction.java | 51 ++++++++ src/test/java/examples/mysql/MySQLTest.java | 122 ++++++++++++++++++ 5 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 src/test/java/examples/mysql/MemberOfCondition.java create mode 100644 src/test/java/examples/mysql/MemberOfFunction.java create mode 100644 src/test/java/examples/mysql/MySQLTest.java diff --git a/pom.xml b/pom.xml index 6bfbfd73f..0e1fd5314 100644 --- a/pom.xml +++ b/pom.xml @@ -175,13 +175,13 @@ org.testcontainers - postgresql + junit-jupiter ${test.containers.version} test org.testcontainers - junit-jupiter + postgresql ${test.containers.version} test @@ -203,6 +203,18 @@ 3.5.1 test + + org.testcontainers + mysql + ${test.containers.version} + test + + + com.mysql + mysql-connector-j + 9.1.0 + test + diff --git a/src/test/java/config/TestContainersConfiguration.java b/src/test/java/config/TestContainersConfiguration.java index 7a4e39450..77fd866b0 100644 --- a/src/test/java/config/TestContainersConfiguration.java +++ b/src/test/java/config/TestContainersConfiguration.java @@ -23,4 +23,5 @@ public interface TestContainersConfiguration { DockerImageName POSTGRES_LATEST = DockerImageName.parse("postgres:17.2"); DockerImageName MARIADB_LATEST = DockerImageName.parse("mariadb:11.6.2"); + DockerImageName MYSQL_LATEST = DockerImageName.parse("mysql:9.1.0"); } diff --git a/src/test/java/examples/mysql/MemberOfCondition.java b/src/test/java/examples/mysql/MemberOfCondition.java new file mode 100644 index 000000000..33b967556 --- /dev/null +++ b/src/test/java/examples/mysql/MemberOfCondition.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.mysql; + +import java.util.Objects; + +import org.jspecify.annotations.NullMarked; +import org.mybatis.dynamic.sql.AbstractNoValueCondition; + +@NullMarked +public class MemberOfCondition extends AbstractNoValueCondition { + private final String jsonArray; + + protected MemberOfCondition(String jsonArray) { + this.jsonArray = Objects.requireNonNull(jsonArray); + } + + @Override + public String operator() { + return "member of(" + jsonArray + ")"; + } + + public static MemberOfCondition memberOf(String jsonArray) { + return new MemberOfCondition<>(jsonArray); + } +} diff --git a/src/test/java/examples/mysql/MemberOfFunction.java b/src/test/java/examples/mysql/MemberOfFunction.java new file mode 100644 index 000000000..1035bf3e0 --- /dev/null +++ b/src/test/java/examples/mysql/MemberOfFunction.java @@ -0,0 +1,51 @@ +/* + * Copyright 2016-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.mysql; + +import java.util.Objects; + +import org.jspecify.annotations.NullMarked; +import org.mybatis.dynamic.sql.BasicColumn; +import org.mybatis.dynamic.sql.BindableColumn; +import org.mybatis.dynamic.sql.render.RenderingContext; +import org.mybatis.dynamic.sql.select.function.AbstractTypeConvertingFunction; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; + +@NullMarked +public class MemberOfFunction extends AbstractTypeConvertingFunction> { + + private final String jsonArray; + + protected MemberOfFunction(BasicColumn column, String jsonArray) { + super(column); + this.jsonArray = Objects.requireNonNull(jsonArray); + } + + @Override + protected MemberOfFunction copy() { + return new MemberOfFunction<>(column, jsonArray); + } + + @Override + public FragmentAndParameters render(RenderingContext renderingContext) { + return column.render(renderingContext) + .mapFragment(f -> f + " member of(" + jsonArray + ")"); + } + + public static MemberOfFunction memberOf(BindableColumn column, String jsonArray) { + return new MemberOfFunction<>(column, jsonArray); + } +} diff --git a/src/test/java/examples/mysql/MySQLTest.java b/src/test/java/examples/mysql/MySQLTest.java new file mode 100644 index 000000000..54c43c34a --- /dev/null +++ b/src/test/java/examples/mysql/MySQLTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2016-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.mysql; + +import static examples.mysql.MemberOfCondition.memberOf; +import static examples.mysql.MemberOfFunction.memberOf; +import static examples.mariadb.ItemsDynamicSQLSupport.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.mybatis.dynamic.sql.SqlBuilder.*; + +import java.util.List; +import java.util.Map; + +import config.TestContainersConfiguration; +import org.apache.ibatis.datasource.unpooled.UnpooledDataSource; +import org.apache.ibatis.mapping.Environment; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mybatis.dynamic.sql.render.RenderingStrategies; +import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; +import org.mybatis.dynamic.sql.util.mybatis3.CommonSelectMapper; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +class MySQLTest { + + @SuppressWarnings("resource") + @Container + private static final MySQLContainer mysql = + new MySQLContainer<>(TestContainersConfiguration.MYSQL_LATEST) + .withInitScript("examples/mariadb/CreateDB.sql"); + + private static SqlSessionFactory sqlSessionFactory; + + @BeforeAll + static void setup() { + UnpooledDataSource ds = new UnpooledDataSource(mysql.getDriverClassName(), mysql.getJdbcUrl(), + mysql.getUsername(), mysql.getPassword()); + Environment environment = new Environment("test", new JdbcTransactionFactory(), ds); + Configuration config = new Configuration(environment); + config.addMapper(CommonSelectMapper.class); + sqlSessionFactory = new SqlSessionFactoryBuilder().build(config); + } + + @Test + void smokeTest() { + try (SqlSession session = sqlSessionFactory.openSession()) { + CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); + + SelectStatementProvider selectStatement = select(id, description) + .from(items) + .orderBy(id) + .build() + .render(RenderingStrategies.MYBATIS3); + List> rows = mapper.selectManyMappedRows(selectStatement); + assertThat(rows).hasSize(20); + } + } + + @Test + void testMemberOfAsCondition() { + try (SqlSession session = sqlSessionFactory.openSession()) { + CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); + + SelectStatementProvider selectStatement = select(id, memberOf(id, "'[1, 2, 3]'").as("inList")) + .from(items) + .where(id, memberOf("'[1, 2, 3]'")) + .orderBy(id) + .build() + .render(RenderingStrategies.MYBATIS3); + + assertThat(selectStatement.getSelectStatement()) + .isEqualTo("select id, id member of('[1, 2, 3]') as inList from items where id member of('[1, 2, 3]') order by id"); + + List> rows = mapper.selectManyMappedRows(selectStatement); + assertThat(rows).hasSize(3); + assertThat(rows.get(2)).containsOnly(entry("id", 3), entry("inList", 1L)); + } + } + + @Test + void testMemberOfAsFunction() { + try (SqlSession session = sqlSessionFactory.openSession()) { + CommonSelectMapper mapper = session.getMapper(CommonSelectMapper.class); + + SelectStatementProvider selectStatement = select(id, memberOf(id, "'[1, 2, 3]'").as("inList")) + .from(items) + .where(memberOf(id,"'[1, 2, 3]'"), isEqualTo(1L)) + .orderBy(id) + .build() + .render(RenderingStrategies.MYBATIS3); + + assertThat(selectStatement.getSelectStatement()) + .isEqualTo("select id, id member of('[1, 2, 3]') as inList from items where id member of('[1, 2, 3]') = #{parameters.p1} order by id"); + + List> rows = mapper.selectManyMappedRows(selectStatement); + assertThat(rows).hasSize(3); + assertThat(rows.get(2)).containsOnly(entry("id", 3), entry("inList", 1L)); + } + } +}