Skip to content

Commit 2a60a67

Browse files
feat: add SQL Server JDBC support (#263)
* add support for mssql-jdbc driver * update artifact name and mssql-jdbc version * added integration tests for sqlserver * split jdbc and r2dbc connectors into separate folders * update mssql driver verison * added relative path to parent section of pom.xml * update integration tests * sqlserver integration tests * move core out of jdbc folder * remove folder restructuring * remove relativePath from poms other than sqlserver * Update sqlserver/pom.xml Co-authored-by: Kurtis Van Gent <31518063+kurtisvg@users.noreply.github.com> * add dependency info for SQL server to README Co-authored-by: Kurtis Van Gent <31518063+kurtisvg@users.noreply.github.com>
1 parent 062278c commit 2a60a67

File tree

6 files changed

+300
-2
lines changed

6 files changed

+300
-2
lines changed

README.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,34 @@ Include the following in the project's `pom.xml`:
7272
</dependency>
7373
```
7474

75-
#### Gradle
75+
##### Gradle
7676
Include the following the project's `gradle.build`
7777
```gradle
7878
compile 'com.google.cloud.sql:postgres-socket-factory:1.1.0'
7979
```
8080
*Note: Also include the JDBC Driver for MySQL, `org.postgresql:postgresql:<LATEST-VERSION>`
8181

82-
[//]: # ({x-version-update-end})
8382

83+
#### SQL Server
84+
85+
##### Maven
86+
Include the following in the project's `pom.xml`:
87+
```maven-pom
88+
<dependency>
89+
    <groupId>com.google.cloud.sql</groupId>
90+
    <artifactId>cloud-sql-connector-jdbc-sqlserver</artifactId>
91+
    <version>1.1.0</version>
92+
</dependency>
93+
```
94+
95+
##### Gradle
96+
Include the following the project's `gradle.build`
97+
```gradle
98+
compile 'com.google.cloud.sql:cloud-sql-connector-jdbc-sqlserver:1.1.0'
99+
```
100+
101+
102+
[//]: # ({x-version-update-end})
84103

85104
#### Creating the JDBC URL
86105

core/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<groupId>com.google.cloud.sql</groupId>
99
<artifactId>jdbc-socket-factory-parent</artifactId>
1010
<version>1.1.1-SNAPSHOT</version> <!-- {x-version-update:cloud-sql-java-connector:current} -->
11+
<relativePath>..</relativePath>
1112
</parent>
1213
<artifactId>jdbc-socket-factory-core</artifactId>
1314
<packaging>jar</packaging>

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
<module>connector-j-6</module>
6767
<module>connector-j-8</module>
6868
<module>postgres</module>
69+
<module>sqlserver</module>
6970
<module>r2dbc-core</module>
7071
<module>r2dbc-mysql</module>
7172
<module>r2dbc-postgres</module>

sqlserver/pom.xml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>com.google.cloud.sql</groupId>
9+
<artifactId>jdbc-socket-factory-parent</artifactId>
10+
<version>1.1.1-SNAPSHOT</version> <!-- {x-version-update:cloud-sql-java-connector:current} -->
11+
<relativePath>..</relativePath>
12+
</parent>
13+
<artifactId>cloud-sql-connector-jdbc-sqlserver</artifactId>
14+
<packaging>jar</packaging>
15+
16+
<name>Cloud SQL JDBC connector for SQL Server</name>
17+
<description>
18+
Socket factory for the Microsoft JDBC Driver for SQL Server that allows a user with the
19+
appropriate permissions to connect to a Cloud SQL database without having to deal with IP
20+
allowlisting or SSL certificates manually.
21+
</description>
22+
23+
<dependencies>
24+
<dependency>
25+
<groupId>com.microsoft.sqlserver</groupId>
26+
<artifactId>mssql-jdbc</artifactId>
27+
<version>9.1.0.jre8-preview</version>
28+
</dependency>
29+
<dependency>
30+
<groupId>com.google.cloud.sql</groupId>
31+
<artifactId>jdbc-socket-factory-core</artifactId>
32+
<version>1.1.1-SNAPSHOT</version>
33+
</dependency>
34+
<dependency>
35+
<groupId>junit</groupId>
36+
<artifactId>junit</artifactId>
37+
<version>4.13.1</version>
38+
<scope>test</scope>
39+
</dependency>
40+
<dependency>
41+
<groupId>com.google.truth</groupId>
42+
<artifactId>truth</artifactId>
43+
<version>1.0.1</version>
44+
<scope>test</scope>
45+
</dependency>
46+
<dependency>
47+
<groupId>com.zaxxer</groupId>
48+
<artifactId>HikariCP</artifactId>
49+
<version>3.4.5</version>
50+
<scope>test</scope>
51+
</dependency>
52+
</dependencies>
53+
54+
<profiles>
55+
<profile>
56+
<id>jar-with-driver-and-dependencies</id>
57+
<dependencies>
58+
<dependency>
59+
<groupId>com.microsoft.sqlserver</groupId>
60+
<artifactId>mssql-jdbc</artifactId>
61+
<version>9.1.0-SNAPSHOT</version>
62+
</dependency>
63+
</dependencies>
64+
</profile>
65+
</profiles>
66+
67+
</project>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2020 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.sql.sqlserver;
18+
19+
import com.google.cloud.sql.core.CoreSocketFactory;
20+
import java.io.IOException;
21+
import java.net.InetAddress;
22+
import java.net.Socket;
23+
import java.util.Properties;
24+
import java.util.logging.Logger;
25+
26+
public class SocketFactory extends javax.net.SocketFactory {
27+
28+
private static final Logger logger = Logger.getLogger(SocketFactory.class.getName());
29+
private Properties props = new Properties();
30+
31+
static {
32+
CoreSocketFactory.addArtifactId("cloud-sql-connector-jdbc-sqlserver");
33+
}
34+
35+
/**
36+
* Implements the {@link SocketFactory} constructor, which can be used to create authenticated
37+
* connections to a Cloud SQL instance.
38+
*/
39+
public SocketFactory(String instanceName) {
40+
this.props.setProperty(CoreSocketFactory.CLOUD_SQL_INSTANCE_PROPERTY, instanceName);
41+
}
42+
43+
@Override
44+
public Socket createSocket() throws IOException {
45+
return CoreSocketFactory.connect(props);
46+
}
47+
48+
@Override
49+
public Socket createSocket(String host, int port) throws IOException {
50+
throw new UnsupportedOperationException();
51+
}
52+
53+
@Override
54+
public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
55+
throws IOException {
56+
throw new UnsupportedOperationException();
57+
}
58+
59+
@Override
60+
public Socket createSocket(InetAddress host, int port) throws IOException {
61+
throw new UnsupportedOperationException();
62+
}
63+
64+
@Override
65+
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
66+
throws IOException {
67+
throw new UnsupportedOperationException();
68+
}
69+
}
70+
71+
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.sql.sqlserver;
18+
19+
20+
import static com.google.common.truth.Truth.assertThat;
21+
import static com.google.common.truth.Truth.assertWithMessage;
22+
23+
import com.google.common.collect.ImmutableList;
24+
import com.zaxxer.hikari.HikariConfig;
25+
import com.zaxxer.hikari.HikariDataSource;
26+
import java.sql.Connection;
27+
import java.sql.PreparedStatement;
28+
import java.sql.ResultSet;
29+
import java.sql.SQLException;
30+
import java.util.ArrayList;
31+
import java.util.List;
32+
import java.util.Properties;
33+
import java.util.UUID;
34+
import java.util.concurrent.TimeUnit;
35+
import org.junit.After;
36+
import org.junit.Before;
37+
import org.junit.BeforeClass;
38+
import org.junit.Rule;
39+
import org.junit.Test;
40+
import org.junit.rules.Timeout;
41+
import org.junit.runner.RunWith;
42+
import org.junit.runners.JUnit4;
43+
44+
45+
@RunWith(JUnit4.class)
46+
public class JdbcSqlServerIntegrationTests {
47+
48+
private static final String CONNECTION_NAME = System.getenv("SQLSERVER_CONNECTION_NAME");
49+
private static final String DB_NAME = System.getenv("SQLSERVER_DB");
50+
private static final String DB_USER = System.getenv("SQLSERVER_USER");
51+
private static final String DB_PASSWORD = System.getenv("SQLSERVER_PASS");
52+
private static ImmutableList<String> requiredEnvVars = ImmutableList
53+
.of("SQLSERVER_USER", "SQLSERVER_PASS", "SQLSERVER_DB", "SQLSERVER_CONNECTION_NAME");
54+
@Rule
55+
public Timeout globalTimeout = new Timeout(20, TimeUnit.SECONDS);
56+
57+
private HikariDataSource connectionPool;
58+
private String tableName;
59+
60+
@BeforeClass
61+
public static void checkEnvVars() {
62+
// Check that required env vars are set
63+
requiredEnvVars.stream().forEach((varName) -> {
64+
assertWithMessage(
65+
String.format("Environment variable '%s' must be set to perform these tests.", varName))
66+
.that(System.getenv(varName)).isNotEmpty();
67+
});
68+
}
69+
70+
@Before
71+
public void setUpPool() throws SQLException {
72+
73+
// Initialize connection pool
74+
HikariConfig config = new HikariConfig();
75+
config
76+
.setDataSourceClassName("com.microsoft.sqlserver.jdbc.SQLServerDataSource");
77+
config.setUsername(DB_USER); // e.g. "root", "sqlserver"
78+
config.setPassword(DB_PASSWORD); // e.g. "my-password"
79+
config.addDataSourceProperty("databaseName", DB_NAME);
80+
81+
config.addDataSourceProperty("socketFactoryClass",
82+
"com.google.cloud.sql.sqlserver.SocketFactory");
83+
config.addDataSourceProperty("socketFactoryConstructorArg", CONNECTION_NAME);
84+
85+
this.connectionPool = new HikariDataSource(config);
86+
this.tableName = String.format("books_%s", UUID.randomUUID().toString().replace("-", ""));
87+
88+
// Create table
89+
try (Connection conn = connectionPool.getConnection()) {
90+
String stmt = String.format("CREATE TABLE %s (", this.tableName)
91+
+ " ID CHAR(20) NOT NULL,"
92+
+ " TITLE TEXT NOT NULL"
93+
+ ");";
94+
try (PreparedStatement createTableStatement = conn.prepareStatement(stmt)) {
95+
createTableStatement.execute();
96+
}
97+
}
98+
}
99+
100+
101+
@After
102+
public void dropTableIfPresent() throws SQLException {
103+
try (Connection conn = connectionPool.getConnection()) {
104+
String stmt = String.format("DROP TABLE %s;", this.tableName);
105+
try (PreparedStatement dropTableStatement = conn.prepareStatement(stmt)) {
106+
dropTableStatement.execute();
107+
}
108+
}
109+
}
110+
111+
@Test
112+
public void pooledConnectionTest() throws SQLException {
113+
try (Connection conn = connectionPool.getConnection()) {
114+
String stmt = String.format("INSERT INTO %s (ID, TITLE) VALUES (?, ?)", this.tableName);
115+
try (PreparedStatement insertStmt = conn.prepareStatement(stmt)) {
116+
insertStmt.setString(1, "book1");
117+
insertStmt.setString(2, "Book One");
118+
insertStmt.execute();
119+
insertStmt.setString(1, "book2");
120+
insertStmt.setString(2, "Book Two");
121+
insertStmt.execute();
122+
}
123+
}
124+
125+
List<String> bookList = new ArrayList<>();
126+
try (Connection conn = connectionPool.getConnection()) {
127+
String stmt = String.format("SELECT TITLE FROM %s ORDER BY ID", this.tableName);
128+
try (PreparedStatement selectStmt = conn.prepareStatement(stmt)) {
129+
130+
ResultSet rs = selectStmt.executeQuery();
131+
while (rs.next()) {
132+
bookList.add(rs.getString("TITLE"));
133+
}
134+
}
135+
}
136+
assertThat(bookList).containsExactly("Book One", "Book Two");
137+
138+
}
139+
}

0 commit comments

Comments
 (0)