Skip to content

Commit 1eb6aa3

Browse files
authored
perf(spanner-jdbc): cache commonly used query parameter names (#14036)
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 30e031b commit 1eb6aa3

6 files changed

Lines changed: 66 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.Statement;
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 = Statement.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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ Statement createStatement() throws SQLException {
7474
ParametersInfo paramInfo = getParametersInfo();
7575
Statement.Builder builder = Statement.newBuilder(paramInfo.sqlWithNamedParameters);
7676
for (int index = 1; index <= getParameters().getHighestIndex(); index++) {
77-
getParameters().bindParameterValue(builder.bind("p" + index), index);
77+
getParameters().bindParameterValue(builder.bind(Statement.getParameterName(index)), index);
7878
}
7979
return builder.build();
8080
}

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static com.google.common.base.Preconditions.checkNotNull;
2020
import static com.google.common.base.Preconditions.checkState;
2121

22+
import com.google.api.core.InternalApi;
2223
import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode;
2324
import com.google.cloud.spanner.connection.AbstractStatementParser;
2425
import com.google.cloud.spanner.connection.AbstractStatementParser.ParametersInfo;
@@ -63,6 +64,28 @@ public final class Statement implements Serializable {
6364
private final String sql;
6465
private final QueryOptions queryOptions;
6566

67+
private static final int MAX_PRECOMPUTED_PARAM_NAMES = 950;
68+
private static final String[] PRECOMPUTED_P_PARAM_NAMES =
69+
new String[MAX_PRECOMPUTED_PARAM_NAMES + 1];
70+
71+
static {
72+
for (int index = 1; index <= MAX_PRECOMPUTED_PARAM_NAMES; index++) {
73+
PRECOMPUTED_P_PARAM_NAMES[index] = "p" + index;
74+
}
75+
}
76+
77+
/**
78+
* Returns the parameter name for the given 1-based index (e.g. "p1", "p2"). Precomputed string
79+
* constants are returned for indices 1 to 950.
80+
*/
81+
@InternalApi
82+
public static String getParameterName(int index) {
83+
if (index > 0 && index <= MAX_PRECOMPUTED_PARAM_NAMES) {
84+
return PRECOMPUTED_P_PARAM_NAMES[index];
85+
}
86+
return "p" + index;
87+
}
88+
6689
private Statement(String sql, Map<String, Value> parameters, QueryOptions queryOptions) {
6790
this.sql = sql;
6891
this.parameters = parameters;
@@ -347,7 +370,7 @@ private Map<String, Value> getUnnamedParametersMap(Object[] values) {
347370
Map<String, Value> parameters = new HashMap<>();
348371
int index = 1;
349372
for (Object value : values) {
350-
parameters.put("p" + (index++), Value.toValue(value));
373+
parameters.put(getParameterName(index++), Value.toValue(value));
351374
}
352375
return parameters;
353376
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,17 @@ private String removeOpeningBrackets(String sql) {
776776
static final ReadQueryUpdateTransactionOption[] EMPTY_OPTIONS =
777777
new ReadQueryUpdateTransactionOption[0];
778778

779+
/**
780+
* Returns the parameter name for the given 1-based parameter index with the standard 'p' prefix
781+
* (e.g. "p1", "p2").
782+
*
783+
* <p>Delegates to {@link Statement#getParameterName(int)}.
784+
*/
785+
@InternalApi
786+
public static String getParameterName(int index) {
787+
return Statement.getParameterName(index);
788+
}
789+
779790
/** Parameter information with positional parameters translated to named parameters. */
780791
@InternalApi
781792
public static class ParametersInfo {

java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/StatementTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static org.junit.Assert.assertEquals;
2222
import static org.junit.Assert.assertFalse;
2323
import static org.junit.Assert.assertNotNull;
24+
import static org.junit.Assert.assertSame;
2425
import static org.junit.Assert.assertThrows;
2526

2627
import com.google.cloud.ByteArray;
@@ -185,4 +186,18 @@ public void equalsAndHashCode() {
185186
Statement.newBuilder("SELECT @x, @y").bind("x").to((Value) null).build());
186187
tester.testEquals();
187188
}
189+
190+
@Test
191+
public void testGetParameterName() {
192+
assertEquals("p1", Statement.getParameterName(1));
193+
assertEquals("p950", Statement.getParameterName(950));
194+
assertEquals("p951", Statement.getParameterName(951));
195+
assertEquals("p0", Statement.getParameterName(0));
196+
assertEquals("p-1", Statement.getParameterName(-1));
197+
198+
// Verify instance caching for indices 1..950
199+
for (int i = 1; i <= 950; i++) {
200+
assertSame(Statement.getParameterName(i), Statement.getParameterName(i));
201+
}
202+
}
188203
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1866,6 +1866,19 @@ public void testStatementCache_ParameterizedStatement() {
18661866
assertEquals(1, stats.hitCount());
18671867
}
18681868

1869+
@Test
1870+
public void testGetParameterName() {
1871+
for (int i = 1; i <= 950; i++) {
1872+
assertEquals("p" + i, AbstractStatementParser.getParameterName(i));
1873+
assertSame(
1874+
AbstractStatementParser.getParameterName(i), AbstractStatementParser.getParameterName(i));
1875+
}
1876+
assertEquals("p951", AbstractStatementParser.getParameterName(951));
1877+
assertEquals("p1000", AbstractStatementParser.getParameterName(1000));
1878+
assertEquals("p0", AbstractStatementParser.getParameterName(0));
1879+
assertEquals("p-1", AbstractStatementParser.getParameterName(-1));
1880+
}
1881+
18691882
@Test
18701883
public void testPositionalParametersCache() {
18711884
CacheStats statsBefore = parser.getPositionalParametersCacheStats();

0 commit comments

Comments
 (0)