Skip to content

Commit e7ff741

Browse files
committed
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.
1 parent 96a7e6f commit e7ff741

5 files changed

Lines changed: 46 additions & 3 deletions

File tree

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.google.cloud.spanner.JdbcDataTypeConverter;
2020
import com.google.cloud.spanner.ResultSet;
21+
import com.google.cloud.spanner.connection.AbstractStatementParser;
2122
import com.google.rpc.Code;
2223
import com.google.spanner.v1.StructType;
2324
import com.google.spanner.v1.StructType.Field;
@@ -44,7 +45,7 @@ class JdbcParameterMetaData extends AbstractJdbcWrapper implements ParameterMeta
4445

4546
private Field getField(int param) throws SQLException {
4647
JdbcPreconditions.checkArgument(param > 0 && param <= parameters.getFieldsCount(), param);
47-
String paramName = "p" + param;
48+
String paramName = AbstractStatementParser.getParameterName(param);
4849
return parameters.getFieldsList().stream()
4950
.filter(field -> field.getName().equals(paramName))
5051
.findAny()

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.cloud.spanner.SpannerException;
2525
import com.google.cloud.spanner.Statement;
2626
import com.google.cloud.spanner.Type;
27+
import com.google.cloud.spanner.connection.AbstractStatementParser;
2728
import com.google.cloud.spanner.connection.AbstractStatementParser.ParametersInfo;
2829
import com.google.common.annotations.VisibleForTesting;
2930
import com.google.common.base.Preconditions;
@@ -74,7 +75,9 @@ Statement createStatement() throws SQLException {
7475
ParametersInfo paramInfo = getParametersInfo();
7576
Statement.Builder builder = Statement.newBuilder(paramInfo.sqlWithNamedParameters);
7677
for (int index = 1; index <= getParameters().getHighestIndex(); index++) {
77-
getParameters().bindParameterValue(builder.bind("p" + index), index);
78+
getParameters()
79+
.bindParameterValue(
80+
builder.bind(AbstractStatementParser.getParameterName(index)), index);
7881
}
7982
return builder.build();
8083
}

java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ private Map<String, Value> getUnnamedParametersMap(Object[] values) {
347347
Map<String, Value> parameters = new HashMap<>();
348348
int index = 1;
349349
for (Object value : values) {
350-
parameters.put("p" + (index++), Value.toValue(value));
350+
parameters.put(AbstractStatementParser.getParameterName(index++), Value.toValue(value));
351351
}
352352
return parameters;
353353
}

java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,31 @@ private String removeOpeningBrackets(String sql) {
750750
static final ReadQueryUpdateTransactionOption[] EMPTY_OPTIONS =
751751
new ReadQueryUpdateTransactionOption[0];
752752

753+
private static final int MAX_PRECOMPUTED_PARAM_INDEX = 256;
754+
private static final String[] PRECOMPUTED_P_PARAM_NAMES =
755+
new String[MAX_PRECOMPUTED_PARAM_INDEX + 1];
756+
757+
static {
758+
for (int i = 1; i <= MAX_PRECOMPUTED_PARAM_INDEX; i++) {
759+
PRECOMPUTED_P_PARAM_NAMES[i] = "p" + i;
760+
}
761+
}
762+
763+
/**
764+
* Returns the parameter name for the given 1-based parameter index with the standard 'p' prefix
765+
* (e.g. "p1", "p2").
766+
*
767+
* <p>Returns pre-allocated strings for indices between 1 and 256 to avoid heap allocations in hot
768+
* parameter binding loops.
769+
*/
770+
@InternalApi
771+
public static String getParameterName(int index) {
772+
if (index > 0 && index <= MAX_PRECOMPUTED_PARAM_INDEX) {
773+
return PRECOMPUTED_P_PARAM_NAMES[index];
774+
}
775+
return "p" + index;
776+
}
777+
753778
/** Parameter information with positional parameters translated to named parameters. */
754779
@InternalApi
755780
public static class ParametersInfo {

java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static org.junit.Assert.assertEquals;
2121
import static org.junit.Assert.assertFalse;
2222
import static org.junit.Assert.assertNotSame;
23+
import static org.junit.Assert.assertSame;
2324
import static org.junit.Assert.assertThrows;
2425
import static org.junit.Assert.assertTrue;
2526
import static org.junit.Assert.fail;
@@ -1864,6 +1865,19 @@ public void testStatementCache_ParameterizedStatement() {
18641865
assertEquals(1, stats.hitCount());
18651866
}
18661867

1868+
@Test
1869+
public void testGetParameterName() {
1870+
for (int i = 1; i <= 256; i++) {
1871+
assertEquals("p" + i, AbstractStatementParser.getParameterName(i));
1872+
assertSame(
1873+
AbstractStatementParser.getParameterName(i), AbstractStatementParser.getParameterName(i));
1874+
}
1875+
assertEquals("p257", AbstractStatementParser.getParameterName(257));
1876+
assertEquals("p1000", AbstractStatementParser.getParameterName(1000));
1877+
assertEquals("p0", AbstractStatementParser.getParameterName(0));
1878+
assertEquals("p-1", AbstractStatementParser.getParameterName(-1));
1879+
}
1880+
18671881
@Test
18681882
public void testClientSideStatementWithComment() {
18691883
String sql = "-- Null (no timeout)\n" + "SET STATEMENT_TIMEOUT=null";

0 commit comments

Comments
 (0)