-
Notifications
You must be signed in to change notification settings - Fork 118
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add r2dbc support for MS SQL Server (#328)
* add mssql r2dbc support * add mssql r2dbc support * add mssql r2dbc support * switch to 0.8.5.RELEASE * switch to 1.1.1 * PR comments * remove unnecessary import * Correct name and description. * rename artifact and update readme * fix dependency convergence * fix tests * rename test file Co-authored-by: kurtisvg <[email protected]> Co-authored-by: Shubha Rajan <[email protected]>
- Loading branch information
1 parent
23860c2
commit fddcc7f
Showing
6 changed files
with
305 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<groupId>com.google.cloud.sql</groupId> | ||
<artifactId>jdbc-socket-factory-parent</artifactId> | ||
<version>1.1.1-SNAPSHOT</version> <!-- {x-version-update:cloud-sql-java-connector:current} --> | ||
</parent> | ||
<artifactId>cloud-sql-connector-r2dbc-sqlserver</artifactId> | ||
<packaging>jar</packaging> | ||
|
||
<name>Cloud SQL R2DBC connector for SQL Server</name> | ||
<description> | ||
Connection Factory for the R2DBC Driver for SQL Server that allows a user with the | ||
appropriate permissions to connect to a Cloud SQL database without having to deal with IP | ||
allowlisting or SSL certificates manually. | ||
</description> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>io.r2dbc</groupId> | ||
<artifactId>r2dbc-mssql</artifactId> | ||
<version>0.8.5.RELEASE</version> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.cloud.sql</groupId> | ||
<artifactId>cloud-sql-connector-r2dbc-core</artifactId> | ||
<version>1.1.1-SNAPSHOT</version> <!-- {x-version-update:cloud-sql-java-connector:current} --> | ||
</dependency> | ||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
<version>4.13.1</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.truth</groupId> | ||
<artifactId>truth</artifactId> | ||
<version>1.1</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.r2dbc</groupId> | ||
<artifactId>r2dbc-pool</artifactId> | ||
<version>0.8.5.RELEASE</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<profiles> | ||
<profile> | ||
<id>jar-with-driver-and-dependencies</id> | ||
<dependencies> | ||
<dependency> | ||
<groupId>io.r2dbc</groupId> | ||
<artifactId>r2dbc-mssql</artifactId> | ||
<version>0.8.5.RELEASE</version> | ||
</dependency> | ||
</dependencies> | ||
</profile> | ||
</profiles> | ||
|
||
</project> |
73 changes: 73 additions & 0 deletions
73
...-sqlserver/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderMssql.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* | ||
* Copyright 2020 Google Inc. All Rights Reserved. | ||
* | ||
* 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 com.google.cloud.sql.core; | ||
|
||
import static io.r2dbc.spi.ConnectionFactoryOptions.Builder; | ||
import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER; | ||
|
||
import io.netty.handler.ssl.SslContextBuilder; | ||
import io.r2dbc.mssql.MssqlConnectionFactoryProvider; | ||
import io.r2dbc.spi.ConnectionFactory; | ||
import io.r2dbc.spi.ConnectionFactoryOptions; | ||
import io.r2dbc.spi.ConnectionFactoryProvider; | ||
import java.util.function.Function; | ||
|
||
/** | ||
* {@link ConnectionFactoryProvider} for proxied access to GCP MsSQL instances. | ||
*/ | ||
public class GcpConnectionFactoryProviderMssql extends GcpConnectionFactoryProvider { | ||
|
||
static { | ||
CoreSocketFactory.addArtifactId("cloud-sql-connector-r2dbc-mssql"); | ||
} | ||
|
||
/** | ||
* MsSQL driver option value. | ||
*/ | ||
private static final String MSSQL_DRIVER = "mssql"; | ||
|
||
@Override | ||
boolean supportedProtocol(String protocol) { | ||
return protocol.equals(MSSQL_DRIVER); | ||
} | ||
|
||
@Override | ||
ConnectionFactory tcpConnectonFactory( | ||
Builder optionBuilder, | ||
Function<SslContextBuilder, SslContextBuilder> customizer, | ||
String csqlHostName) { | ||
optionBuilder | ||
.option(MssqlConnectionFactoryProvider.SSL_TUNNEL, customizer) | ||
.option(MssqlConnectionFactoryProvider.TCP_NODELAY, true) | ||
.option(MssqlConnectionFactoryProvider.TCP_KEEPALIVE, true); | ||
|
||
return new CloudSqlConnectionFactory( | ||
(ConnectionFactoryOptions options) -> new MssqlConnectionFactoryProvider().create(options), | ||
optionBuilder, | ||
csqlHostName); | ||
} | ||
|
||
@Override | ||
ConnectionFactory socketConnectionFactory(Builder optionBuilder, String socket) { | ||
throw new RuntimeException("UNIX socket connections are not supported"); | ||
} | ||
|
||
@Override | ||
Builder createBuilder(ConnectionFactoryOptions connectionFactoryOptions) { | ||
return connectionFactoryOptions.mutate().option(DRIVER, MSSQL_DRIVER); | ||
} | ||
} |
1 change: 1 addition & 0 deletions
1
r2dbc-sqlserver/src/main/resources/META-INF/services/io.r2dbc.spi.ConnectionFactoryProvider
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
com.google.cloud.sql.core.GcpConnectionFactoryProviderMssql |
132 changes: 132 additions & 0 deletions
132
r2dbc-sqlserver/src/test/java/com/google/cloud/sql/core/R2dbcSqlserverIntegrationTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
/* | ||
* Copyright 2020 Google LLC | ||
* | ||
* 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 com.google.cloud.sql.core; | ||
|
||
import static com.google.common.truth.Truth.assertThat; | ||
import static com.google.common.truth.Truth.assertWithMessage; | ||
|
||
import com.google.common.collect.ImmutableList; | ||
import io.r2dbc.pool.ConnectionPool; | ||
import io.r2dbc.pool.ConnectionPoolConfiguration; | ||
import io.r2dbc.spi.ConnectionFactories; | ||
import io.r2dbc.spi.ConnectionFactory; | ||
import java.util.List; | ||
import java.util.UUID; | ||
import java.util.concurrent.TimeUnit; | ||
import org.junit.After; | ||
import org.junit.Before; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.junit.rules.Timeout; | ||
import org.junit.runner.RunWith; | ||
import org.junit.runners.JUnit4; | ||
import reactor.core.publisher.Mono; | ||
|
||
@RunWith(JUnit4.class) | ||
public class R2dbcSqlserverIntegrationTests { | ||
|
||
private static final ImmutableList<String> requiredEnvVars = ImmutableList | ||
.of("SQLSERVER_USER", "SQLSERVER_PASS", "SQLSERVER_DB", "SQLSERVER_CONNECTION_NAME"); | ||
|
||
private static final String CONNECTION_NAME = System.getenv("SQLSERVER_CONNECTION_NAME"); | ||
private static final String DB_NAME = System.getenv("SQLSERVER_DB"); | ||
private static final String DB_USER = System.getenv("SQLSERVER_USER"); | ||
private static final String DB_PASSWORD = System.getenv("SQLSERVER_PASS"); | ||
|
||
@Rule | ||
public Timeout globalTimeout = new Timeout(20, TimeUnit.SECONDS); | ||
|
||
private ConnectionPool connectionPool; | ||
private String tableName; | ||
|
||
@Before | ||
public void setUpPool() { | ||
// Check that required env vars are set | ||
requiredEnvVars.forEach((varName) -> { | ||
assertWithMessage( | ||
String.format("Environment variable '%s' must be set to perform these tests.", varName)) | ||
.that(System.getenv(varName)).isNotEmpty(); | ||
}); | ||
|
||
// Set up URL parameters | ||
String r2dbcURL = String | ||
.format("r2dbc:gcp:mssql://%s:%s@%s/%s", DB_USER, DB_PASSWORD, CONNECTION_NAME, | ||
DB_NAME); | ||
|
||
// Initialize connection pool | ||
ConnectionFactory connectionFactory = ConnectionFactories.get(r2dbcURL); | ||
ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration | ||
.builder(connectionFactory) | ||
.build(); | ||
|
||
this.connectionPool = new ConnectionPool(configuration); | ||
this.tableName = String.format("books_%s", UUID.randomUUID().toString().replace("-", "")); | ||
|
||
// Create table | ||
Mono.from(this.connectionPool.create()) | ||
.flatMapMany( | ||
c -> | ||
c.createStatement( | ||
String.format("CREATE TABLE %s (", this.tableName) | ||
+ " ID CHAR(20) NOT NULL," | ||
+ " TITLE TEXT NOT NULL" | ||
+ ")") | ||
.execute()) | ||
.blockLast(); | ||
} | ||
|
||
@After | ||
public void dropTableIfPresent() { | ||
String dropStmt = String.format("DROP TABLE %s", this.tableName); | ||
Mono.from(this.connectionPool.create()) | ||
.delayUntil(c -> c.createStatement(dropStmt).execute()) | ||
.block(); | ||
} | ||
|
||
@Test | ||
public void pooledConnectionTest() { | ||
String insertStmt = String.format("INSERT INTO %s (ID, TITLE) VALUES (@id, @title)", this.tableName); | ||
Mono.from(this.connectionPool.create()) | ||
.flatMapMany( | ||
c -> | ||
c.createStatement(insertStmt) | ||
.bind("id", "book1") | ||
.bind("title", "Book One") | ||
.add() | ||
.bind("id", "book2") | ||
.bind("title", "Book Two") | ||
.execute()) | ||
.flatMap(result -> result.map((row, rowMetadata) -> row.get(0))) | ||
.blockLast(); | ||
|
||
String selectStmt = String.format("SELECT TITLE FROM %s ORDER BY ID", this.tableName); | ||
List<String> books = | ||
Mono.from(this.connectionPool.create()) | ||
.flatMapMany( | ||
connection -> | ||
connection.createStatement(selectStmt).execute()) | ||
.flatMap( | ||
result -> | ||
result.map( | ||
(r, meta) -> r.get("TITLE", String.class))) | ||
.collectList() | ||
.block(); | ||
|
||
assertThat(books).containsExactly("Book One", "Book Two"); | ||
|
||
} | ||
} |