diff --git a/CHANGELOG.md b/CHANGELOG.md index b1f158cbe..49ae77c14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/issues?q=miles ### Added - Changed the public SQLBuilder API to accept Collection instead of List for in conditions and batch record inserts. This should have no impact on existing code, but allow for some future flexibility +- Added the ability have have table catalog and/or schema calculated at query runtime. This is useful for situations where there are different database schemas for different environments, or in some sharding situations ## Release 1.1.1 - April 7, 2019 diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlTable.java b/src/main/java/org/mybatis/dynamic/sql/SqlTable.java index ba55b2da8..781679821 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlTable.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlTable.java @@ -1,5 +1,5 @@ /** - * Copyright 2016-2018 the original author or authors. + * Copyright 2016-2019 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. @@ -17,17 +17,60 @@ import java.sql.JDBCType; import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; public class SqlTable { + + private Supplier nameSupplier; + + protected SqlTable(String fullyQualifiedTableName) { + Objects.requireNonNull(fullyQualifiedTableName); + + this.nameSupplier = () -> fullyQualifiedTableName; + } + + protected SqlTable(Supplier> schemaSupplier, String tableName) { + this(Optional::empty, schemaSupplier, tableName); + } + + protected SqlTable(Supplier> catalogSupplier, Supplier> schemaSupplier, String tableName) { + Objects.requireNonNull(catalogSupplier); + Objects.requireNonNull(schemaSupplier); + Objects.requireNonNull(tableName); + + this.nameSupplier = () -> compose(catalogSupplier, schemaSupplier, tableName); + } + + private String compose(Supplier> catalogSupplier, Supplier> schemaSupplier, String tableName) { + return catalogSupplier.get().map(c -> compose(c, schemaSupplier, tableName)) + .orElse(compose(schemaSupplier, tableName)); + } + + private String compose(String catalog, Supplier> schemaSupplier, String tableName) { + return schemaSupplier.get().map(s -> composeCatalogSchemaAndAndTable(catalog, s, tableName)) + .orElse(composeCatalogAndTable(catalog, tableName)); + } - private String name; + private String compose(Supplier> schemaSupplier, String tableName) { + return schemaSupplier.get().map(s -> composeSchemaAndTable(s, tableName)) + .orElse(tableName); + } + + private String composeCatalogAndTable(String catalog, String tableName) { + return catalog + ".." + tableName; //$NON-NLS-1$ + } - protected SqlTable(String name) { - this.name = Objects.requireNonNull(name); + private String composeSchemaAndTable(String schema, String tableName) { + return schema + "." + tableName; //$NON-NLS-1$ } - public String name() { - return name; + private String composeCatalogSchemaAndAndTable(String catalog, String schema, String tableName) { + return catalog + "." + schema + "." + tableName; //$NON-NLS-1$ //$NON-NLS-2$ + } + + public String fullyQualifiedTableName() { + return nameSupplier.get(); } public SqlColumn allColumns() { diff --git a/src/main/java/org/mybatis/dynamic/sql/delete/render/DeleteRenderer.java b/src/main/java/org/mybatis/dynamic/sql/delete/render/DeleteRenderer.java index 2987e186e..c10ba2799 100644 --- a/src/main/java/org/mybatis/dynamic/sql/delete/render/DeleteRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/delete/render/DeleteRenderer.java @@ -58,7 +58,7 @@ private Optional renderWhereClause(WhereModel whereModel) { private String calculateDeleteStatement(Optional whereClause) { return "delete from" //$NON-NLS-1$ - + spaceBefore(deleteModel.table().name()) + + spaceBefore(deleteModel.table().fullyQualifiedTableName()) + spaceBefore(whereClause.map(WhereClauseProvider::getWhereClause)); } diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/BatchInsertRenderer.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/BatchInsertRenderer.java index de904779e..1727cc1fd 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/BatchInsertRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/BatchInsertRenderer.java @@ -54,7 +54,7 @@ private FieldAndValue toFieldAndValue(ValuePhraseVisitor visitor, InsertMapping private String calculateInsertStatement(FieldAndValueCollector collector) { return "insert into" //$NON-NLS-1$ - + spaceBefore(model.table().name()) + + spaceBefore(model.table().fullyQualifiedTableName()) + spaceBefore(collector.columnsPhrase()) + spaceBefore(collector.valuesPhrase()); } diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertRenderer.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertRenderer.java index d9f26bd56..bf59f17d7 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertRenderer.java @@ -46,7 +46,7 @@ public InsertStatementProvider render() { private String calculateInsertStatement(FieldAndValueCollector collector) { return "insert into" //$NON-NLS-1$ - + spaceBefore(model.table().name()) + + spaceBefore(model.table().fullyQualifiedTableName()) + spaceBefore(collector.columnsPhrase()) + spaceBefore(collector.valuesPhrase()); } diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertSelectRenderer.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertSelectRenderer.java index 5e109c8c0..ac61e9f75 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertSelectRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertSelectRenderer.java @@ -47,7 +47,7 @@ public InsertSelectStatementProvider render() { private String calculateInsertStatement(SelectStatementProvider selectStatement) { return "insert into" //$NON-NLS-1$ - + spaceBefore(model.table().name()) + + spaceBefore(model.table().fullyQualifiedTableName()) + spaceBefore(calculateColumnsPhrase()) + spaceBefore(selectStatement.getSelectStatement()); } diff --git a/src/main/java/org/mybatis/dynamic/sql/render/GuaranteedTableAliasCalculator.java b/src/main/java/org/mybatis/dynamic/sql/render/GuaranteedTableAliasCalculator.java index 0692f22b1..1480e93bc 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/GuaranteedTableAliasCalculator.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/GuaranteedTableAliasCalculator.java @@ -1,5 +1,5 @@ /** - * Copyright 2016-2017 the original author or authors. + * Copyright 2016-2019 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. @@ -37,7 +37,7 @@ private GuaranteedTableAliasCalculator(Map aliases) { public Optional aliasForColumn(SqlTable table) { return super.aliasForColumn(table) .map(Optional::of) - .orElse(Optional.of(table.name())); + .orElse(Optional.of(table.fullyQualifiedTableName())); } public static TableAliasCalculator of(Map aliases) { diff --git a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionModel.java b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionModel.java index 188a9c93b..dbf21020d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionModel.java @@ -1,5 +1,5 @@ /** - * Copyright 2016-2018 the original author or authors. + * Copyright 2016-2019 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. @@ -88,7 +88,7 @@ public Optional groupByModel() { } public String calculateTableNameIncludingAlias(SqlTable table) { - return table.name() + return table.fullyQualifiedTableName() + spaceBefore(tableAliasCalculator.aliasForTable(table)); } diff --git a/src/main/java/org/mybatis/dynamic/sql/update/render/UpdateRenderer.java b/src/main/java/org/mybatis/dynamic/sql/update/render/UpdateRenderer.java index 88374165b..ca9599e17 100644 --- a/src/main/java/org/mybatis/dynamic/sql/update/render/UpdateRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/update/render/UpdateRenderer.java @@ -65,7 +65,7 @@ private FragmentCollector calculateColumnMappings() { private String calculateUpdateStatement(FragmentCollector fc, Optional whereClause) { return "update" //$NON-NLS-1$ - + spaceBefore(updateModel.table().name()) + + spaceBefore(updateModel.table().fullyQualifiedTableName()) + spaceBefore(calculateSetPhrase(fc)) + spaceBefore(whereClause.map(WhereClauseProvider::getWhereClause)); } diff --git a/src/site/markdown/docs/databaseObjects.md b/src/site/markdown/docs/databaseObjects.md new file mode 100644 index 000000000..851a0459a --- /dev/null +++ b/src/site/markdown/docs/databaseObjects.md @@ -0,0 +1,102 @@ +# Database Object Representation +MyBatis Dynamic SQL works with Java objects that represent relational tables or views. + +## Table or View Representation + +The class `SqlTable` is used to represent a table or view in a database. An `SqlTable` holds a name, and a collection of `SqlColumn` objects that represent the columns in a table or view. + +A fully qualified name has three parts: + +1. The catalog - which is rarely used outside of Microsoft SQL Server +1. The schema - which is often specified, but may be left blank if you are operating on tables in the default schema +1. The name - which is required + +Typical examples of fully qualified names are as follows: + +- `"dbo..bar"` - a fully qualified name with a catalog (dbo) and a name (bar). This is typical for SQL Server +- `"foo.bar"` - a fully qualified name with a schema (foo) and a name (bar). This is typical in many databases when you want to access tables that are not in the default schema +- `"bar"` - a fully qualified name with just a name (bar). This will access a table or view in the default catalog and schema for a connection + + +In MyBatis Dynamic SQL, the fully qualified name can be specified in different ways: + +1. The fully qualified table name can be a constant String +1. The fully qualified table name can be calculated at runtime based on a dynamic catalog and/or schema and a constant table name + +### Constant Names + +Constant names are used when you use the `SqlTable` constructor with a single String argument. For example: + +```java +public class MyTable extends SqlTable { + public MyTable() { + super("MyTable"); + } +} +``` + +Or + +```java +public class MyTable extends SqlTable { + public MyTable() { + super("MySchema.MyTable"); + } +} +``` + + +### Dynamic Names +MyBatis Dynamic SQL allows you to dynamically specify a catalog and/or schema. This is useful for applications where the schema may change for different users or environments, or if you are using different schemas to shard the database. Dynamic names are used when you use a `SqlTable` constructor that accepts one or more `java.util.function.Supplier` arguments. + +For example, suppose you wanted to change the schema based on the value of a system property. You could write a class like this: + +```java +public class SchemaSupplier { + public static final String schema_property = "schemaToUse"; + + public static Optional schemaPropertyReader() { + return Optional.ofNullable(System.getProperty(schema_property)); + } +} +``` + +This class has a static method `schemaPropertyReader` that will return an `Optional` containing the value of a system property. You could then reference this method in the constructor of the `SqlTable` like this: + +```java +public static final class User extends SqlTable { + public User() { + super(SchemaSupplier::schemaPropertyReader, "User"); + } +} +``` + +Whenever the table is referenced for rendering SQL, the name will be calculated based on the current value of the system property. + +There are two constructors that can be used for dynamic names: + +1. A constructor that accepts `Supplier>` for the schema, and `String` for the name. This constructor assumes that the catalog is always empty or not used +1. A constructor that accepts `Supplier>` for the catalog, `Supplier>` for the schema, and `String` for the name + +If you are using Microsoft SQL Server and want to use a dynamic catalog name and ignore the schema, then you should use the second constructor like this: + +```java +public static final class User extends SqlTable { + public User() { + super(CatalogSupplier::catalogPropertyReader, Optional::empty, "User"); + } +} +``` + +The following table shows how the name is calculated in all combinations of suppliers: + +Catalog Supplier Value | Schema Supplier Value | Name | Fully Qualified Name +---|---|---|--- +"MyCatalog" | "MySchema" | "MyTable" | "MyCatalog.MySchema.MyTable" +<empty> | "MySchema" | "MyTable" | "MySchema.MyTable" +"MyCatalog" | <empty> | "MyTable" | "MyCatalog..MyTable" +<empty> | <empty> | "MyTable" | "MyTable" + + + +## Column Representation diff --git a/src/site/site.xml b/src/site/site.xml index d2bebddef..fe302fde9 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -37,6 +37,7 @@ + diff --git a/src/test/java/examples/schema_supplier/SchemaSupplier.java b/src/test/java/examples/schema_supplier/SchemaSupplier.java new file mode 100644 index 000000000..6c40e5707 --- /dev/null +++ b/src/test/java/examples/schema_supplier/SchemaSupplier.java @@ -0,0 +1,26 @@ +/** + * Copyright 2016-2019 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 + * + * http://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.schema_supplier; + +import java.util.Optional; + +public class SchemaSupplier { + public static final String schema_property = "schemaToUse"; + + public static Optional schemaPropertyReader() { + return Optional.ofNullable(System.getProperty(schema_property)); + } +} diff --git a/src/test/java/examples/schema_supplier/SchemaSupplierTest.java b/src/test/java/examples/schema_supplier/SchemaSupplierTest.java new file mode 100644 index 000000000..99e82f58d --- /dev/null +++ b/src/test/java/examples/schema_supplier/SchemaSupplierTest.java @@ -0,0 +1,155 @@ +/** + * Copyright 2016-2019 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 + * + * http://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.schema_supplier; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.sql.Connection; +import java.sql.DriverManager; +import java.util.List; + +import org.apache.ibatis.datasource.unpooled.UnpooledDataSource; +import org.apache.ibatis.exceptions.PersistenceException; +import org.apache.ibatis.jdbc.ScriptRunner; +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.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SchemaSupplierTest { + + private static final String JDBC_URL = "jdbc:hsqldb:mem:aname"; + private static final String JDBC_DRIVER = "org.hsqldb.jdbcDriver"; + + private SqlSessionFactory sqlSessionFactory; + + @BeforeEach + public void setup() throws Exception { + Class.forName(JDBC_DRIVER); + InputStream is = getClass().getResourceAsStream("/examples/schema_supplier/CreateDB.sql"); + try (Connection connection = DriverManager.getConnection(JDBC_URL, "sa", "")) { + ScriptRunner sr = new ScriptRunner(connection); + sr.setLogWriter(null); + sr.runScript(new InputStreamReader(is)); + } + + UnpooledDataSource ds = new UnpooledDataSource(JDBC_DRIVER, JDBC_URL, "sa", ""); + Environment environment = new Environment("test", new JdbcTransactionFactory(), ds); + Configuration config = new Configuration(environment); + config.addMapper(UserMapper.class); + sqlSessionFactory = new SqlSessionFactoryBuilder().build(config); + } + + @Test + public void testUnsetSchemaProperty() { + System.clearProperty(SchemaSupplier.schema_property); + + try (SqlSession session = sqlSessionFactory.openSession()) { + UserMapper mapper = session.getMapper(UserMapper.class); + + assertThrows(PersistenceException.class, () -> { + User user = new User(); + user.setId(1); + user.setName("Fred"); + + int rows = mapper.insert(user); + + assertThat(rows).isEqualTo(1); + }); + } + } + + @Test + public void testSchemaProperty() { + System.setProperty(SchemaSupplier.schema_property, "schema1"); + + try (SqlSession session = sqlSessionFactory.openSession()) { + UserMapper mapper = session.getMapper(UserMapper.class); + + insertFlintstones(mapper); + + List records = mapper.selectByExample() + .build() + .execute(); + assertThat(records.size()).isEqualTo(2); + } + } + + @Test + public void testSchemaSwitchingProperty() { + try (SqlSession session = sqlSessionFactory.openSession()) { + UserMapper mapper = session.getMapper(UserMapper.class); + + System.setProperty(SchemaSupplier.schema_property, "schema1"); + insertFlintstones(mapper); + + List records = mapper.selectByExample() + .build() + .execute(); + assertThat(records.size()).isEqualTo(2); + + + System.setProperty(SchemaSupplier.schema_property, "schema2"); + insertRubbles(mapper); + + records = mapper.selectByExample() + .build() + .execute(); + assertThat(records.size()).isEqualTo(3); + } + } + + private void insertFlintstones(UserMapper mapper) { + User user = new User(); + user.setId(1); + user.setName("Fred"); + int rows = mapper.insert(user); + assertThat(rows).isEqualTo(1); + + user = new User(); + user.setId(2); + user.setName("Wilma"); + rows = mapper.insert(user); + assertThat(rows).isEqualTo(1); + } + + private void insertRubbles(UserMapper mapper) { + User user = new User(); + user.setId(1); + user.setName("Barney"); + int rows = mapper.insert(user); + assertThat(rows).isEqualTo(1); + + user = new User(); + user.setId(2); + user.setName("Betty"); + rows = mapper.insert(user); + assertThat(rows).isEqualTo(1); + + user = new User(); + user.setId(3); + user.setName("Bamm Bamm"); + rows = mapper.insert(user); + assertThat(rows).isEqualTo(1); + } +} diff --git a/src/test/java/examples/schema_supplier/User.java b/src/test/java/examples/schema_supplier/User.java new file mode 100644 index 000000000..4585d7f19 --- /dev/null +++ b/src/test/java/examples/schema_supplier/User.java @@ -0,0 +1,37 @@ +/** + * Copyright 2016-2019 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 + * + * http://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.schema_supplier; + +public class User { + private int id; + private String name; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/test/java/examples/schema_supplier/UserDynamicSqlSupport.java b/src/test/java/examples/schema_supplier/UserDynamicSqlSupport.java new file mode 100644 index 000000000..9467b551f --- /dev/null +++ b/src/test/java/examples/schema_supplier/UserDynamicSqlSupport.java @@ -0,0 +1,36 @@ +/** + * Copyright 2016-2019 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 + * + * http://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.schema_supplier; + +import java.sql.JDBCType; + +import org.mybatis.dynamic.sql.SqlColumn; +import org.mybatis.dynamic.sql.SqlTable; + +public class UserDynamicSqlSupport { + public static final User user = new User(); + public static final SqlColumn id = user.id; + public static final SqlColumn name = user.name; + + public static final class User extends SqlTable { + public final SqlColumn id = column("user_id", JDBCType.INTEGER); + public final SqlColumn name = column("user_name", JDBCType.VARCHAR); + + public User() { + super(SchemaSupplier::schemaPropertyReader, "User"); + } + } +} diff --git a/src/test/java/examples/schema_supplier/UserMapper.java b/src/test/java/examples/schema_supplier/UserMapper.java new file mode 100644 index 000000000..700d31f15 --- /dev/null +++ b/src/test/java/examples/schema_supplier/UserMapper.java @@ -0,0 +1,61 @@ +/** + * Copyright 2016-2019 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 + * + * http://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.schema_supplier; + +import static examples.schema_supplier.UserDynamicSqlSupport.*; + +import java.util.List; + +import org.apache.ibatis.annotations.InsertProvider; +import org.apache.ibatis.annotations.Result; +import org.apache.ibatis.annotations.Results; +import org.apache.ibatis.annotations.SelectProvider; +import org.apache.ibatis.type.JdbcType; +import org.mybatis.dynamic.sql.SqlBuilder; +import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider; +import org.mybatis.dynamic.sql.render.RenderingStrategy; +import org.mybatis.dynamic.sql.select.MyBatis3SelectModelAdapter; +import org.mybatis.dynamic.sql.select.QueryExpressionDSL; +import org.mybatis.dynamic.sql.select.SelectDSL; +import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; +import org.mybatis.dynamic.sql.util.SqlProviderAdapter; + +public interface UserMapper { + + @InsertProvider(type=SqlProviderAdapter.class, method="insert") + int insert(InsertStatementProvider insertStatement); + + @SelectProvider(type=SqlProviderAdapter.class, method="select") + @Results(id="UserResult", value= { + @Result(column="USER_ID", property="id", jdbcType=JdbcType.INTEGER, id=true), + @Result(column="USER_NAME", property="name", jdbcType=JdbcType.VARCHAR) + }) + List selectMany(SelectStatementProvider selectStatement); + + default QueryExpressionDSL>> selectByExample() { + return SelectDSL.selectWithMapper(this::selectMany, id, name) + .from(user); + } + + default int insert(User record) { + return insert(SqlBuilder.insert(record) + .into(user) + .map(id).toProperty("id") + .map(name).toProperty("name") + .build() + .render(RenderingStrategy.MYBATIS3)); + } +} diff --git a/src/test/java/org/mybatis/dynamic/sql/SqlTableTest.java b/src/test/java/org/mybatis/dynamic/sql/SqlTableTest.java new file mode 100644 index 000000000..a88a19eeb --- /dev/null +++ b/src/test/java/org/mybatis/dynamic/sql/SqlTableTest.java @@ -0,0 +1,161 @@ +/** + * Copyright 2016-2019 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 + * + * http://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 org.mybatis.dynamic.sql; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Optional; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +public class SqlTableTest { + + @Test + public void testSchemaSupplierEmpty() { + SqlTable table = new SqlTable(Optional::empty, "my_table"); + assertThat(table.fullyQualifiedTableName()).isEqualTo("my_table"); + } + + @Test + public void testSchemaSupplierWithValue() { + SqlTable table = new SqlTable(() -> Optional.of("my_schema"), "my_table"); + assertThat(table.fullyQualifiedTableName()).isEqualTo("my_schema.my_table"); + } + + @Test + public void testSingletonSchemaSupplier() { + SqlTable table = new SqlTable(MySchemaSupplier.instance(), "my_table"); + assertThat(table.fullyQualifiedTableName()).isEqualTo("first_schema.my_table"); + } + + @Test + public void testThatSchemaSupplierDoesDelay() { + MySchemaSupplier schemaSupplier = new MySchemaSupplier(); + SqlTable table = new SqlTable(schemaSupplier, "my_table"); + assertThat(table.fullyQualifiedTableName()).isEqualTo("first_schema.my_table"); + + schemaSupplier.setFirst(false); + assertThat(table.fullyQualifiedTableName()).isEqualTo("second_schema.my_table"); + } + + @Test + public void testCatalogAndSchemaSupplierEmpty() { + SqlTable table = new SqlTable(Optional::empty, Optional::empty, "my_table"); + assertThat(table.fullyQualifiedTableName()).isEqualTo("my_table"); + } + + @Test + public void testCatalogSupplierWithValue() { + SqlTable table = new SqlTable(() -> Optional.of("my_catalog"), Optional::empty, "my_table"); + assertThat(table.fullyQualifiedTableName()).isEqualTo("my_catalog..my_table"); + } + + @Test + public void testThatCatalogSupplierDoesDelay() { + MyCatalogSupplier catalogSupplier = new MyCatalogSupplier(); + SqlTable table = new SqlTable(catalogSupplier, Optional::empty, "my_table"); + assertThat(table.fullyQualifiedTableName()).isEqualTo("first_catalog..my_table"); + + catalogSupplier.setFirst(false); + assertThat(table.fullyQualifiedTableName()).isEqualTo("second_catalog..my_table"); + } + + @Test + public void testThatCatalogSupplierAndSchemaSupplierBothDelay() { + MyCatalogSupplier catalogSupplier = new MyCatalogSupplier(); + MySchemaSupplier schemaSupplier = new MySchemaSupplier(); + SqlTable table = new SqlTable(catalogSupplier, schemaSupplier, "my_table"); + assertThat(table.fullyQualifiedTableName()).isEqualTo("first_catalog.first_schema.my_table"); + + catalogSupplier.setFirst(false); + assertThat(table.fullyQualifiedTableName()).isEqualTo("second_catalog.first_schema.my_table"); + + catalogSupplier.setFirst(true); + schemaSupplier.setFirst(false); + assertThat(table.fullyQualifiedTableName()).isEqualTo("first_catalog.second_schema.my_table"); + + catalogSupplier.setFirst(false); + assertThat(table.fullyQualifiedTableName()).isEqualTo("second_catalog.second_schema.my_table"); + + catalogSupplier.setEmpty(true); + assertThat(table.fullyQualifiedTableName()).isEqualTo("second_schema.my_table"); + + schemaSupplier.setEmpty(true); + assertThat(table.fullyQualifiedTableName()).isEqualTo("my_table"); + + catalogSupplier.setEmpty(false); + assertThat(table.fullyQualifiedTableName()).isEqualTo("second_catalog..my_table"); + } + + public static class MySchemaSupplier implements Supplier> { + private static MySchemaSupplier instance = new MySchemaSupplier(); + + public static MySchemaSupplier instance() { + return instance; + } + + private boolean first = true; + private boolean empty; + + public void setFirst(boolean first) { + this.first = first; + } + + public void setEmpty(boolean empty) { + this.empty = empty; + } + + @Override + public Optional get() { + if (empty) { + return Optional.empty(); + } + + if (first) { + return Optional.of("first_schema"); + } else { + return Optional.of("second_schema"); + } + } + } + + public static class MyCatalogSupplier implements Supplier> { + private boolean first = true; + private boolean empty; + + public void setFirst(boolean first) { + this.first = first; + } + + public void setEmpty(boolean empty) { + this.empty = empty; + } + + @Override + public Optional get() { + if (empty) { + return Optional.empty(); + } + + if (first) { + return Optional.of("first_catalog"); + } else { + return Optional.of("second_catalog"); + } + } + } +} diff --git a/src/test/java/org/mybatis/dynamic/sql/insert/InsertStatementTest.java b/src/test/java/org/mybatis/dynamic/sql/insert/InsertStatementTest.java index bfa6cccc1..cb5b63995 100644 --- a/src/test/java/org/mybatis/dynamic/sql/insert/InsertStatementTest.java +++ b/src/test/java/org/mybatis/dynamic/sql/insert/InsertStatementTest.java @@ -1,5 +1,5 @@ /** - * Copyright 2016-2018 the original author or authors. + * Copyright 2016-2019 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. @@ -137,7 +137,7 @@ public void testParallelStream() { FieldAndValueCollector collector = mappings.parallelStream().collect(Collector.of( - () -> new FieldAndValueCollector<>(), + FieldAndValueCollector::new, FieldAndValueCollector::add, FieldAndValueCollector::merge)); diff --git a/src/test/java/org/mybatis/dynamic/sql/mybatis3/CriterionRendererTest.java b/src/test/java/org/mybatis/dynamic/sql/mybatis3/CriterionRendererTest.java index 584a2a0c9..e4046d56f 100644 --- a/src/test/java/org/mybatis/dynamic/sql/mybatis3/CriterionRendererTest.java +++ b/src/test/java/org/mybatis/dynamic/sql/mybatis3/CriterionRendererTest.java @@ -1,5 +1,5 @@ /** - * Copyright 2016-2018 the original author or authors. + * Copyright 2016-2019 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. @@ -86,54 +86,6 @@ public void testAliasWithoutIgnore() { ); } - @Test - public void testNoAliasWithIgnore() { - SqlTable table = SqlTable.of("foo"); - SqlColumn column = table.column("id", JDBCType.INTEGER); - IsEqualTo condition = IsEqualTo.of(() -> 3); - SqlCriterion criterion = SqlCriterion.withColumn(column) - .withCondition(condition) - .build(); - AtomicInteger sequence = new AtomicInteger(1); - FragmentAndParameters fp = CriterionRenderer.withCriterion(criterion) - .withSequence(sequence) - .withRenderingStrategy(RenderingStrategy.MYBATIS3) - .withTableAliasCalculator(TableAliasCalculator.empty()) - .build() - .render() - .get() - .renderWithInitialConnector(); - - assertAll( - () -> assertThat(fp.fragment()).isEqualTo("id = #{parameters.p1,jdbcType=INTEGER}"), - () -> assertThat(fp.parameters().size()).isEqualTo(1) - ); - } - - @Test - public void testNoAliasWithoutIgnore() { - SqlTable table = SqlTable.of("foo"); - SqlColumn column = table.column("id", JDBCType.INTEGER); - IsEqualTo condition = IsEqualTo.of(() -> 3); - SqlCriterion criterion = SqlCriterion.withColumn(column) - .withCondition(condition) - .build(); - AtomicInteger sequence = new AtomicInteger(1); - FragmentAndParameters fp = CriterionRenderer.withCriterion(criterion) - .withSequence(sequence) - .withRenderingStrategy(RenderingStrategy.MYBATIS3) - .withTableAliasCalculator(TableAliasCalculator.empty()) - .build() - .render() - .get() - .renderWithInitialConnector(); - - assertAll( - () -> assertThat(fp.fragment()).isEqualTo("id = #{parameters.p1,jdbcType=INTEGER}"), - () -> assertThat(fp.parameters().size()).isEqualTo(1) - ); - } - @Test public void testTypeHandler() { SqlTable table = SqlTable.of("foo"); @@ -142,7 +94,7 @@ public void testTypeHandler() { .withJdbcType(JDBCType.DATE) .withTypeHandler("foo.Bar") .build(); - IsEqualTo condition = IsEqualTo.of(() -> new Date()); + IsEqualTo condition = IsEqualTo.of(Date::new); SqlCriterion criterion = SqlCriterion.withColumn(column) .withCondition(condition) .build(); diff --git a/src/test/java/org/mybatis/dynamic/sql/where/render/CriterionRendererTest.java b/src/test/java/org/mybatis/dynamic/sql/where/render/CriterionRendererTest.java index 3d60b5118..086e2cfb3 100644 --- a/src/test/java/org/mybatis/dynamic/sql/where/render/CriterionRendererTest.java +++ b/src/test/java/org/mybatis/dynamic/sql/where/render/CriterionRendererTest.java @@ -1,5 +1,5 @@ /** - * Copyright 2016-2018 the original author or authors. + * Copyright 2016-2019 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. @@ -83,50 +83,4 @@ public void testAliasWithoutIgnore() { assertThat(fp.parameters().size()).isEqualTo(1); assertThat(fp.parameters().get("p1")).isEqualTo(3); } - - @Test - public void testNoAliasWithIgnore() { - SqlTable table = SqlTable.of("foo"); - SqlColumn column = table.column("id", JDBCType.INTEGER); - IsEqualTo condition = IsEqualTo.of(() -> 3); - SqlCriterion criterion = SqlCriterion.withColumn(column) - .withCondition(condition) - .build(); - AtomicInteger sequence = new AtomicInteger(1); - FragmentAndParameters fp = CriterionRenderer.withCriterion(criterion) - .withSequence(sequence) - .withRenderingStrategy(RenderingStrategy.MYBATIS3) - .withTableAliasCalculator(TableAliasCalculator.empty()) - .build() - .render() - .get() - .renderWithInitialConnector(); - - assertThat(fp.fragment()).isEqualTo("id = #{parameters.p1,jdbcType=INTEGER}"); - assertThat(fp.parameters().size()).isEqualTo(1); - assertThat(fp.parameters().get("p1")).isEqualTo(3); - } - - @Test - public void testNoAliasWithoutIgnore() { - SqlTable table = SqlTable.of("foo"); - SqlColumn column = table.column("id", JDBCType.INTEGER); - IsEqualTo condition = IsEqualTo.of(() -> 3); - SqlCriterion criterion = SqlCriterion.withColumn(column) - .withCondition(condition) - .build(); - AtomicInteger sequence = new AtomicInteger(1); - FragmentAndParameters fp = CriterionRenderer.withCriterion(criterion) - .withSequence(sequence) - .withRenderingStrategy(RenderingStrategy.MYBATIS3) - .withTableAliasCalculator(TableAliasCalculator.empty()) - .build() - .render() - .get() - .renderWithInitialConnector(); - - assertThat(fp.fragment()).isEqualTo("id = #{parameters.p1,jdbcType=INTEGER}"); - assertThat(fp.parameters().size()).isEqualTo(1); - assertThat(fp.parameters().get("p1")).isEqualTo(3); - } } diff --git a/src/test/java/org/mybatis/dynamic/sql/where/render/OptionalCriterionRenderTest.java b/src/test/java/org/mybatis/dynamic/sql/where/render/OptionalCriterionRenderTest.java index d9c80399b..75039e559 100644 --- a/src/test/java/org/mybatis/dynamic/sql/where/render/OptionalCriterionRenderTest.java +++ b/src/test/java/org/mybatis/dynamic/sql/where/render/OptionalCriterionRenderTest.java @@ -1,5 +1,5 @@ /** - * Copyright 2016-2018 the original author or authors. + * Copyright 2016-2019 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. @@ -19,6 +19,8 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.mybatis.dynamic.sql.SqlBuilder.*; +import java.util.Objects; + import org.junit.jupiter.api.Test; import org.mybatis.dynamic.sql.SqlColumn; import org.mybatis.dynamic.sql.SqlTable; @@ -48,7 +50,7 @@ public void testNoRenderableCriteria() { public void testNoRenderableCriteriaWithIf() { Integer nullId = null; - WhereClauseProvider whereClause = where(id, isEqualTo(nullId).when(v -> v != null)) + WhereClauseProvider whereClause = where(id, isEqualTo(nullId).when(Objects::nonNull)) .build() .render(RenderingStrategy.SPRING_NAMED_PARAMETER); diff --git a/src/test/resources/examples/schema_supplier/CreateDB.sql b/src/test/resources/examples/schema_supplier/CreateDB.sql new file mode 100644 index 000000000..59fe21efd --- /dev/null +++ b/src/test/resources/examples/schema_supplier/CreateDB.sql @@ -0,0 +1,32 @@ +-- +-- Copyright 2016-2019 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 +-- +-- http://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. +-- + +drop schema schema1 if exists cascade; +drop schema schema2 if exists cascade; + +create schema schema1 + create table User ( + user_id int not null, + user_name varchar(30) not null, + primary key (user_id) + ); + +create schema schema2 + create table User ( + user_id int not null, + user_name varchar(30) not null, + primary key (user_id) + );