11package com .clickhouse .jdbc .internal ;
22
3- import com .clickhouse .client .ClickHouseNode ;
43import com .clickhouse .client .api .Client ;
54import 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 ;
126import java .net .URI ;
13- import java .net .URISyntaxException ;
14- import java .net .URL ;
157import java .sql .DriverPropertyInfo ;
168import java .sql .SQLException ;
17- import java .util .ArrayList ;
18- import java .util .Collections ;
199import java .util .Comparator ;
2010import java .util .HashMap ;
2111import java .util .List ;
2212import java .util .Map ;
2313import java .util .Properties ;
14+ import java .util .regex .Matcher ;
15+ import java .util .regex .Pattern ;
2416
2517public 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 ¶m2=}.
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 );
0 commit comments