Skip to content

Add SqlTable constructor with single Supplier #94

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/main/java/org/mybatis/dynamic/sql/SqlTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,16 @@ public class SqlTable {

private Supplier<String> nameSupplier;

protected SqlTable(String fullyQualifiedTableName) {
Objects.requireNonNull(fullyQualifiedTableName);
protected SqlTable(String tableName) {
Objects.requireNonNull(tableName);

this.nameSupplier = () -> tableName;
}

protected SqlTable(Supplier<String> tableNameSupplier) {
Objects.requireNonNull(tableNameSupplier);

this.nameSupplier = () -> fullyQualifiedTableName;
this.nameSupplier = tableNameSupplier;
}

protected SqlTable(Supplier<Optional<String>> schemaSupplier, String tableName) {
Expand Down Expand Up @@ -69,7 +75,7 @@ private String composeCatalogSchemaAndAndTable(String catalog, String schema, St
return catalog + "." + schema + "." + tableName; //$NON-NLS-1$ //$NON-NLS-2$
}

public String fullyQualifiedTableName() {
public String tableNameAtRuntime() {
return nameSupplier.get();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ private Optional<WhereClauseProvider> renderWhereClause(WhereModel whereModel) {

private String calculateDeleteStatement(Optional<WhereClauseProvider> whereClause) {
return "delete from" //$NON-NLS-1$
+ spaceBefore(deleteModel.table().fullyQualifiedTableName())
+ spaceBefore(deleteModel.table().tableNameAtRuntime())
+ spaceBefore(whereClause.map(WhereClauseProvider::getWhereClause));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private FieldAndValue toFieldAndValue(ValuePhraseVisitor visitor, InsertMapping

private String calculateInsertStatement(FieldAndValueCollector<T> collector) {
return "insert into" //$NON-NLS-1$
+ spaceBefore(model.table().fullyQualifiedTableName())
+ spaceBefore(model.table().tableNameAtRuntime())
+ spaceBefore(collector.columnsPhrase())
+ spaceBefore(collector.valuesPhrase());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public InsertStatementProvider<T> render() {

private String calculateInsertStatement(FieldAndValueCollector<T> collector) {
return "insert into" //$NON-NLS-1$
+ spaceBefore(model.table().fullyQualifiedTableName())
+ spaceBefore(model.table().tableNameAtRuntime())
+ spaceBefore(collector.columnsPhrase())
+ spaceBefore(collector.valuesPhrase());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public InsertSelectStatementProvider render() {

private String calculateInsertStatement(SelectStatementProvider selectStatement) {
return "insert into" //$NON-NLS-1$
+ spaceBefore(model.table().fullyQualifiedTableName())
+ spaceBefore(model.table().tableNameAtRuntime())
+ spaceBefore(calculateColumnsPhrase())
+ spaceBefore(selectStatement.getSelectStatement());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ private GuaranteedTableAliasCalculator(Map<SqlTable, String> aliases) {
public Optional<String> aliasForColumn(SqlTable table) {
return super.aliasForColumn(table)
.map(Optional::of)
.orElse(Optional.of(table.fullyQualifiedTableName()));
.orElse(Optional.of(table.tableNameAtRuntime()));
}

public static TableAliasCalculator of(Map<SqlTable, String> aliases) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public Optional<GroupByModel> groupByModel() {
}

public String calculateTableNameIncludingAlias(SqlTable table) {
return table.fullyQualifiedTableName()
return table.tableNameAtRuntime()
+ spaceBefore(tableAliasCalculator.aliasForTable(table));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ private FragmentCollector calculateColumnMappings() {

private String calculateUpdateStatement(FragmentCollector fc, Optional<WhereClauseProvider> whereClause) {
return "update" //$NON-NLS-1$
+ spaceBefore(updateModel.table().fullyQualifiedTableName())
+ spaceBefore(updateModel.table().tableNameAtRuntime())
+ spaceBefore(calculateSetPhrase(fc))
+ spaceBefore(whereClause.map(WhereClauseProvider::getWhereClause));
}
Expand Down
72 changes: 57 additions & 15 deletions src/site/markdown/docs/databaseObjects.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@ MyBatis Dynamic SQL works with Java objects that represent relational tables or

## 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.
The class `org.mybatis.dynamic.sql.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:
A table or view name in SQL 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
1. The catalog - which is optional and is rarely used outside of Microsoft SQL Server. If unspecified the default catalog will be used - and many databases only have one catalog
1. The schema - which is optional but is very often specified. If unspecified the default schema will be used
1. The table name - which is required

Typical examples of fully qualified names are as follows:
Typical examples of 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
- `"dbo..bar"` - a name with a catalog (dbo) and a table name (bar). This is typical for SQL Server
- `"foo.bar"` - a name with a schema (foo) and a table name (bar). This is typical in many databases when you want to access tables that are not in the default schema
- `"bar"` - a name with just a table name (bar). This will access a table or view in the default catalog and schema for a connection

In MyBatis Dynamic SQL, the table or view name can be specified in different ways:

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
1. The name can be a constant String
1. The name can be calculated at runtime based on a catalog and/or schema supplier functions and a constant table name
1. The name can be calculated at runtime with a name supplier function

### Constant Names

Expand All @@ -45,8 +45,7 @@ public class MyTable extends SqlTable {
}
```


### Dynamic Names
### Dynamic Catalog and/or Schema 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:
Expand Down Expand Up @@ -98,5 +97,48 @@ Catalog Supplier Value | Schema Supplier Value | Name | Fully Qualified Name
&lt;empty&gt; | &lt;empty&gt; | "MyTable" | "MyTable"


### Fully Dynamic Names
MyBatis Dynamic SQL allows you to dynamically specify a full table name. This is useful for applications where the database is sharded with different tables representing different shards of the whole. Dynamic names are used when you use a `SqlTable` constructor that accepts a single `java.util.function.Supplier` argument.

Note that this functionality should only be used for tables that have different names, but are otherwise identical.

For example, suppose you wanted to change the name based on the value of a system property. You could write a class like this:

```java
public class NameSupplier {
public static final String name_property = "nameToUse";

public static String namePropertyReader() {
return System.getProperty(name_property);
}
}
```

This class has a static method `namePropertyReader` that will return an `String` 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(NameSupplier::namePropertyReader);
}
}
```

Whenever the table is referenced for rendering SQL, the name will be calculated based on the current value of the system property.



## Column Representation

The class `org.mybatis.dynamic.sql.SqlColumn` is used to represent a column in a table or view. An `SqlColumn` is always associated with a `SqlTable`. In it's most basic form, the `SqlColumn` class holds a name and a reference to the `SqlTable` it is associated with.

The `SqlColumn` class has additional optional attributes that are useful for SQL rendering - especially in MyBatis3. These include:

* The `java.sql.JDBCType` of the column. This will be rendered into the MyBatis3 compatible parameter marker - which helps with picking type handlers and also inserting or updating null capable fields
* A String containing a type handler - either a type handler alias or the fully qualified type of a type handler. This will be rendered into the MyBatis3 compatible parameter marker

If you are not using MyBatis3, then you will not need to specify the JDBC Type or type handler.

Finally, the `SqlColumn` class has methods to designate a column alias or sort order for use in different SQL statements.

We recommend a usage pattern for creating table and column objects that provides quite a bit of flexibility for usage. See the [Quick Start](quickStart.html) page for a complete example.
53 changes: 37 additions & 16 deletions src/test/java/org/mybatis/dynamic/sql/SqlTableTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,82 +23,103 @@
import org.junit.jupiter.api.Test;

public class SqlTableTest {

private static final String NAME_PROPERTY = "nameProperty";

@Test
public void testFullName() {
SqlTable table = new SqlTable("my_table");
assertThat(table.tableNameAtRuntime()).isEqualTo("my_table");
}

@Test
public void testFullNameSupplier() {

System.setProperty(NAME_PROPERTY, "my_table");
SqlTable table = new SqlTable(SqlTableTest::namePropertyReader);
assertThat(table.tableNameAtRuntime()).isEqualTo("my_table");
System.clearProperty(NAME_PROPERTY);
}

@Test
public void testSchemaSupplierEmpty() {
SqlTable table = new SqlTable(Optional::empty, "my_table");
assertThat(table.fullyQualifiedTableName()).isEqualTo("my_table");
assertThat(table.tableNameAtRuntime()).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");
assertThat(table.tableNameAtRuntime()).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");
assertThat(table.tableNameAtRuntime()).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");
assertThat(table.tableNameAtRuntime()).isEqualTo("first_schema.my_table");

schemaSupplier.setFirst(false);
assertThat(table.fullyQualifiedTableName()).isEqualTo("second_schema.my_table");
assertThat(table.tableNameAtRuntime()).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");
assertThat(table.tableNameAtRuntime()).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");
assertThat(table.tableNameAtRuntime()).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");
assertThat(table.tableNameAtRuntime()).isEqualTo("first_catalog..my_table");

catalogSupplier.setFirst(false);
assertThat(table.fullyQualifiedTableName()).isEqualTo("second_catalog..my_table");
assertThat(table.tableNameAtRuntime()).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");
assertThat(table.tableNameAtRuntime()).isEqualTo("first_catalog.first_schema.my_table");

catalogSupplier.setFirst(false);
assertThat(table.fullyQualifiedTableName()).isEqualTo("second_catalog.first_schema.my_table");
assertThat(table.tableNameAtRuntime()).isEqualTo("second_catalog.first_schema.my_table");

catalogSupplier.setFirst(true);
schemaSupplier.setFirst(false);
assertThat(table.fullyQualifiedTableName()).isEqualTo("first_catalog.second_schema.my_table");
assertThat(table.tableNameAtRuntime()).isEqualTo("first_catalog.second_schema.my_table");

catalogSupplier.setFirst(false);
assertThat(table.fullyQualifiedTableName()).isEqualTo("second_catalog.second_schema.my_table");
assertThat(table.tableNameAtRuntime()).isEqualTo("second_catalog.second_schema.my_table");

catalogSupplier.setEmpty(true);
assertThat(table.fullyQualifiedTableName()).isEqualTo("second_schema.my_table");
assertThat(table.tableNameAtRuntime()).isEqualTo("second_schema.my_table");

schemaSupplier.setEmpty(true);
assertThat(table.fullyQualifiedTableName()).isEqualTo("my_table");
assertThat(table.tableNameAtRuntime()).isEqualTo("my_table");

catalogSupplier.setEmpty(false);
assertThat(table.fullyQualifiedTableName()).isEqualTo("second_catalog..my_table");
assertThat(table.tableNameAtRuntime()).isEqualTo("second_catalog..my_table");
}

private static String namePropertyReader() {
return System.getProperty(NAME_PROPERTY);
}

public static class MySchemaSupplier implements Supplier<Optional<String>> {
Expand Down