Skip to content

Commit b794e3c

Browse files
committed
Rework jdbc connection string pattern
* Make it compliant with Couchbase's Java SDK
1 parent b256830 commit b794e3c

File tree

6 files changed

+94
-89
lines changed

6 files changed

+94
-89
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ sourceSets {
2727
}
2828

2929
dependencies {
30+
implementation 'org.jetbrains:annotations:19.0.0'
3031
compile 'com.couchbase.client:java-client:3.0.5'
3132
testCompile group: 'junit', name: 'junit', version: '4.12'
3233
}

driver/src/main/java/com/intellij/CouchbaseClientURI.java

Lines changed: 43 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -10,79 +10,58 @@
1010
import java.security.KeyStore;
1111
import java.sql.SQLException;
1212
import java.util.ArrayList;
13+
import java.util.Arrays;
1314
import java.util.HashMap;
15+
import java.util.HashSet;
1416
import java.util.List;
1517
import java.util.Locale;
1618
import java.util.Map;
1719
import java.util.Optional;
1820
import java.util.Properties;
21+
import java.util.Set;
22+
import java.util.stream.Collectors;
1923

2024
import static com.intellij.DriverPropertyInfoHelper.ENABLE_SSL;
2125
import static com.intellij.DriverPropertyInfoHelper.ENABLE_SSL_DEFAULT;
26+
import static com.intellij.DriverPropertyInfoHelper.PASSWORD;
27+
import static com.intellij.DriverPropertyInfoHelper.USER;
2228
import static com.intellij.DriverPropertyInfoHelper.isTrue;
2329

2430
class CouchbaseClientURI {
2531
static final String PREFIX = "jdbc:couchbase:";
2632

27-
private final String hosts;
28-
private final String keyspace;
29-
private final String collection;
33+
private static final Set<String> JDBC_KEYS = new HashSet<>(Arrays.asList(USER, PASSWORD, ENABLE_SSL));
34+
35+
private final String connectionString;
3036
private final String uri;
37+
private final String hosts;
3138
private final String userName;
3239
private final String password;
3340
private final boolean sslEnabled;
3441

3542
public CouchbaseClientURI(String uri, Properties info) {
3643
this.uri = uri;
37-
if (!uri.startsWith(PREFIX))
44+
if (!uri.startsWith(PREFIX)) {
3845
throw new IllegalArgumentException("URI needs to start with " + PREFIX);
46+
}
3947

40-
uri = uri.substring(PREFIX.length());
41-
42-
43-
String serverPart;
44-
String nsPart;
48+
String trimmedUri = uri.substring(PREFIX.length());
4549
Map<String, List<String>> options = null;
50+
String serverPart;
4651

47-
{
48-
int lastSlashIndex = uri.lastIndexOf("/");
49-
if (lastSlashIndex < 0) {
50-
if (uri.contains("?")) {
51-
throw new IllegalArgumentException("URI contains options without trailing slash");
52-
}
53-
serverPart = uri;
54-
nsPart = null;
55-
} else {
56-
serverPart = uri.substring(0, lastSlashIndex);
57-
nsPart = uri.substring(lastSlashIndex + 1);
58-
59-
int questionMarkIndex = nsPart.indexOf("?");
60-
if (questionMarkIndex >= 0) {
61-
options = parseOptions(nsPart.substring(questionMarkIndex + 1));
62-
nsPart = nsPart.substring(0, questionMarkIndex);
63-
}
64-
}
65-
}
66-
67-
hosts = serverPart;
68-
this.userName = getOption(info, options, "user", null);
69-
this.password = getOption(info, options, "password", null);
70-
String sslEnabledOption = getOption(info, options, ENABLE_SSL, ENABLE_SSL_DEFAULT);
71-
this.sslEnabled = isTrue(sslEnabledOption);
72-
73-
if (nsPart != null && nsPart.length() != 0) { // keyspace._collection
74-
int dotIndex = nsPart.indexOf(".");
75-
if (dotIndex < 0) {
76-
keyspace = nsPart;
77-
collection = null;
78-
} else {
79-
keyspace = nsPart.substring(0, dotIndex);
80-
collection = nsPart.substring(dotIndex + 1);
81-
}
52+
int optionsStartIndex = trimmedUri.indexOf("?");
53+
if (optionsStartIndex < 0) {
54+
serverPart = trimmedUri;
8255
} else {
83-
keyspace = null;
84-
collection = null;
56+
serverPart = trimmedUri.substring(0, optionsStartIndex);
57+
options = parseOptions(trimmedUri.substring(optionsStartIndex + 1));
8558
}
59+
60+
this.userName = getOption(info, options, USER, null);
61+
this.password = getOption(info, options, PASSWORD, null);
62+
this.sslEnabled = isTrue(getOption(info, options, ENABLE_SSL, ENABLE_SSL_DEFAULT));
63+
this.hosts = serverPart;
64+
this.connectionString = createConnectionString(serverPart, options);
8665
}
8766

8867
/**
@@ -114,7 +93,7 @@ Cluster createCluster() throws SQLException {
11493
}
11594
authenticator = PasswordAuthenticator.create(userName, password);
11695
}
117-
return Cluster.connect(hosts, ClusterOptions.clusterOptions(authenticator));
96+
return Cluster.connect(connectionString, ClusterOptions.clusterOptions(authenticator));
11897
}
11998

12099
private String getLastValue(final Map<String, List<String>> optionsMap, final String key) {
@@ -127,7 +106,7 @@ private String getLastValue(final Map<String, List<String>> optionsMap, final St
127106
private Map<String, List<String>> parseOptions(String optionsPart) {
128107
Map<String, List<String>> optionsMap = new HashMap<>();
129108

130-
for (String _part : optionsPart.split("[&;]")) {
109+
for (String _part : optionsPart.split("&")) {
131110
int idx = _part.indexOf("=");
132111
if (idx >= 0) {
133112
String key = _part.substring(0, idx).toLowerCase(Locale.ENGLISH);
@@ -144,6 +123,15 @@ private Map<String, List<String>> parseOptions(String optionsPart) {
144123
return optionsMap;
145124
}
146125

126+
private String createConnectionString(String hosts, Map<String, List<String>> optionsMap) {
127+
if (optionsMap == null) {
128+
return hosts;
129+
}
130+
return optionsMap.keySet().stream()
131+
.filter(key -> !JDBC_KEYS.contains(key))
132+
.map(key -> key + "=" + getLastValue(optionsMap, key))
133+
.collect(Collectors.joining("&", hosts + "?", ""));
134+
}
147135

148136
// ---------------------------------
149137

@@ -175,31 +163,21 @@ public Boolean getSslEnabled() {
175163
}
176164

177165
/**
178-
* Gets the list of hosts
166+
* Gets the list of hosts and params sent directly to Java SDK
179167
*
180168
* @return the host list
181169
*/
182-
public String getHosts() {
183-
return hosts;
170+
public String getConnectionString() {
171+
return connectionString;
184172
}
185173

186174
/**
187-
* Gets the keyspace name
188-
*
189-
* @return the keyspace name
190-
*/
191-
public String getKeyspace() {
192-
return keyspace;
193-
}
194-
195-
196-
/**
197-
* Gets the collection name
175+
* Gets the list of hosts
198176
*
199-
* @return the collection name
177+
* @return the host list
200178
*/
201-
public String getCollection() {
202-
return collection;
179+
public String getHosts() {
180+
return hosts;
203181
}
204182

205183
/**

driver/src/main/java/com/intellij/CouchbaseConnection.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.intellij;
22

33
import com.couchbase.client.java.Cluster;
4+
import org.jetbrains.annotations.NotNull;
45

56
import java.sql.Array;
67
import java.sql.Blob;
@@ -22,18 +23,20 @@
2223
import java.util.concurrent.Executor;
2324

2425
public class CouchbaseConnection implements Connection {
26+
private static final String RETURN_NULL_STRINGS_FROM_INTRO_QUERY_KEY =
27+
"couchbase.jdbc.return.null.strings.from.intro.query";
2528

2629
private final Cluster cluster;
2730
private final CouchbaseJdbcDriver driver;
28-
private final boolean returnNullStringsFromIntroQuery;
31+
private final Properties properties;
2932
private boolean isClosed = false;
3033
private boolean isReadOnly = false;
3134

32-
CouchbaseConnection(Cluster cluster, CouchbaseJdbcDriver couchbaseJdbcDriver,
33-
boolean returnNullStringsFromIntroQuery) {
35+
CouchbaseConnection(@NotNull Cluster cluster, @NotNull CouchbaseJdbcDriver couchbaseJdbcDriver,
36+
@NotNull Properties properties) {
3437
this.cluster = cluster;
35-
driver = couchbaseJdbcDriver;
36-
this.returnNullStringsFromIntroQuery = returnNullStringsFromIntroQuery;
38+
this.driver = couchbaseJdbcDriver;
39+
this.properties = properties;
3740
}
3841

3942
public String getCatalog() throws SQLException {
@@ -45,6 +48,10 @@ Cluster getCluster() {
4548
return cluster;
4649
}
4750

51+
Properties getProperties() {
52+
return properties;
53+
}
54+
4855
@Override
4956
public <T> T unwrap(Class<T> iface) throws SQLException {
5057
checkClosed();
@@ -81,6 +88,8 @@ public Statement createStatement(int resultSetType, int resultSetConcurrency, in
8188
public PreparedStatement prepareStatement(String sql) throws SQLException {
8289
checkClosed();
8390
try {
91+
boolean returnNullStringsFromIntroQuery =
92+
Boolean.parseBoolean(properties.getProperty(RETURN_NULL_STRINGS_FROM_INTRO_QUERY_KEY));
8493
return new CouchbasePreparedStatement(cluster, sql, returnNullStringsFromIntroQuery);
8594
} catch (Throwable t) {
8695
throw new SQLException(t.getMessage(), t);

driver/src/main/java/com/intellij/CouchbaseJdbcDriver.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.intellij;
22

33
import com.couchbase.client.java.Cluster;
4+
import org.jetbrains.annotations.NotNull;
5+
import org.jetbrains.annotations.Nullable;
46

57
import java.sql.Connection;
68
import java.sql.Driver;
@@ -18,7 +20,6 @@
1820
* Minimal implementation of the JDBC standards for the Couchbase database.
1921
*/
2022
public class CouchbaseJdbcDriver implements Driver {
21-
private static final String RETURN_NULL_STRINGS_FROM_INTRO_QUERY_KEY = "couchbase.jdbc.return.null.strings.from.intro.query";
2223

2324
static {
2425
try {
@@ -29,19 +30,19 @@ public class CouchbaseJdbcDriver implements Driver {
2930
}
3031

3132
/**
32-
* todo: change this syntax to more suitable for Couchbase
3333
* Connect to the database using a URL like :
34-
* jdbc:couchbase:host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[keyspace][?options]]
34+
* jdbc:couchbase:host1[:port1][,host2[:port2],...][?option=value[&option=value&...]]
3535
* The URL's hosts and ports configuration is passed as it is to the Couchbase native Java driver.
3636
*/
37-
public Connection connect(String url, Properties info) throws SQLException {
38-
if (url != null && acceptsURL(url)) {
39-
CouchbaseClientURI clientURI = new CouchbaseClientURI(url, info);
37+
public Connection connect(@NotNull String url, @Nullable Properties info) throws SQLException {
38+
if (acceptsURL(url)) {
4039
try {
40+
CouchbaseClientURI clientURI = new CouchbaseClientURI(url, info);
4141
Cluster cluster = clientURI.createCluster();
42-
boolean returnNullStringsFromIntroQuery =
43-
Boolean.parseBoolean(info.getProperty(RETURN_NULL_STRINGS_FROM_INTRO_QUERY_KEY));
44-
return new CouchbaseConnection(cluster, this, returnNullStringsFromIntroQuery);
42+
if (info == null) {
43+
info = new Properties();
44+
}
45+
return new CouchbaseConnection(cluster, this, info);
4546
} catch (Exception e) {
4647
throw new SQLException(e.getMessage(), e);
4748
}
@@ -50,10 +51,11 @@ public Connection connect(String url, Properties info) throws SQLException {
5051
}
5152

5253
/**
53-
* URLs accepted are of the form: jdbc:couchbase:host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[keyspace][?options]]
54+
* URLs accepted are of the form:
55+
* jdbc:couchbase:host1[:port1][,host2[:port2],...][?option=value[&option=value&...]]
5456
*/
5557
@Override
56-
public boolean acceptsURL(String url) {
58+
public boolean acceptsURL(@NotNull String url) {
5759
return url.startsWith(PREFIX);
5860
}
5961

driver/src/main/java/com/intellij/DriverPropertyInfoHelper.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
public class DriverPropertyInfoHelper {
88
public static final String ENABLE_SSL = "sslenabled";
99
public static final String ENABLE_SSL_DEFAULT = "false";
10+
public static final String USER = "user";
11+
public static final String PASSWORD = "password";
1012
private static final String[] choices = new String[]{"true", "false"};
1113

1214

1315
public static DriverPropertyInfo[] getPropertyInfo() {
1416
ArrayList<DriverPropertyInfo> propInfos = new ArrayList<>();
1517

1618
addPropInfo(propInfos, ENABLE_SSL, ENABLE_SSL_DEFAULT, "Enable ssl.", choices);
19+
addPropInfo(propInfos, USER, null, "Username.", null);
20+
addPropInfo(propInfos, PASSWORD, null, "Password.", null);
1721

1822
return propInfos.toArray(new DriverPropertyInfo[0]);
1923
}

driver/src/test/com/intellij/CassandraClientURITest.java renamed to driver/src/test/com/intellij/CouchbaseClientURITest.java

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,13 @@
1111
/**
1212
* @author Liudmila Kornilova
1313
**/
14-
public class CassandraClientURITest {
14+
public class CouchbaseClientURITest {
1515

1616
@Test(expected = IllegalArgumentException.class)
1717
public void testUriForDifferentDb() {
1818
new CouchbaseClientURI("jdbc:postgresql://localhost:54332/guest", null);
1919
}
2020

21-
@Test(expected = IllegalArgumentException.class)
22-
public void testUriWithInvalidParameters() {
23-
new CouchbaseClientURI("jdbc:couchbase1:localhost:9042?name=cassandra", null);
24-
}
25-
2621
@Test
2722
public void testSimpleUri() {
2823
CouchbaseClientURI uri = new CouchbaseClientURI("jdbc:couchbase:localhost:9042", null);
@@ -33,20 +28,36 @@ public void testSimpleUri() {
3328

3429
@Test
3530
public void testUriWithUserName() {
36-
CouchbaseClientURI uri = new CouchbaseClientURI("jdbc:couchbase:localhost:9042/?user=cassandra", null);
31+
CouchbaseClientURI uri = new CouchbaseClientURI("jdbc:couchbase:localhost:9042?user=cassandra", null);
3732
List<String> hosts = Arrays.asList(uri.getHosts().split(","));
3833
assertEquals(1, hosts.size());
3934
assertEquals("localhost:9042", hosts.get(0));
4035
assertEquals("cassandra", uri.getUsername());
4136
}
4237

38+
@Test
39+
public void testUriConnectionString() {
40+
String uriSting = "jdbc:couchbase:localhost:9042?user=cass&kv.timeout=123s&retry=exponential&password=pass";
41+
CouchbaseClientURI uri = new CouchbaseClientURI(uriSting, null);
42+
assertEquals("cass", uri.getUsername());
43+
assertEquals("pass", uri.getPassword());
44+
assertEquals("localhost:9042?kv.timeout=123s&retry=exponential", uri.getConnectionString());
45+
}
46+
47+
@Test
48+
public void testUriConnectionStringWithoutOptionsPart() {
49+
String uriSting = "jdbc:couchbase:127.0.0.1:9042?user=p&password=p";
50+
CouchbaseClientURI uri = new CouchbaseClientURI(uriSting, null);
51+
assertEquals("127.0.0.1:9042?", uri.getConnectionString());
52+
}
53+
4354
@Test
4455
public void testOptionsInProperties() {
4556
Properties properties = new Properties();
4657
properties.put("user", "NameFromProperties");
4758
properties.put("password", "PasswordFromProperties");
4859
CouchbaseClientURI uri = new CouchbaseClientURI(
49-
"jdbc:couchbase:localhost:9042/?user=cassandra&password=cassandra",
60+
"jdbc:couchbase:localhost:9042?user=cassandra&password=cassandra",
5061
properties);
5162
List<String> hosts = Arrays.asList(uri.getHosts().split(","));
5263
assertEquals(1, hosts.size());

0 commit comments

Comments
 (0)