Skip to content

Commit 31c628f

Browse files
olavloiterahul2393
andauthored
perf(spanner-jdbc): cache JDBC metadata query strings (#14041)
Cache the SQL strings that are loaded from disk for the standard JDBC metadata queries. --------- Co-authored-by: rahul2393 <irahul@google.com>
1 parent 7d00726 commit 31c628f

4 files changed

Lines changed: 126 additions & 20 deletions

File tree

java-spanner-jdbc/samples/spring-data-mybatis/googlesql/src/test/java/com/google/cloud/spanner/sample/ApplicationEmulatorTest.java

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,55 @@
1818

1919
import static org.junit.Assume.assumeTrue;
2020

21+
import com.google.cloud.spanner.connection.SpannerPool;
22+
import org.junit.AfterClass;
2123
import org.junit.BeforeClass;
2224
import org.junit.Test;
2325
import org.junit.runner.RunWith;
2426
import org.junit.runners.JUnit4;
27+
import org.springframework.boot.SpringApplication;
2528
import org.testcontainers.DockerClientFactory;
29+
import org.testcontainers.containers.GenericContainer;
30+
import org.testcontainers.containers.wait.strategy.Wait;
31+
import org.testcontainers.utility.DockerImageName;
2632

2733
@RunWith(JUnit4.class)
2834
public class ApplicationEmulatorTest {
35+
private static GenericContainer<?> emulator;
2936

3037
@BeforeClass
31-
public static void checkDocker() {
32-
assumeTrue(
33-
"Docker is required for this test", DockerClientFactory.instance().isDockerAvailable());
38+
public static void startEmulator() {
39+
assumeTrue(DockerClientFactory.instance().isDockerAvailable());
40+
41+
emulator =
42+
new GenericContainer<>(
43+
DockerImageName.parse("gcr.io/cloud-spanner-emulator/emulator:latest"))
44+
.withExposedPorts(9010)
45+
.waitingFor(Wait.forLogMessage(".*gRPC server listening at.*\\n", 1));
46+
emulator.start();
47+
}
48+
49+
@AfterClass
50+
public static void cleanup() {
51+
SpannerPool.closeSpannerPool();
52+
if (emulator != null) {
53+
emulator.stop();
54+
}
55+
System.clearProperty("open_telemetry.enabled");
56+
System.clearProperty("open_telemetry.project");
57+
System.clearProperty("spanner.emulator");
58+
System.clearProperty("spanner.auto_start_emulator");
59+
System.clearProperty("spanner.endpoint");
3460
}
3561

3662
@Test
37-
public void testRunApplicationOnEmulator() {
63+
public void testRunApplication() {
64+
System.setProperty("open_telemetry.enabled", "false");
65+
System.setProperty("open_telemetry.project", "test-project");
3866
System.setProperty("spanner.emulator", "true");
39-
System.setProperty("spanner.auto_start_emulator", "true");
40-
Application.main(new String[] {});
67+
System.setProperty("spanner.auto_start_emulator", "false");
68+
System.setProperty(
69+
"spanner.endpoint", String.format("//localhost:%d", emulator.getMappedPort(9010)));
70+
SpringApplication.run(Application.class).close();
4171
}
4272
}

java-spanner-jdbc/samples/spring-data-mybatis/postgresql/src/test/java/com/google/cloud/spanner/sample/ApplicationEmulatorTest.java

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,55 @@
1616

1717
package com.google.cloud.spanner.sample;
1818

19+
import static org.junit.Assume.assumeTrue;
20+
21+
import com.google.cloud.spanner.connection.SpannerPool;
22+
import org.junit.AfterClass;
23+
import org.junit.BeforeClass;
1924
import org.junit.Test;
2025
import org.junit.runner.RunWith;
2126
import org.junit.runners.JUnit4;
27+
import org.springframework.boot.SpringApplication;
28+
import org.testcontainers.DockerClientFactory;
29+
import org.testcontainers.containers.GenericContainer;
30+
import org.testcontainers.containers.wait.strategy.Wait;
31+
import org.testcontainers.utility.DockerImageName;
2232

2333
@RunWith(JUnit4.class)
2434
public class ApplicationEmulatorTest {
35+
private static GenericContainer<?> emulator;
36+
37+
@BeforeClass
38+
public static void startEmulator() {
39+
assumeTrue(DockerClientFactory.instance().isDockerAvailable());
40+
41+
emulator =
42+
new GenericContainer<>(
43+
DockerImageName.parse("gcr.io/cloud-spanner-emulator/emulator:latest"))
44+
.withExposedPorts(9010)
45+
.waitingFor(Wait.forLogMessage(".*gRPC server listening at.*\\n", 1));
46+
emulator.start();
47+
}
48+
49+
@AfterClass
50+
public static void cleanup() {
51+
SpannerPool.closeSpannerPool();
52+
if (emulator != null) {
53+
emulator.stop();
54+
}
55+
System.clearProperty("open_telemetry.enabled");
56+
System.clearProperty("open_telemetry.project");
57+
System.clearProperty("spanner.emulator");
58+
System.clearProperty("spanner.endpoint");
59+
}
2560

2661
@Test
27-
public void testRunApplicationOnEmulator() {
62+
public void testRunApplication() {
63+
System.setProperty("open_telemetry.enabled", "false");
64+
System.setProperty("open_telemetry.project", "test-project");
2865
System.setProperty("spanner.emulator", "true");
29-
Application.main(new String[] {});
66+
System.setProperty(
67+
"spanner.endpoint", String.format("//localhost:%d", emulator.getMappedPort(9010)));
68+
SpringApplication.run(Application.class).close();
3069
}
3170
}

java-spanner-jdbc/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,20 @@
2020
import com.google.auth.ServiceAccountSigner;
2121
import com.google.auth.oauth2.UserCredentials;
2222
import com.google.cloud.spanner.Dialect;
23+
import com.google.cloud.spanner.ErrorCode;
2324
import com.google.cloud.spanner.ResultSets;
25+
import com.google.cloud.spanner.SpannerException;
26+
import com.google.cloud.spanner.SpannerExceptionFactory;
2427
import com.google.cloud.spanner.Struct;
2528
import com.google.cloud.spanner.Type;
2629
import com.google.cloud.spanner.Type.StructField;
2730
import com.google.cloud.spanner.connection.Connection.InternalMetadataQuery;
2831
import com.google.common.annotations.VisibleForTesting;
2932
import com.google.common.collect.ImmutableSet;
30-
import java.io.BufferedReader;
33+
import java.io.IOException;
3134
import java.io.InputStream;
3235
import java.io.InputStreamReader;
36+
import java.nio.charset.StandardCharsets;
3337
import java.sql.Connection;
3438
import java.sql.DatabaseMetaData;
3539
import java.sql.ResultSet;
@@ -40,6 +44,8 @@
4044
import java.util.Collections;
4145
import java.util.Properties;
4246
import java.util.Scanner;
47+
import java.util.concurrent.ConcurrentHashMap;
48+
import java.util.concurrent.ConcurrentMap;
4349

4450
/** {@link DatabaseMetaData} implementation for Cloud Spanner */
4551
class JdbcDatabaseMetaData extends AbstractJdbcWrapper implements DatabaseMetaData {
@@ -50,24 +56,36 @@ class JdbcDatabaseMetaData extends AbstractJdbcWrapper implements DatabaseMetaDa
5056
private static final String PRODUCT_NAME = "Google Cloud Spanner";
5157
private static final String POSTGRESQL_PRODUCT_NAME = PRODUCT_NAME + " PostgreSQL";
5258

59+
private static final ConcurrentMap<String, String> SQL_CACHE = new ConcurrentHashMap<>();
60+
5361
@VisibleForTesting
54-
static String readSqlFromFile(String filename, Dialect dialect) {
55-
InputStream in;
56-
switch (dialect) {
57-
case POSTGRESQL:
58-
in = JdbcDatabaseMetaData.class.getResourceAsStream("postgresql/" + filename);
59-
break;
60-
case GOOGLE_STANDARD_SQL:
61-
default:
62-
in = JdbcDatabaseMetaData.class.getResourceAsStream(filename);
62+
static String readSqlFromFile(String filename, Dialect dialect) throws SQLException {
63+
try {
64+
return SQL_CACHE.computeIfAbsent(
65+
filename + "/" + dialect, (key) -> loadSqlFromFile(filename, dialect));
66+
} catch (SpannerException e) {
67+
throw JdbcSqlExceptionFactory.of(e);
68+
}
69+
}
70+
71+
private static String loadSqlFromFile(String filename, Dialect dialect) {
72+
String resourcePath = dialect == Dialect.POSTGRESQL ? "postgresql/" + filename : filename;
73+
InputStream in = JdbcDatabaseMetaData.class.getResourceAsStream(resourcePath);
74+
if (in == null) {
75+
throw SpannerExceptionFactory.newSpannerException(
76+
ErrorCode.NOT_FOUND, "Resource not found: " + resourcePath);
6377
}
64-
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
6578
StringBuilder builder = new StringBuilder();
66-
try (Scanner scanner = new Scanner(reader)) {
79+
try (InputStream input = in;
80+
InputStreamReader reader = new InputStreamReader(input, StandardCharsets.UTF_8);
81+
Scanner scanner = new Scanner(reader)) {
6782
while (scanner.hasNextLine()) {
6883
String line = scanner.nextLine();
6984
builder.append(line).append("\n");
7085
}
86+
} catch (IOException e) {
87+
throw SpannerExceptionFactory.newSpannerException(
88+
ErrorCode.INTERNAL, "Could not read SQL file " + resourcePath, e);
7189
}
7290
return builder.toString();
7391
}

java-spanner-jdbc/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import static org.junit.Assert.assertEquals;
2020
import static org.junit.Assert.assertFalse;
2121
import static org.junit.Assert.assertNotNull;
22+
import static org.junit.Assert.assertSame;
23+
import static org.junit.Assert.assertThrows;
2224
import static org.junit.Assert.assertTrue;
2325
import static org.mockito.Mockito.mock;
2426
import static org.mockito.Mockito.when;
@@ -574,4 +576,21 @@ public void testGetUserName() throws SQLException, IOException {
574576
DatabaseMetaData meta = new JdbcDatabaseMetaData(connection);
575577
assertEquals("test@test-project.iam.gserviceaccount.com", meta.getUserName());
576578
}
579+
580+
@Test
581+
public void testReadSqlFromFileCaching() throws SQLException {
582+
String sql1 = JdbcDatabaseMetaData.readSqlFromFile("DatabaseMetaData_GetTables.sql", dialect);
583+
String sql2 = JdbcDatabaseMetaData.readSqlFromFile("DatabaseMetaData_GetTables.sql", dialect);
584+
assertNotNull(sql1);
585+
assertSame(sql1, sql2);
586+
}
587+
588+
@Test
589+
public void testReadSqlFromFileNotFound() {
590+
SQLException exception =
591+
assertThrows(
592+
SQLException.class,
593+
() -> JdbcDatabaseMetaData.readSqlFromFile("NonExistent.sql", dialect));
594+
assertTrue(exception.getMessage().contains("Resource not found"));
595+
}
577596
}

0 commit comments

Comments
 (0)