Skip to content

Commit 9aa0898

Browse files
committed
fixed parsing url parameters
1 parent e9459ae commit 9aa0898

File tree

2 files changed

+134
-36
lines changed

2 files changed

+134
-36
lines changed

jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcConfiguration.java

Lines changed: 80 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,18 @@
11
package com.clickhouse.jdbc.internal;
22

3-
import com.clickhouse.client.ClickHouseNode;
43
import com.clickhouse.client.api.Client;
54
import com.clickhouse.client.api.ClientConfigProperties;
6-
import com.clickhouse.client.api.enums.Protocol;
7-
import com.clickhouse.client.api.http.ClickHouseHttpProto;
8-
import com.clickhouse.data.ClickHouseUtils;
9-
import com.clickhouse.jdbc.Driver;
105

11-
import java.net.MalformedURLException;
126
import java.net.URI;
13-
import java.net.URISyntaxException;
14-
import java.net.URL;
157
import java.sql.DriverPropertyInfo;
168
import java.sql.SQLException;
17-
import java.util.ArrayList;
18-
import java.util.Collections;
199
import java.util.Comparator;
2010
import java.util.HashMap;
2111
import java.util.List;
2212
import java.util.Map;
2313
import java.util.Properties;
14+
import java.util.regex.Matcher;
15+
import java.util.regex.Pattern;
2416

2517
public class JdbcConfiguration {
2618
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(JdbcConfiguration.class);
@@ -31,7 +23,7 @@ public class JdbcConfiguration {
3123

3224
final boolean disableFrameworkDetection;
3325

34-
private final Map<String, String> clientProperties;
26+
final Map<String, String> clientProperties;
3527

3628
private final Map<String, String> driverProperties;
3729

@@ -51,10 +43,14 @@ public JdbcConfiguration(String url, Properties info) throws SQLException {
5143
this.disableFrameworkDetection = Boolean.parseBoolean(info.getProperty("disable_frameworks_detection", "false"));
5244
this.clientProperties = new HashMap<>();
5345
this.driverProperties = new HashMap<>();
54-
initProperties(stripUrlPrefix(url), info);
5546

47+
Map<String, String> urlProperties = parseUrl(url);
48+
String tmpConnectionUrl = urlProperties.remove(PARSE_URL_CONN_URL_PROP);
49+
initProperties(urlProperties, info);
50+
51+
// after initializing all properties - set final connection URL
5652
boolean useSSL = Boolean.parseBoolean(info.getProperty("ssl", "false"));
57-
this.connectionUrl = createConnectionURL(url, useSSL);
53+
this.connectionUrl = createConnectionURL(tmpConnectionUrl, useSSL);
5854
}
5955

6056
public static boolean acceptsURL(String url) {
@@ -76,7 +72,6 @@ public String getConnectionUrl() {
7672
* @return URL without JDBC prefix
7773
*/
7874
static String createConnectionURL(String url, boolean ssl) throws SQLException {
79-
url = stripUrlPrefix(url);
8075
if (url.startsWith("//")) {
8176
url = (ssl ? "https:" : "http:") + url;
8277
}
@@ -89,7 +84,7 @@ static String createConnectionURL(String url, boolean ssl) throws SQLException {
8984
}
9085
}
9186

92-
private static String stripUrlPrefix(String url) {
87+
private static String stripJDBCPrefix(String url) {
9388
if (url.startsWith(PREFIX_CLICKHOUSE)) {
9489
return url.substring(PREFIX_CLICKHOUSE.length());
9590
} else if (url.startsWith(PREFIX_CLICKHOUSE_SHORT)) {
@@ -101,25 +96,74 @@ private static String stripUrlPrefix(String url) {
10196

10297
List<DriverPropertyInfo> listOfProperties;
10398

104-
private void initProperties(String url, Properties providedProperties) {
99+
/**
100+
* RegExp that extracts main parts:
101+
* <ul>
102+
* <li>1 - protocol (ex.: {@code http:}) (optional)</li>
103+
* <li>2 - host (ex.: {@code localhost} (required)</li>
104+
* <li>3 - port (ex.: {@code 8123 } (optional)</li>
105+
* <li>4 - database name (optional)</li>
106+
* <li>5 - query parameters as is (optional)</li>
107+
* </ul>
108+
*/
109+
private static final Pattern URL_REGEXP = Pattern.compile("(https?:)?\\/\\/([\\w\\.\\-]+):?([\\d]*)(?:\\/([\\w]+))?\\/?\\??(.*)$");
105110

106-
// Parse url for database name and override
107-
try {
108-
URI tmp = new URI(url);
109-
String path = tmp.getPath();
110-
if (path != null) {
111-
String[] pathElements = path.split("([\\/]+)+", 3);
112-
if (pathElements.length > 2) {
113-
throw new IllegalArgumentException("There can be only one URL path element indicating a database name");
114-
} else if (pathElements.length == 2 && pathElements[1] != null && !pathElements[1].trim().isEmpty()) {
115-
providedProperties.put(ClientConfigProperties.DATABASE.getKey(), pathElements[1]);
111+
/**
112+
* Extracts positions of parameters names.
113+
* Match will be {@code param1=} or {@code &param2=}.
114+
* There is limitation to not have '=' in values.
115+
*/
116+
private static final Pattern PARAM_EXTRACT_REGEXP = Pattern.compile("(?:&?[\\w\\.]+)=(?:[\\\\w])*");
117+
private Map<String, String> parseUrl(String url) throws SQLException {
118+
Map<String, String> properties = new HashMap<>();
119+
120+
// process host and protocol
121+
url = stripJDBCPrefix(url);
122+
Matcher m = URL_REGEXP.matcher(url);
123+
if (!m.find()) {
124+
throw new SQLException("Invalid url " + url);
125+
}
126+
String proto = m.group(1);
127+
String host = m.group(2);
128+
String port = m.group(3);
129+
130+
String connectionUrl = (proto == null ? "" : proto) + "//" + host + (port.isEmpty() ? "" : ":" + port);
131+
properties.put(PARSE_URL_CONN_URL_PROP, connectionUrl);
132+
133+
// Set database if present
134+
String database = m.group(4);
135+
if (database != null && !database.isEmpty()) {
136+
properties.put(ClientConfigProperties.DATABASE.getKey(), database);
137+
}
138+
139+
// Parse query string
140+
String queryStr = m.group(5);
141+
if (queryStr != null && !queryStr.isEmpty()) {
142+
Matcher qm = PARAM_EXTRACT_REGEXP.matcher(queryStr);
143+
144+
if (qm.find()) {
145+
String name = queryStr.substring(qm.start() + (queryStr.charAt(qm.start()) == '&' ? 1 : 0), qm.end() - 1);
146+
int valStartPos = qm.end();
147+
while (qm.find()) {
148+
String value = queryStr.substring(valStartPos, qm.start());
149+
properties.put(name, value);
150+
name = queryStr.substring(qm.start() + (queryStr.charAt(qm.start()) == '&' ? 1 : 0), qm.end() - 1);
151+
valStartPos = qm.end();
116152
}
153+
154+
String value = queryStr.substring(valStartPos);
155+
properties.put(name, value);
117156
}
118-
} catch (URISyntaxException e) {
119-
throw new IllegalArgumentException("Invalid JDBC URL is specified");
120157
}
121158

122-
// Process properties
159+
return properties;
160+
}
161+
162+
private static final String PARSE_URL_CONN_URL_PROP = "connection_url";
163+
164+
private void initProperties(Map<String, String> urlProperties, Properties providedProperties) {
165+
166+
// Copy provided properties
123167
Map<String, String> props = new HashMap<>();
124168
for (Map.Entry<Object, Object> entry : providedProperties.entrySet()) {
125169
if (entry.getKey() instanceof String && entry.getValue() instanceof String) {
@@ -129,9 +173,15 @@ private void initProperties(String url, Properties providedProperties) {
129173
}
130174
}
131175

176+
for (Map.Entry<String, String> entry : urlProperties.entrySet()) {
177+
props.put(entry.getKey(), entry.getValue());
178+
}
179+
180+
// Process all properties
132181
Map<String, DriverPropertyInfo> propertyInfos = new HashMap<>();
182+
133183
// create initial list of properties that will be passed to a client
134-
for (Map.Entry<String, String> prop : ClickHouseUtils.extractParameters(url, props).entrySet()) {
184+
for (Map.Entry<String, String> prop : props.entrySet()) {
135185
DriverPropertyInfo propertyInfo = new DriverPropertyInfo(prop.getKey(), prop.getValue());
136186
propertyInfo.description = "(User Defined)";
137187
propertyInfos.put(prop.getKey(), propertyInfo);

jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcConfigurationTest.java

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,80 @@
77

88
import java.sql.DriverPropertyInfo;
99
import java.util.Arrays;
10+
import java.util.Collections;
1011
import java.util.Map;
1112
import java.util.Properties;
13+
import java.util.regex.Matcher;
14+
import java.util.regex.Pattern;
1215
import java.util.stream.Collectors;
1316

1417
import static org.testng.Assert.assertEquals;
1518

1619
public class JdbcConfigurationTest {
1720

1821
@Test(dataProvider = "testConnectionUrlDataProvider")
19-
public void testConnectionUrl(String jdbcUrl, String connectionUrl, Properties properties) throws Exception {
22+
public void testConnectionUrl(String jdbcUrl, String connectionUrl, Properties properties, Map<String, String> expectedClientProps) throws Exception {
2023
JdbcConfiguration configuration = new JdbcConfiguration(jdbcUrl, properties);
2124
assertEquals(configuration.getConnectionUrl(), connectionUrl);
25+
System.out.println(configuration.clientProperties);
26+
assertEquals(configuration.clientProperties, expectedClientProps);
27+
}
28+
29+
@Test
30+
public void testRegExp() {
31+
String url = "//localhost:8123/";
32+
33+
Pattern pattern = Pattern.compile("(https?:)?\\/\\/([\\w\\.\\-]+):?([\\d]*)(?:\\/([\\w]+))?\\/?\\??(.*)$");
34+
35+
36+
Matcher m = pattern.matcher(url);
37+
System.out.println("hasMatch: " + m.matches());
38+
for (int i = 1; i <= m.groupCount(); i++) {
39+
System.out.println("g: " + i + " > " + m.group(i));
40+
}
41+
}
42+
43+
@Test
44+
public void testRe3gExp() {
45+
String url = "param1=value1&param2=value2,value3,\"value4\"&p_p.3=\" = \"";
46+
47+
Pattern pattern = Pattern.compile("(?:&?[\\w\\.]+)=(?:[\\\\w])*");
48+
49+
50+
Matcher m = pattern.matcher(url);
51+
while (m.find()) {
52+
System.out.println("found: " + (url.substring(m.start(), m.end())));
53+
54+
}
55+
System.out.println("hasMatch: " + m.matches());
56+
for (int i = 1; i <= m.groupCount(); i++) {
57+
System.out.println("g: " + i + " > " + m.group(i));
58+
}
2259
}
2360

2461
@DataProvider(name = "testConnectionUrlDataProvider")
2562
public static Object[][] testConnectionUrlDataProvider() {
2663
Properties defaultProps = new Properties();
64+
defaultProps.setProperty(ClientConfigProperties.USER.getKey(), "default");
65+
defaultProps.setProperty(ClientConfigProperties.PASSWORD.getKey(), "");
2766
Properties useSSL = new Properties();
2867
useSSL.put(JdbcConfiguration.USE_SSL_PROP, "true");
2968

69+
Map<String, String> defaultParams = Map.of( "user", "default", "password", "");
70+
Map<String, String> simpleParams = Map.of( "database", "clickhouse", "param1", "value1", "param2", "value2", "user", "default", "password", "");
71+
Map<String, String> useSSLParams = Map.of("ssl", "true");
72+
Map<String, String> withListParams = Map.of("database", "default", "param1", "value1", "custom_header1", "val1,val2,val3", "user", "default", "password", "");
73+
Map<String, String> withListParamsQuotes = Map.of("database", "default", "param1", "value1", "custom_header1", "\"role 1,3,4\",'val2',val3", "user", "default", "password", "");
74+
75+
3076
return new Object[][] {
31-
{"jdbc:clickhouse://localhost:8123/", "http://localhost:8123", defaultProps},
32-
{"jdbc:clickhouse://localhost:8443/clickhouse?param1=value1&param2=value2", "http://localhost:8443", defaultProps},
33-
{"jdbc:clickhouse:https://localhost:8123/clickhouse?param1=value1&param2=value2", "https://localhost:8123", defaultProps},
34-
{"jdbc:clickhouse://localhost:8443/", "https://localhost:8443", useSSL},
35-
{"jdbc:clickhouse://localhost:8443/default?param1=value1&custom_header1=val1,val2,val3", "https://localhost:8443", useSSL},
77+
{"jdbc:clickhouse://localhost:8123/", "http://localhost:8123", defaultProps, defaultParams},
78+
{"jdbc:clickhouse://localhost:8443/clickhouse?param1=value1&param2=value2", "http://localhost:8443", defaultProps, simpleParams},
79+
{"jdbc:clickhouse:https://localhost:8123/clickhouse?param1=value1&param2=value2", "https://localhost:8123", defaultProps, simpleParams},
80+
{"jdbc:clickhouse://localhost:8443/", "https://localhost:8443", useSSL, useSSLParams},
81+
{"jdbc:clickhouse://localhost:8443/default?param1=value1&custom_header1=val1,val2,val3", "http://localhost:8443", defaultProps, withListParams},
82+
{"jdbc:clickhouse://localhost:8443/default?custom_header1=\"role 1,3,4\",'val2',val3&param1=value1", "http://localhost:8443", defaultProps, withListParamsQuotes},
83+
3684
};
3785
}
3886

0 commit comments

Comments
 (0)