Skip to content

Commit 68f1e0b

Browse files
committed
#20 - Add Dialect initial support for H2, PostgreSQL, and Microsoft SQL Server.
We now provide dialect support for H2, PostgreSQL, and Microsoft SQL Server databases, configurable through AbstractR2dbcConfiguration. By default, we obtain the Dialect by inspecting ConnectionFactoryMetadata to identify the database and the most likely dialect to use. BindableOperation encapsulates statements/queries that can accept parameters. Use BindableOperation for statements through DatabaseClient. Extract SQL creation. Split integration test into abstract base class that can be implemented with a database-specific test class. Original pull request: #24.
1 parent dc3a0d5 commit 68f1e0b

File tree

51 files changed

+2594
-445
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2594
-445
lines changed

pom.xml

+23
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@
3030
<degraph-check.version>0.1.4</degraph-check.version>
3131
<hsqldb.version>2.4.1</hsqldb.version>
3232
<postgresql.version>42.2.5</postgresql.version>
33+
<mssql-jdbc.version>7.1.2.jre8-preview</mssql-jdbc.version>
3334
<r2dbc-spi.version>1.0.0.M6</r2dbc-spi.version>
3435
<r2dbc-postgresql.version>1.0.0.M6</r2dbc-postgresql.version>
36+
<r2dbc-h2.version>1.0.0.M6</r2dbc-h2.version>
37+
<r2dbc-mssql.version>1.0.0.M6</r2dbc-mssql.version>
3538
<testcontainers.version>1.10.1</testcontainers.version>
3639

3740
</properties>
@@ -235,13 +238,33 @@
235238
<scope>test</scope>
236239
</dependency>
237240

241+
<dependency>
242+
<groupId>com.microsoft.sqlserver</groupId>
243+
<artifactId>mssql-jdbc</artifactId>
244+
<version>${mssql-jdbc.version}</version>
245+
</dependency>
246+
238247
<dependency>
239248
<groupId>io.r2dbc</groupId>
240249
<artifactId>r2dbc-postgresql</artifactId>
241250
<version>${r2dbc-postgresql.version}</version>
242251
<scope>test</scope>
243252
</dependency>
244253

254+
<dependency>
255+
<groupId>io.r2dbc</groupId>
256+
<artifactId>r2dbc-h2</artifactId>
257+
<version>${r2dbc-h2.version}</version>
258+
<scope>test</scope>
259+
</dependency>
260+
261+
<dependency>
262+
<groupId>io.r2dbc</groupId>
263+
<artifactId>r2dbc-mssql</artifactId>
264+
<version>${r2dbc-mssql.version}</version>
265+
<scope>test</scope>
266+
</dependency>
267+
245268
<dependency>
246269
<groupId>de.schauderhaft.degraph</groupId>
247270
<artifactId>degraph-check</artifactId>
+29-5
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package org.springframework.data.r2dbc.repository.config;
16+
package org.springframework.data.r2dbc.config;
17+
18+
import io.r2dbc.spi.ConnectionFactory;
1719

1820
import java.util.Optional;
1921

20-
import io.r2dbc.spi.ConnectionFactory;
2122
import org.springframework.context.annotation.Bean;
2223
import org.springframework.context.annotation.Configuration;
24+
import org.springframework.data.r2dbc.dialect.Database;
25+
import org.springframework.data.r2dbc.dialect.Dialect;
2326
import org.springframework.data.r2dbc.function.DatabaseClient;
2427
import org.springframework.data.r2dbc.function.DefaultReactiveDataAccessStrategy;
2528
import org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy;
@@ -37,7 +40,7 @@
3740
* @author Mark Paluch
3841
* @see ConnectionFactory
3942
* @see DatabaseClient
40-
* @see EnableR2dbcRepositories
43+
* @see org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories
4144
*/
4245
@Configuration
4346
public abstract class AbstractR2dbcConfiguration {
@@ -50,6 +53,24 @@ public abstract class AbstractR2dbcConfiguration {
5053
*/
5154
public abstract ConnectionFactory connectionFactory();
5255

56+
/**
57+
* Return a {@link Dialect} for the given {@link ConnectionFactory}. This method attempts to resolve a {@link Dialect}
58+
* from {@link io.r2dbc.spi.ConnectionFactoryMetadata}. Override this method to specify a dialect instead of
59+
* attempting to resolve one.
60+
*
61+
* @param connectionFactory the configured {@link ConnectionFactory}.
62+
* @return the resolved {@link Dialect}.
63+
* @throws UnsupportedOperationException if the {@link Dialect} cannot be determined.
64+
*/
65+
public Dialect getDialect(ConnectionFactory connectionFactory) {
66+
67+
return Database.findDatabase(connectionFactory)
68+
.orElseThrow(() -> new UnsupportedOperationException(
69+
String.format("Cannot determine a dialect for %s using %s. Please provide a Dialect.",
70+
connectionFactory.getMetadata().getName(), connectionFactory)))
71+
.latestDialect();
72+
}
73+
5374
/**
5475
* Register a {@link DatabaseClient} using {@link #connectionFactory()} and {@link RelationalMappingContext}.
5576
*
@@ -86,18 +107,21 @@ public RelationalMappingContext r2dbcMappingContext(Optional<NamingStrategy> nam
86107
}
87108

88109
/**
89-
* Creates a {@link ReactiveDataAccessStrategy} using the configured {@link #r2dbcMappingContext(Optional) RelationalMappingContext}.
110+
* Creates a {@link ReactiveDataAccessStrategy} using the configured {@link #r2dbcMappingContext(Optional)
111+
* RelationalMappingContext}.
90112
*
91113
* @param mappingContext the configured {@link RelationalMappingContext}.
92114
* @return must not be {@literal null}.
93115
* @see #r2dbcMappingContext(Optional)
116+
* @see #getDialect(ConnectionFactory)
94117
* @throws IllegalArgumentException if any of the {@literal mappingContext} is {@literal null}.
95118
*/
96119
@Bean
97120
public ReactiveDataAccessStrategy reactiveDataAccessStrategy(RelationalMappingContext mappingContext) {
98121

99122
Assert.notNull(mappingContext, "MappingContext must not be null!");
100-
return new DefaultReactiveDataAccessStrategy(new BasicRelationalConverter(mappingContext));
123+
return new DefaultReactiveDataAccessStrategy(getDialect(connectionFactory()),
124+
new BasicRelationalConverter(mappingContext));
101125
}
102126

103127
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Configuration classes for Spring Data R2DBC.
3+
*/
4+
@org.springframework.lang.NonNullApi
5+
@org.springframework.lang.NonNullFields
6+
package org.springframework.data.r2dbc.config;

src/main/java/org/springframework/data/r2dbc/dialect/BindMarker.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public interface BindMarker {
2828
* {@literal null} values.
2929
* @see Statement#bind
3030
*/
31-
void bindValue(Statement<?> statement, Object value);
31+
void bind(Statement<?> statement, Object value);
3232

3333
/**
3434
* Bind a {@literal null} value to the {@link Statement} using the underlying binding strategy.
@@ -37,6 +37,5 @@ public interface BindMarker {
3737
* @param valueType value type, must not be {@literal null}.
3838
* @see Statement#bindNull
3939
*/
40-
4140
void bindNull(Statement<?> statement, Class<?> valueType);
4241
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package org.springframework.data.r2dbc.dialect;
2+
3+
import io.r2dbc.spi.ConnectionFactory;
4+
import io.r2dbc.spi.ConnectionFactoryMetadata;
5+
6+
import java.util.Arrays;
7+
import java.util.Locale;
8+
import java.util.Optional;
9+
10+
import org.springframework.util.Assert;
11+
12+
/**
13+
* Enumeration of known Databases for offline {@link Dialect} resolution. R2DBC {@link io.r2dbc.spi.ConnectionFactory}
14+
* provides {@link io.r2dbc.spi.ConnectionFactoryMetadata metadata} that allows resolving an appropriate {@link Dialect}
15+
* if none was configured explicitly.
16+
*
17+
* @author Mark Paluch
18+
*/
19+
public enum Database {
20+
21+
POSTGRES {
22+
@Override
23+
public String driverName() {
24+
return "PostgreSQL";
25+
}
26+
27+
@Override
28+
public Dialect latestDialect() {
29+
return PostgresDialect.INSTANCE;
30+
}
31+
},
32+
33+
SQL_SERVER {
34+
@Override
35+
public String driverName() {
36+
return "Microsoft SQL Server";
37+
}
38+
39+
@Override
40+
public Dialect latestDialect() {
41+
return SqlServerDialect.INSTANCE;
42+
}
43+
},
44+
45+
H2 {
46+
@Override
47+
public String driverName() {
48+
return "H2";
49+
}
50+
51+
@Override
52+
public Dialect latestDialect() {
53+
return H2Dialect.INSTANCE;
54+
}
55+
};
56+
57+
/**
58+
* Find a {@link Database} type using {@link ConnectionFactory} and its metadata.
59+
*
60+
* @param connectionFactory must not be {@literal null}.
61+
* @return the resolved {@link Database} or {@link Optional#empty()} if the database type cannot be determined from
62+
* {@link ConnectionFactory}.
63+
*/
64+
public static Optional<Database> findDatabase(ConnectionFactory connectionFactory) {
65+
66+
Assert.notNull(connectionFactory, "ConnectionFactor must not be null!");
67+
68+
ConnectionFactoryMetadata metadata = connectionFactory.getMetadata();
69+
70+
return Arrays.stream(values()).filter(it -> matches(metadata, it.driverName())).findFirst();
71+
}
72+
73+
private static boolean matches(ConnectionFactoryMetadata metadata, String databaseType) {
74+
return metadata.getName().toLowerCase(Locale.ENGLISH).contains(databaseType.toLowerCase(Locale.ENGLISH));
75+
}
76+
77+
/**
78+
* Returns the driver name.
79+
*
80+
* @return the driver name.
81+
* @see ConnectionFactoryMetadata#getName()
82+
*/
83+
public abstract String driverName();
84+
85+
/**
86+
* Returns the latest {@link Dialect} for the underlying database.
87+
*
88+
* @return the latest {@link Dialect} for the underlying database.
89+
*/
90+
public abstract Dialect latestDialect();
91+
92+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.springframework.data.r2dbc.dialect;
2+
3+
/**
4+
* Represents a dialect that is implemented by a particular database.
5+
*
6+
* @author Mark Paluch
7+
*/
8+
public interface Dialect {
9+
10+
/**
11+
* Returns the {@link BindMarkersFactory} used by this dialect.
12+
*
13+
* @return the {@link BindMarkersFactory} used by this dialect.
14+
*/
15+
BindMarkersFactory getBindMarkersFactory();
16+
17+
/**
18+
* Returns the statement to include for returning generated keys. The returned query is directly appended to
19+
* {@code INSERT} statements.
20+
*
21+
* @return the statement to include for returning generated keys.
22+
*/
23+
String returnGeneratedKeys();
24+
25+
/**
26+
* Return the {@link LimitClause} used by this dialect.
27+
*
28+
* @return the {@link LimitClause} used by this dialect.
29+
*/
30+
LimitClause limit();
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.springframework.data.r2dbc.dialect;
2+
3+
/**
4+
* An SQL dialect for H2 in Postgres Compatibility mode.
5+
*
6+
* @author Mark Paluch
7+
*/
8+
public class H2Dialect extends PostgresDialect {
9+
10+
/**
11+
* Singleton instance.
12+
*/
13+
public static final H2Dialect INSTANCE = new H2Dialect();
14+
15+
/*
16+
* (non-Javadoc)
17+
* @see org.springframework.data.r2dbc.dialect.Dialect#returnGeneratedKeys()
18+
*/
19+
@Override
20+
public String returnGeneratedKeys() {
21+
return "";
22+
}
23+
}

src/main/java/org/springframework/data/r2dbc/dialect/IndexedBindMarkers.java

+8-6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class IndexedBindMarkers implements BindMarkers {
1818
// access via COUNTER_INCREMENTER
1919
@SuppressWarnings("unused") private volatile int counter;
2020

21+
private final int offset;
2122
private final String prefix;
2223

2324
/**
@@ -29,9 +30,10 @@ class IndexedBindMarkers implements BindMarkers {
2930
IndexedBindMarkers(String prefix, int beginWith) {
3031
this.counter = beginWith;
3132
this.prefix = prefix;
33+
this.offset = 0 - beginWith;
3234
}
3335

34-
/*
36+
/*
3537
* (non-Javadoc)
3638
* @see org.springframework.data.r2dbc.dialect.BindMarkers#next()
3739
*/
@@ -40,7 +42,7 @@ public BindMarker next() {
4042

4143
int index = COUNTER_INCREMENTER.getAndIncrement(this);
4244

43-
return new IndexedBindMarker(prefix + "" + index, index);
45+
return new IndexedBindMarker(prefix + "" + index, index + offset);
4446
}
4547

4648
/**
@@ -57,7 +59,7 @@ static class IndexedBindMarker implements BindMarker {
5759
this.index = index;
5860
}
5961

60-
/*
62+
/*
6163
* (non-Javadoc)
6264
* @see org.springframework.data.r2dbc.dialect.BindMarker#getPlaceholder()
6365
*/
@@ -66,16 +68,16 @@ public String getPlaceholder() {
6668
return placeholder;
6769
}
6870

69-
/*
71+
/*
7072
* (non-Javadoc)
7173
* @see org.springframework.data.r2dbc.dialect.BindMarker#bindValue(io.r2dbc.spi.Statement, java.lang.Object)
7274
*/
7375
@Override
74-
public void bindValue(Statement<?> statement, Object value) {
76+
public void bind(Statement<?> statement, Object value) {
7577
statement.bind(this.index, value);
7678
}
7779

78-
/*
80+
/*
7981
* (non-Javadoc)
8082
* @see org.springframework.data.r2dbc.dialect.BindMarker#bindNull(io.r2dbc.spi.Statement, java.lang.Class)
8183
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.springframework.data.r2dbc.dialect;
2+
3+
/**
4+
* A clause representing Dialect-specific {@code LIMIT}.
5+
*
6+
* @author Mark Paluch
7+
*/
8+
public interface LimitClause {
9+
10+
/**
11+
* Returns the {@code LIMIT} clause
12+
*
13+
* @param limit the actual limit to use.
14+
* @return rendered limit clause.
15+
*/
16+
String getClause(long limit);
17+
18+
/**
19+
* Returns the {@code LIMIT} clause
20+
*
21+
* @param limit the actual limit to use.
22+
* @param offset the offset to start from.
23+
* @return rendered limit clause.
24+
*/
25+
String getClause(long limit, long offset);
26+
27+
/**
28+
* Returns the {@link Position} where to apply the {@link #getClause(long) clause}.
29+
*/
30+
Position getClausePosition();
31+
32+
/**
33+
* Enumeration of where to render the clause within the SQL statement.
34+
*/
35+
enum Position {
36+
37+
/**
38+
* Append the clause at the end of the statement.
39+
*/
40+
END
41+
}
42+
}

0 commit comments

Comments
 (0)