Skip to content

Commit 0085947

Browse files
feat: support unnamed parameters
1 parent 4cf5261 commit 0085947

File tree

6 files changed

+698
-0
lines changed

6 files changed

+698
-0
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.google.cloud.spanner.Options.RpcPriority;
2222
import com.google.cloud.spanner.Options.TransactionOption;
2323
import com.google.cloud.spanner.Options.UpdateOption;
24+
import com.google.cloud.spanner.Statement.StatementFactory;
2425
import com.google.spanner.v1.BatchWriteResponse;
2526
import com.google.spanner.v1.TransactionOptions.IsolationLevel;
2627

@@ -606,4 +607,16 @@ ServerStream<BatchWriteResponse> batchWriteAtLeastOnce(
606607
* idempotent, such as deleting old rows from a very large table.
607608
*/
608609
long executePartitionedUpdate(Statement stmt, UpdateOption... options);
610+
611+
/**
612+
* Returns StatementFactory for the given dialect. With StatementFactory, unnamed parameterized
613+
* queries can be passed along with the values to create a Statement.
614+
*
615+
* <p>Examples using {@link StatementFactory}
616+
*
617+
* <p>databaseClient.newStatementFactory().of("SELECT NAME FROM TABLE WHERE ID = ?", 10)
618+
*/
619+
default StatementFactory newStatementFactory() {
620+
return new StatementFactory(getDialect());
621+
}
609622
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.spanner;
18+
19+
import com.google.cloud.Date;
20+
import com.google.protobuf.ListValue;
21+
import java.text.SimpleDateFormat;
22+
import java.time.LocalDate;
23+
import java.time.LocalDateTime;
24+
import java.time.OffsetDateTime;
25+
import java.time.ZoneId;
26+
import java.time.ZonedDateTime;
27+
import java.time.format.DateTimeFormatter;
28+
import java.time.temporal.TemporalAccessor;
29+
import java.util.ArrayList;
30+
import java.util.Iterator;
31+
import java.util.List;
32+
import java.util.function.Function;
33+
import java.util.stream.Collectors;
34+
import java.util.stream.Stream;
35+
36+
final class SpannerTypeConverter {
37+
38+
private static final String DATE_PATTERN = "yyyy-MM-dd";
39+
private static final SimpleDateFormat SIMPLE_DATE_FORMATTER = new SimpleDateFormat(DATE_PATTERN);
40+
private static final ZoneId UTC_ZONE = ZoneId.of("UTC");
41+
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_PATTERN);
42+
private static final DateTimeFormatter ISO_8601_DATE_FORMATTER =
43+
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX");
44+
45+
static <T> Value createUntypedArrayValue(Stream<T> stream) {
46+
List<com.google.protobuf.Value> values =
47+
stream
48+
.map(
49+
val ->
50+
com.google.protobuf.Value.newBuilder()
51+
.setStringValue(String.valueOf(val))
52+
.build())
53+
.collect(Collectors.toList());
54+
return Value.untyped(
55+
com.google.protobuf.Value.newBuilder()
56+
.setListValue(ListValue.newBuilder().addAllValues(values).build())
57+
.build());
58+
}
59+
60+
static <T extends TemporalAccessor> String convertToISO8601(T dateTime) {
61+
return ISO_8601_DATE_FORMATTER.format(dateTime);
62+
}
63+
64+
static <T> Value createUntypedValue(T value) {
65+
return Value.untyped(
66+
com.google.protobuf.Value.newBuilder().setStringValue(String.valueOf(value)).build());
67+
}
68+
69+
@SuppressWarnings("unchecked")
70+
static <T, U> Iterable<U> convertToTypedIterable(
71+
Function<T, U> func, T val, Iterator<?> iterator) {
72+
List<U> values = new ArrayList<>();
73+
values.add(func.apply(val));
74+
iterator.forEachRemaining(value -> values.add(func.apply((T) value)));
75+
return values;
76+
}
77+
78+
static <T> Iterable<T> convertToTypedIterable(T val, Iterator<?> iterator) {
79+
return convertToTypedIterable(v -> v, val, iterator);
80+
}
81+
82+
static Date convertUtilDateToSpannerDate(java.util.Date date) {
83+
return Date.parseDate(SIMPLE_DATE_FORMATTER.format(date));
84+
}
85+
86+
static Date convertLocalDateToSpannerDate(LocalDate date) {
87+
return Date.parseDate(DATE_FORMATTER.format(date));
88+
}
89+
90+
static <T> Value createUntypedIterableValue(
91+
T value, Iterator<?> iterator, Function<T, String> func) {
92+
return Value.untyped(
93+
com.google.protobuf.Value.newBuilder()
94+
.setListValue(
95+
ListValue.newBuilder()
96+
.addAllValues(
97+
SpannerTypeConverter.convertToTypedIterable(
98+
(val) ->
99+
com.google.protobuf.Value.newBuilder()
100+
.setStringValue(func.apply(val))
101+
.build(),
102+
value,
103+
iterator)))
104+
.build());
105+
}
106+
107+
static ZonedDateTime convertToUTCTimezone(LocalDateTime localDateTime) {
108+
return localDateTime.atZone(UTC_ZONE);
109+
}
110+
111+
static ZonedDateTime convertToUTCTimezone(OffsetDateTime localDateTime) {
112+
return localDateTime.atZoneSameInstant(UTC_ZONE);
113+
}
114+
115+
static ZonedDateTime convertToUTCTimezone(ZonedDateTime localDateTime) {
116+
return localDateTime.withZoneSameInstant(UTC_ZONE);
117+
}
118+
}

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import static com.google.common.base.Preconditions.checkState;
2121

2222
import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode;
23+
import com.google.cloud.spanner.connection.AbstractStatementParser;
24+
import com.google.cloud.spanner.connection.AbstractStatementParser.ParametersInfo;
2325
import com.google.common.base.Preconditions;
2426
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
2527
import java.io.Serializable;
@@ -245,4 +247,56 @@ StringBuilder toString(StringBuilder b) {
245247
}
246248
return b;
247249
}
250+
251+
public static final class StatementFactory {
252+
private final Dialect dialect;
253+
254+
StatementFactory(Dialect dialect) {
255+
this.dialect = dialect;
256+
}
257+
258+
public Statement of(String sql) {
259+
return Statement.of(sql);
260+
}
261+
262+
/**
263+
* @param sql SQL statement with unnamed parameter denoted as ?
264+
* @param values list of values which needs to replace ? in the sql
265+
* @return Statement object
266+
* <p>This function accepts the SQL statement with unnamed parameters(?) and accepts the
267+
* list of objects to replace unnamed parameters. Primitive types are supported
268+
* <p>For Date column, following types are supported
269+
* <ul>
270+
* <li>java.util.Date
271+
* <li>LocalDate
272+
* <li>com.google.cloud.Date
273+
* </ul>
274+
* <p>For Timestamp column, following types are supported. All the dates should be in UTC
275+
* format. Incase if the timezone is not in UTC, spanner client will convert that to UTC
276+
* automatically
277+
* <ul>
278+
* <li>LocalDateTime
279+
* <li>OffsetDateTime
280+
* <li>ZonedDateTime
281+
* </ul>
282+
* <p>
283+
* @see DatabaseClient#newStatementFactory
284+
*/
285+
public Statement of(String sql, Object... values) {
286+
Map<String, Value> parameters = getUnnamedParametersMap(values);
287+
AbstractStatementParser statementParser = AbstractStatementParser.getInstance(this.dialect);
288+
ParametersInfo parametersInfo =
289+
statementParser.convertPositionalParametersToNamedParameters('?', sql);
290+
return new Statement(parametersInfo.sqlWithNamedParameters, parameters, null);
291+
}
292+
293+
private Map<String, Value> getUnnamedParametersMap(Object[] values) {
294+
Map<String, Value> parameters = new HashMap<>();
295+
int index = 1;
296+
for (Object value : values) {
297+
parameters.put("p" + (index++), Value.toValue(value));
298+
}
299+
return parameters;
300+
}
301+
}
248302
}

0 commit comments

Comments
 (0)