Skip to content

Add the ability to calculate catalog and/or schema at runtime. #92

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 14 commits into from
May 23, 2019
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 49 additions & 6 deletions src/main/java/org/mybatis/dynamic/sql/SqlTable.java
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<String> nameSupplier;

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

this.nameSupplier = () -> fullyQualifiedTableName;
}

protected SqlTable(Supplier<Optional<String>> schemaSupplier, String tableName) {
this(Optional::empty, schemaSupplier, tableName);
}

protected SqlTable(Supplier<Optional<String>> catalogSupplier, Supplier<Optional<String>> schemaSupplier, String tableName) {
Objects.requireNonNull(catalogSupplier);
Objects.requireNonNull(schemaSupplier);
Objects.requireNonNull(tableName);

this.nameSupplier = () -> compose(catalogSupplier, schemaSupplier, tableName);
}

private String compose(Supplier<Optional<String>> catalogSupplier, Supplier<Optional<String>> schemaSupplier, String tableName) {
return catalogSupplier.get().map(c -> compose(c, schemaSupplier, tableName))
.orElse(compose(schemaSupplier, tableName));
}

private String compose(String catalog, Supplier<Optional<String>> schemaSupplier, String tableName) {
return schemaSupplier.get().map(s -> composeCatalogSchemaAndAndTable(catalog, s, tableName))
.orElse(composeCatalogAndTable(catalog, tableName));
}

private String name;
private String compose(Supplier<Optional<String>> 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 <T> SqlColumn<T> allColumns() {
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().name())
+ spaceBefore(deleteModel.table().fullyQualifiedTableName())
+ 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().name())
+ spaceBefore(model.table().fullyQualifiedTableName())
+ 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().name())
+ spaceBefore(model.table().fullyQualifiedTableName())
+ 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().name())
+ spaceBefore(model.table().fullyQualifiedTableName())
+ spaceBefore(calculateColumnsPhrase())
+ spaceBefore(selectStatement.getSelectStatement());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down 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.name()));
.orElse(Optional.of(table.fullyQualifiedTableName()));
}

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

public String calculateTableNameIncludingAlias(SqlTable table) {
return table.name()
return table.fullyQualifiedTableName()
+ 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().name())
+ spaceBefore(updateModel.table().fullyQualifiedTableName())
+ spaceBefore(calculateSetPhrase(fc))
+ spaceBefore(whereClause.map(WhereClauseProvider::getWhereClause));
}
Expand Down
102 changes: 102 additions & 0 deletions src/site/markdown/docs/databaseObjects.md
Original file line number Diff line number Diff line change
@@ -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<String> schemaPropertyReader() {
return Optional.ofNullable(System.getProperty(schema_property));
}
}
```

This class has a static method `schemaPropertyReader` that will return an `Optional<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(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<Optional<String>>` 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<Optional<String>>` for the catalog, `Supplier<Optional<String>>` 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"
&lt;empty&gt; | "MySchema" | "MyTable" | "MySchema.MyTable"
"MyCatalog" | &lt;empty&gt; | "MyTable" | "MyCatalog..MyTable"
&lt;empty&gt; | &lt;empty&gt; | "MyTable" | "MyTable"



## Column Representation
1 change: 1 addition & 0 deletions src/site/site.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<item href="docs/introduction.html" name="Introduction" />
<item href="docs/CHANGELOG.html" name="Change Log" />
<item href="docs/quickStart.html" name="Quick Start" />
<item href="docs/databaseObjects.html" name="Modeling Database Objects" />
<item href="docs/whereClauses.html" name="WHERE Clause Support" >
<item href="docs/conditions.html" name="WHERE Conditions"/>
</item>
Expand Down
26 changes: 26 additions & 0 deletions src/test/java/examples/schema_supplier/SchemaSupplier.java
Original file line number Diff line number Diff line change
@@ -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<String> schemaPropertyReader() {
return Optional.ofNullable(System.getProperty(schema_property));
}
}
Loading