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";