diff --git a/java-spanner-jdbc/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java b/java-spanner-jdbc/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java index 7168e2f9204b..c7f30fcec1eb 100644 --- a/java-spanner-jdbc/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java +++ b/java-spanner-jdbc/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java @@ -20,16 +20,20 @@ import com.google.auth.ServiceAccountSigner; import com.google.auth.oauth2.UserCredentials; import com.google.cloud.spanner.Dialect; +import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.ResultSets; +import com.google.cloud.spanner.SpannerException; +import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.Struct; import com.google.cloud.spanner.Type; import com.google.cloud.spanner.Type.StructField; import com.google.cloud.spanner.connection.Connection.InternalMetadataQuery; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSet; -import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; @@ -40,6 +44,8 @@ import java.util.Collections; import java.util.Properties; import java.util.Scanner; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; /** {@link DatabaseMetaData} implementation for Cloud Spanner */ class JdbcDatabaseMetaData extends AbstractJdbcWrapper implements DatabaseMetaData { @@ -50,24 +56,36 @@ class JdbcDatabaseMetaData extends AbstractJdbcWrapper implements DatabaseMetaDa private static final String PRODUCT_NAME = "Google Cloud Spanner"; private static final String POSTGRESQL_PRODUCT_NAME = PRODUCT_NAME + " PostgreSQL"; + private static final ConcurrentMap SQL_CACHE = new ConcurrentHashMap<>(); + @VisibleForTesting - static String readSqlFromFile(String filename, Dialect dialect) { - InputStream in; - switch (dialect) { - case POSTGRESQL: - in = JdbcDatabaseMetaData.class.getResourceAsStream("postgresql/" + filename); - break; - case GOOGLE_STANDARD_SQL: - default: - in = JdbcDatabaseMetaData.class.getResourceAsStream(filename); + static String readSqlFromFile(String filename, Dialect dialect) throws SQLException { + try { + return SQL_CACHE.computeIfAbsent( + filename + "/" + dialect, (key) -> loadSqlFromFile(filename, dialect)); + } catch (SpannerException e) { + throw JdbcSqlExceptionFactory.of(e); + } + } + + private static String loadSqlFromFile(String filename, Dialect dialect) { + String resourcePath = dialect == Dialect.POSTGRESQL ? "postgresql/" + filename : filename; + InputStream in = JdbcDatabaseMetaData.class.getResourceAsStream(resourcePath); + if (in == null) { + throw SpannerExceptionFactory.newSpannerException( + ErrorCode.NOT_FOUND, "Resource not found: " + resourcePath); } - BufferedReader reader = new BufferedReader(new InputStreamReader(in)); StringBuilder builder = new StringBuilder(); - try (Scanner scanner = new Scanner(reader)) { + try (InputStream input = in; + InputStreamReader reader = new InputStreamReader(input, StandardCharsets.UTF_8); + Scanner scanner = new Scanner(reader)) { while (scanner.hasNextLine()) { String line = scanner.nextLine(); builder.append(line).append("\n"); } + } catch (IOException e) { + throw SpannerExceptionFactory.newSpannerException( + ErrorCode.INTERNAL, "Could not read SQL file " + resourcePath, e); } return builder.toString(); } diff --git a/java-spanner-jdbc/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java b/java-spanner-jdbc/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java index fc3168ab4f0a..935643930701 100644 --- a/java-spanner-jdbc/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java +++ b/java-spanner-jdbc/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java @@ -19,6 +19,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -574,4 +576,21 @@ public void testGetUserName() throws SQLException, IOException { DatabaseMetaData meta = new JdbcDatabaseMetaData(connection); assertEquals("test@test-project.iam.gserviceaccount.com", meta.getUserName()); } + + @Test + public void testReadSqlFromFileCaching() throws SQLException { + String sql1 = JdbcDatabaseMetaData.readSqlFromFile("DatabaseMetaData_GetTables.sql", dialect); + String sql2 = JdbcDatabaseMetaData.readSqlFromFile("DatabaseMetaData_GetTables.sql", dialect); + assertNotNull(sql1); + assertSame(sql1, sql2); + } + + @Test + public void testReadSqlFromFileNotFound() { + SQLException exception = + assertThrows( + SQLException.class, + () -> JdbcDatabaseMetaData.readSqlFromFile("NonExistent.sql", dialect)); + assertTrue(exception.getMessage().contains("Resource not found")); + } }