Skip to content

Commit 2cddb44

Browse files
committed
feat(bigquery-jdbc): parse endpoint overrides and proxy settings from URL authority
1 parent 9567312 commit 2cddb44

2 files changed

Lines changed: 161 additions & 46 deletions

File tree

java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcUrlUtility.java

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.common.base.Splitter;
2323
import com.google.common.collect.ImmutableList;
2424
import com.google.common.net.UrlEscapers;
25+
import java.util.ArrayList;
2526
import java.util.Arrays;
2627
import java.util.Collections;
2728
import java.util.HashMap;
@@ -711,8 +712,10 @@ private static Map<String, String> parseUrlInternal(String url) {
711712
}
712713

713714
String[] urlParts = url.split(";", 2);
715+
parseAuthority(urlParts[0], map);
716+
714717
if (urlParts.length < 2) {
715-
return map;
718+
return Collections.unmodifiableMap(map);
716719
}
717720

718721
String urlToParse = urlParts[1];
@@ -754,7 +757,13 @@ private static Map<String, String> parseUrlInternal(String url) {
754757
}
755758
String propertyName = PROPERTY_NAME_MAP.get(key);
756759
String value = CharEscapers.decodeUriPath(kv[1].replace("+", "%2B"));
757-
map.put(propertyName, value);
760+
if (propertyName.equals(ENDPOINT_OVERRIDES_PROPERTY_NAME)
761+
&& map.containsKey(ENDPOINT_OVERRIDES_PROPERTY_NAME)) {
762+
String existing = map.get(ENDPOINT_OVERRIDES_PROPERTY_NAME);
763+
map.put(propertyName, mergeEndpointOverrides(existing, value));
764+
} else {
765+
map.put(propertyName, value);
766+
}
758767
}
759768
return Collections.unmodifiableMap(map);
760769
}
@@ -866,4 +875,65 @@ static Map<String, String> parsePropertiesMapFromValue(
866875
}
867876
return propertiesMap;
868877
}
878+
879+
private static void parseAuthority(String urlPart, Map<String, String> map) {
880+
String authority = urlPart.trim();
881+
if (authority.startsWith("jdbc:")) {
882+
authority = authority.substring(5);
883+
}
884+
if (authority.startsWith("bigquery://")) {
885+
authority = authority.substring(11);
886+
} else if (authority.startsWith("bigquery:")) {
887+
authority = authority.substring(9);
888+
}
889+
authority = authority.trim();
890+
if (authority.isEmpty()) {
891+
return;
892+
}
893+
894+
if (authority.startsWith("http://") || authority.startsWith("https://")) {
895+
map.put(
896+
ENDPOINT_OVERRIDES_PROPERTY_NAME,
897+
BIGQUERY_ENDPOINT_OVERRIDE_PROPERTY_NAME + "=" + authority);
898+
return;
899+
}
900+
901+
int colonIndex = authority.indexOf(':');
902+
if (colonIndex == -1) {
903+
map.put(PROXY_HOST_PROPERTY_NAME, authority);
904+
return;
905+
}
906+
907+
String host = authority.substring(0, colonIndex).trim();
908+
String port = authority.substring(colonIndex + 1).trim();
909+
if (!host.isEmpty()) {
910+
map.put(PROXY_HOST_PROPERTY_NAME, host);
911+
}
912+
if (!port.isEmpty()) {
913+
map.put(PROXY_PORT_PROPERTY_NAME, port);
914+
}
915+
}
916+
917+
private static String mergeEndpointOverrides(String existing, String newValue) {
918+
Map<String, String> merged = new LinkedHashMap<>();
919+
parseOverridesIntoMap(existing, merged);
920+
parseOverridesIntoMap(newValue, merged);
921+
List<String> parts = new ArrayList<>();
922+
for (Map.Entry<String, String> entry : merged.entrySet()) {
923+
parts.add(entry.getKey() + "=" + entry.getValue());
924+
}
925+
return String.join(",", parts);
926+
}
927+
928+
private static void parseOverridesIntoMap(String overrides, Map<String, String> targetMap) {
929+
if (overrides == null || overrides.isEmpty()) {
930+
return;
931+
}
932+
for (String part : overrides.split(",")) {
933+
String[] kv = part.split("=", 2);
934+
if (kv.length == 2) {
935+
targetMap.put(kv[0].trim(), kv[1].trim());
936+
}
937+
}
938+
}
869939
}

java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcUrlUtilityTest.java

Lines changed: 89 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -30,78 +30,60 @@
3030

3131
public class BigQueryJdbcUrlUtilityTest extends BigQueryJdbcLoggingBaseTest {
3232

33+
private static final String BASE_URL =
34+
"jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;";
35+
3336
@Test
3437
public void testParsePropertyWithNoDefault() {
35-
String url =
36-
"jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
37-
+ "ProjectId=MyBigQueryProject;"
38-
+ "OAuthAccessToken=RedactedToken";
38+
String url = BASE_URL + "ProjectId=MyBigQueryProject;OAuthAccessToken=RedactedToken";
3939

4040
String result = BigQueryJdbcUrlUtility.parseUriProperty(url, "OAuthType");
4141
assertThat(result).isNull();
4242
}
4343

4444
@Test
4545
public void testParseUrlWithUnknownProperty_no_exception() {
46-
String url =
47-
"jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
48-
+ "ProjectId=MyBigQueryProject;"
49-
+ "UnknownProperty=SomeValue";
46+
String url = BASE_URL + "ProjectId=MyBigQueryProject;UnknownProperty=SomeValue";
5047

5148
BigQueryJdbcUrlUtility.parseUriProperty(url, "ProjectId");
5249
assertThat(assertLogContains("Wrong value or unknown setting")).isTrue();
5350
}
5451

5552
@Test
5653
public void testParseUrlWithTypo_no_exception() {
57-
String url =
58-
"jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
59-
+ "ProjectId=MyBigQueryProject;"
60-
+ "ProjeectId=TypoValue";
54+
String url = BASE_URL + "ProjectId=MyBigQueryProject;ProjeectId=TypoValue";
6155

6256
assertDoesNotThrow(() -> BigQueryJdbcUrlUtility.parseUriProperty(url, "ProjectId"));
6357
assertThat(assertLogContains("Wrong value or unknown setting")).isTrue();
6458
}
6559

6660
@Test
6761
public void testParsePropertyWithDefault() {
68-
String url =
69-
"jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
70-
+ "ProjectId=MyBigQueryProject;"
71-
+ "OAuthAccessToken=RedactedToken";
62+
String url = BASE_URL + "ProjectId=MyBigQueryProject;OAuthAccessToken=RedactedToken";
7263

7364
String result = BigQueryJdbcUrlUtility.parseUriProperty(url, "OAuthType");
7465
assertThat(result).isNull();
7566
}
7667

7768
@Test
7869
public void testParsePropertyWithValue() {
79-
String url =
80-
"jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
81-
+ "ProjectId=MyBigQueryProject;"
82-
+ "OAuthAccessToken=RedactedToken";
70+
String url = BASE_URL + "ProjectId=MyBigQueryProject;OAuthAccessToken=RedactedToken";
8371

8472
String result = BigQueryJdbcUrlUtility.parseUriProperty(url, "ProjectId");
8573
assertThat(result).isEqualTo("MyBigQueryProject");
8674
}
8775

8876
@Test
8977
public void testParsePropertyWithValueCaseInsensitive() {
90-
String url =
91-
"jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
92-
+ "PROJECTID=MyBigQueryProject;"
93-
+ "OAuthAccessToken=RedactedToken";
78+
String url = BASE_URL + "PROJECTID=MyBigQueryProject;OAuthAccessToken=RedactedToken";
9479

9580
String result = BigQueryJdbcUrlUtility.parseUriProperty(url, "ProjectId");
9681
assertThat(result).isEqualTo("MyBigQueryProject");
9782
}
9883

9984
@Test
10085
public void testAppendPropertiesToURL() {
101-
String url =
102-
"jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
103-
+ "ProjectId=MyBigQueryProject;"
104-
+ "OAuthAccessToken=RedactedToken";
86+
String url = BASE_URL + "ProjectId=MyBigQueryProject;OAuthAccessToken=RedactedToken";
10587
Properties properties = new Properties();
10688
properties.setProperty("OAuthType", "3");
10789

@@ -140,7 +122,7 @@ public void testConnectionPropertiesFromURIMultilineNoSemicolon() {
140122
@Test
141123
public void testParseUrl_longUnknownProperty_sanitized() {
142124
String longKey = String.join("", Collections.nCopies(50, "a"));
143-
String url = "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;" + longKey + "=value";
125+
String url = BASE_URL + longKey + "=value";
144126

145127
assertDoesNotThrow(() -> BigQueryJdbcUrlUtility.parseUrl(url));
146128
String message = capturedLogs.get(0).getMessage();
@@ -153,50 +135,42 @@ public void testParseUrl_longUnknownProperty_sanitized() {
153135
@Test
154136
public void testParsePartnerTokenProperty() {
155137
// Case with partner name and environment
156-
String url =
157-
"jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
158-
+ "PartnerToken=(GPN:partner_company; dev);ProjectId=MyBigQueryProject;";
138+
String url = BASE_URL + "PartnerToken=(GPN:partner_company; dev);ProjectId=MyBigQueryProject;";
159139
String expected = " (GPN:partner_company; dev)";
160140
String result = BigQueryJdbcUrlUtility.parseUriProperty(url, "PartnerToken");
161141
assertThat(result).isEqualTo(expected);
162142

163143
// Case with only partner name
164-
url =
165-
"jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
166-
+ "PartnerToken=(GPN:another_partner);ProjectId=MyBigQueryProject;";
144+
url = BASE_URL + "PartnerToken=(GPN:another_partner);ProjectId=MyBigQueryProject;";
167145
expected = " (GPN:another_partner)";
168146
result = BigQueryJdbcUrlUtility.parseUriProperty(url, "PartnerToken");
169147
assertThat(result).isEqualTo(expected);
170148

171149
// Case when PartnerToken property is not present
172-
url =
173-
"jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
174-
+ "ProjectId=MyBigQueryProject;";
150+
url = BASE_URL + "ProjectId=MyBigQueryProject;";
175151
result = BigQueryJdbcUrlUtility.parseUriProperty(url, "PartnerToken");
176152
assertNull(result);
177153

178154
// Case when PartnerToken property is present but empty
179-
url = "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;PartnerToken=();";
155+
url = BASE_URL + "PartnerToken=();";
180156
result = BigQueryJdbcUrlUtility.parseUriProperty(url, "PartnerToken");
181157
assertNull(result);
182158

183159
// Case when PartnerToken property is present but without partner name
184-
url = "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;PartnerToken=(env);";
160+
url = BASE_URL + "PartnerToken=(env);";
185161
result = BigQueryJdbcUrlUtility.parseUriProperty(url, "PartnerToken");
186162
assertNull(result);
187163

188164
// Case with extra spaces around the values
189-
url =
190-
"jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
191-
+ "PartnerToken= ( GPN: partner_name ; test_env ) ;";
165+
url = BASE_URL + "PartnerToken= ( GPN: partner_name ; test_env ) ;";
192166
expected = " (GPN: partner_name ; test_env)";
193167
result = BigQueryJdbcUrlUtility.parseUriProperty(url, "PartnerToken");
194168
assertThat(result).isEqualTo(expected);
195169
}
196170

197171
@Test
198172
public void testAppendPropertiesToURL_propertyWithSemicolon_isEscaped() throws Exception {
199-
String url = "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;";
173+
String url = BASE_URL;
200174
Properties properties = new Properties();
201175
String complexValue = "value;ExtraProperty=injection";
202176
properties.setProperty("ProjectId", complexValue);
@@ -260,4 +234,75 @@ public void testUnrecognizedConnectionProperties() {
260234
String url2 = "jdbc:bigquery://;MalformedProperty";
261235
assertThrows(BigQueryJdbcRuntimeException.class, () -> DataSource.fromUrl(url2));
262236
}
237+
238+
@Test
239+
public void testParseAuthorityEndpointOverride() {
240+
String url = "jdbc:bigquery://https://custom-endpoint.com:443;ProjectId=MyProject";
241+
String endpointOverride = BigQueryJdbcUrlUtility.parseUriProperty(url, "EndpointOverrides");
242+
assertThat(endpointOverride).isEqualTo("BIGQUERY=https://custom-endpoint.com:443");
243+
}
244+
245+
@Test
246+
public void testParseAuthorityEndpointOverrideNoSemicolon() {
247+
String url = "jdbc:bigquery://https://custom-endpoint.com:443";
248+
String endpointOverride = BigQueryJdbcUrlUtility.parseUriProperty(url, "EndpointOverrides");
249+
assertThat(endpointOverride).isEqualTo("BIGQUERY=https://custom-endpoint.com:443");
250+
}
251+
252+
@Test
253+
public void testParseAuthorityProxy() {
254+
String url = "jdbc:bigquery://proxy.example.com:8080;ProjectId=MyProject";
255+
String proxyHost = BigQueryJdbcUrlUtility.parseUriProperty(url, "ProxyHost");
256+
String proxyPort = BigQueryJdbcUrlUtility.parseUriProperty(url, "ProxyPort");
257+
assertThat(proxyHost).isEqualTo("proxy.example.com");
258+
assertThat(proxyPort).isEqualTo("8080");
259+
}
260+
261+
@Test
262+
public void testParseAuthorityProxyNoPort() {
263+
String url = "jdbc:bigquery://proxy.example.com;ProjectId=MyProject";
264+
String proxyHost = BigQueryJdbcUrlUtility.parseUriProperty(url, "ProxyHost");
265+
String proxyPort = BigQueryJdbcUrlUtility.parseUriProperty(url, "ProxyPort");
266+
assertThat(proxyHost).isEqualTo("proxy.example.com");
267+
assertThat(proxyPort).isNull();
268+
}
269+
270+
@Test
271+
public void testParseAuthorityAndMergeEndpointOverrides() {
272+
String url =
273+
BASE_URL
274+
+ "EndpointOverrides=READ_API=https://storage-endpoint.com:443;ProjectId=MyProject";
275+
String endpointOverride = BigQueryJdbcUrlUtility.parseUriProperty(url, "EndpointOverrides");
276+
assertThat(endpointOverride)
277+
.isEqualTo(
278+
"BIGQUERY=https://www.googleapis.com/bigquery/v2:443,READ_API=https://storage-endpoint.com:443");
279+
}
280+
281+
@Test
282+
public void testParseAuthorityAndOverwriteEndpointOverrides() {
283+
String url =
284+
BASE_URL
285+
+ "EndpointOverrides=BIGQUERY=https://another-endpoint.com:443,READ_API=https://storage-endpoint.com:443;ProjectId=MyProject";
286+
String endpointOverride = BigQueryJdbcUrlUtility.parseUriProperty(url, "EndpointOverrides");
287+
assertThat(endpointOverride)
288+
.isEqualTo(
289+
"BIGQUERY=https://another-endpoint.com:443,READ_API=https://storage-endpoint.com:443");
290+
}
291+
292+
@Test
293+
public void testDataSourceFromUrlAuthorityEndpoint() {
294+
String url = BASE_URL + "ProjectId=MyProject";
295+
DataSource ds = DataSource.fromUrl(url);
296+
assertThat(ds.getProjectId()).isEqualTo("MyProject");
297+
assertThat(ds.getOverrideProperties().get("BIGQUERY"))
298+
.isEqualTo("https://www.googleapis.com/bigquery/v2:443");
299+
}
300+
301+
@Test
302+
public void testDataSourceFromUrlAuthorityProxy() {
303+
String url = "jdbc:bigquery://proxy.example.com:8080;ProjectId=MyProject";
304+
DataSource ds = DataSource.fromUrl(url);
305+
assertThat(ds.getProxyHost()).isEqualTo("proxy.example.com");
306+
assertThat(ds.getProxyPort()).isEqualTo("8080");
307+
}
263308
}

0 commit comments

Comments
 (0)