From d4616c8130d851af2a7dfdf99b43059af2f437d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Tue, 11 Aug 2026 15:35:15 +0200 Subject: [PATCH 1/2] perf(spanner-jdbc): cache commonly used query parameter names Cache the most commonly used query parameter names for positional parameters to prevent the creation of new strings for every single SQL statement execution. --- .../spanner/jdbc/JdbcParameterMetaData.java | 3 ++- .../spanner/jdbc/JdbcPreparedStatement.java | 2 +- .../com/google/cloud/spanner/Statement.java | 23 ++++++++++++++++++- .../connection/AbstractStatementParser.java | 11 +++++++++ .../google/cloud/spanner/StatementTest.java | 15 ++++++++++++ .../connection/StatementParserTest.java | 14 +++++++++++ 6 files changed, 65 insertions(+), 3 deletions(-) diff --git a/java-spanner-jdbc/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterMetaData.java b/java-spanner-jdbc/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterMetaData.java index 784a2678fd29..9b13c339eebf 100644 --- a/java-spanner-jdbc/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterMetaData.java +++ b/java-spanner-jdbc/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterMetaData.java @@ -18,6 +18,7 @@ import com.google.cloud.spanner.JdbcDataTypeConverter; import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.Statement; import com.google.rpc.Code; import com.google.spanner.v1.StructType; import com.google.spanner.v1.StructType.Field; @@ -44,7 +45,7 @@ class JdbcParameterMetaData extends AbstractJdbcWrapper implements ParameterMeta private Field getField(int param) throws SQLException { JdbcPreconditions.checkArgument(param > 0 && param <= parameters.getFieldsCount(), param); - String paramName = "p" + param; + String paramName = Statement.getParameterName(param); return parameters.getFieldsList().stream() .filter(field -> field.getName().equals(paramName)) .findAny() diff --git a/java-spanner-jdbc/src/main/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatement.java b/java-spanner-jdbc/src/main/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatement.java index ad74234221c4..b3a40d0f0750 100644 --- a/java-spanner-jdbc/src/main/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatement.java +++ b/java-spanner-jdbc/src/main/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatement.java @@ -74,7 +74,7 @@ Statement createStatement() throws SQLException { ParametersInfo paramInfo = getParametersInfo(); Statement.Builder builder = Statement.newBuilder(paramInfo.sqlWithNamedParameters); for (int index = 1; index <= getParameters().getHighestIndex(); index++) { - getParameters().bindParameterValue(builder.bind("p" + index), index); + getParameters().bindParameterValue(builder.bind(Statement.getParameterName(index)), index); } return builder.build(); } diff --git a/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java b/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java index 1776139d81d1..989cbeb14817 100644 --- a/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java +++ b/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java @@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import com.google.api.core.InternalApi; import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode; import com.google.cloud.spanner.connection.AbstractStatementParser; import com.google.cloud.spanner.connection.AbstractStatementParser.ParametersInfo; @@ -63,6 +64,26 @@ public final class Statement implements Serializable { private final String sql; private final QueryOptions queryOptions; + private static final String[] PRECOMPUTED_P_PARAM_NAMES = new String[257]; + + static { + for (int index = 1; index <= 256; index++) { + PRECOMPUTED_P_PARAM_NAMES[index] = "p" + index; + } + } + + /** + * Returns the parameter name for the given 1-based index (e.g. "p1", "p2"). Precomputed string + * constants are returned for indices 1 to 256. + */ + @InternalApi + public static String getParameterName(int index) { + if (index > 0 && index <= 256) { + return PRECOMPUTED_P_PARAM_NAMES[index]; + } + return "p" + index; + } + private Statement(String sql, Map parameters, QueryOptions queryOptions) { this.sql = sql; this.parameters = parameters; @@ -347,7 +368,7 @@ private Map getUnnamedParametersMap(Object[] values) { Map parameters = new HashMap<>(); int index = 1; for (Object value : values) { - parameters.put("p" + (index++), Value.toValue(value)); + parameters.put(getParameterName(index++), Value.toValue(value)); } return parameters; } diff --git a/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java b/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java index fea032e2f525..4e95074b96e7 100644 --- a/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java +++ b/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java @@ -750,6 +750,17 @@ private String removeOpeningBrackets(String sql) { static final ReadQueryUpdateTransactionOption[] EMPTY_OPTIONS = new ReadQueryUpdateTransactionOption[0]; + /** + * Returns the parameter name for the given 1-based parameter index with the standard 'p' prefix + * (e.g. "p1", "p2"). + * + *

Delegates to {@link Statement#getParameterName(int)}. + */ + @InternalApi + public static String getParameterName(int index) { + return Statement.getParameterName(index); + } + /** Parameter information with positional parameters translated to named parameters. */ @InternalApi public static class ParametersInfo { diff --git a/java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/StatementTest.java b/java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/StatementTest.java index e4a036673bc4..05549f2b5e33 100644 --- a/java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/StatementTest.java +++ b/java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/StatementTest.java @@ -21,6 +21,7 @@ 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 com.google.cloud.ByteArray; @@ -185,4 +186,18 @@ public void equalsAndHashCode() { Statement.newBuilder("SELECT @x, @y").bind("x").to((Value) null).build()); tester.testEquals(); } + + @Test + public void testGetParameterName() { + assertEquals("p1", Statement.getParameterName(1)); + assertEquals("p256", Statement.getParameterName(256)); + assertEquals("p257", Statement.getParameterName(257)); + assertEquals("p0", Statement.getParameterName(0)); + assertEquals("p-1", Statement.getParameterName(-1)); + + // Verify instance caching for indices 1..256 + for (int i = 1; i <= 256; i++) { + assertSame(Statement.getParameterName(i), Statement.getParameterName(i)); + } + } } diff --git a/java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java b/java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java index 300517faaf0c..63b3a96df49a 100644 --- a/java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java +++ b/java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -1864,6 +1865,19 @@ public void testStatementCache_ParameterizedStatement() { assertEquals(1, stats.hitCount()); } + @Test + public void testGetParameterName() { + for (int i = 1; i <= 256; i++) { + assertEquals("p" + i, AbstractStatementParser.getParameterName(i)); + assertSame( + AbstractStatementParser.getParameterName(i), AbstractStatementParser.getParameterName(i)); + } + assertEquals("p257", AbstractStatementParser.getParameterName(257)); + assertEquals("p1000", AbstractStatementParser.getParameterName(1000)); + assertEquals("p0", AbstractStatementParser.getParameterName(0)); + assertEquals("p-1", AbstractStatementParser.getParameterName(-1)); + } + @Test public void testClientSideStatementWithComment() { String sql = "-- Null (no timeout)\n" + "SET STATEMENT_TIMEOUT=null"; From 3bb1d4cbd15301533a2d0c4769f1d9b7c82d2e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Tue, 18 Aug 2026 09:25:00 +0200 Subject: [PATCH 2/2] chore(spanner): precompute 950 query param names --- .../main/java/com/google/cloud/spanner/Statement.java | 10 ++++++---- .../java/com/google/cloud/spanner/StatementTest.java | 8 ++++---- .../cloud/spanner/connection/StatementParserTest.java | 4 ++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java b/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java index 989cbeb14817..40a57bd76ced 100644 --- a/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java +++ b/java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java @@ -64,21 +64,23 @@ public final class Statement implements Serializable { private final String sql; private final QueryOptions queryOptions; - private static final String[] PRECOMPUTED_P_PARAM_NAMES = new String[257]; + private static final int MAX_PRECOMPUTED_PARAM_NAMES = 950; + private static final String[] PRECOMPUTED_P_PARAM_NAMES = + new String[MAX_PRECOMPUTED_PARAM_NAMES + 1]; static { - for (int index = 1; index <= 256; index++) { + for (int index = 1; index <= MAX_PRECOMPUTED_PARAM_NAMES; index++) { PRECOMPUTED_P_PARAM_NAMES[index] = "p" + index; } } /** * Returns the parameter name for the given 1-based index (e.g. "p1", "p2"). Precomputed string - * constants are returned for indices 1 to 256. + * constants are returned for indices 1 to 950. */ @InternalApi public static String getParameterName(int index) { - if (index > 0 && index <= 256) { + if (index > 0 && index <= MAX_PRECOMPUTED_PARAM_NAMES) { return PRECOMPUTED_P_PARAM_NAMES[index]; } return "p" + index; diff --git a/java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/StatementTest.java b/java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/StatementTest.java index 05549f2b5e33..9381d17ed1ce 100644 --- a/java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/StatementTest.java +++ b/java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/StatementTest.java @@ -190,13 +190,13 @@ public void equalsAndHashCode() { @Test public void testGetParameterName() { assertEquals("p1", Statement.getParameterName(1)); - assertEquals("p256", Statement.getParameterName(256)); - assertEquals("p257", Statement.getParameterName(257)); + assertEquals("p950", Statement.getParameterName(950)); + assertEquals("p951", Statement.getParameterName(951)); assertEquals("p0", Statement.getParameterName(0)); assertEquals("p-1", Statement.getParameterName(-1)); - // Verify instance caching for indices 1..256 - for (int i = 1; i <= 256; i++) { + // Verify instance caching for indices 1..950 + for (int i = 1; i <= 950; i++) { assertSame(Statement.getParameterName(i), Statement.getParameterName(i)); } } diff --git a/java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java b/java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java index 1d1105e91c6d..2ccdfd7babe3 100644 --- a/java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java +++ b/java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java @@ -1868,12 +1868,12 @@ public void testStatementCache_ParameterizedStatement() { @Test public void testGetParameterName() { - for (int i = 1; i <= 256; i++) { + for (int i = 1; i <= 950; i++) { assertEquals("p" + i, AbstractStatementParser.getParameterName(i)); assertSame( AbstractStatementParser.getParameterName(i), AbstractStatementParser.getParameterName(i)); } - assertEquals("p257", AbstractStatementParser.getParameterName(257)); + assertEquals("p951", AbstractStatementParser.getParameterName(951)); assertEquals("p1000", AbstractStatementParser.getParameterName(1000)); assertEquals("p0", AbstractStatementParser.getParameterName(0)); assertEquals("p-1", AbstractStatementParser.getParameterName(-1));