Skip to content

Commit 8783918

Browse files
committed
Instant / DateTime64 via parameter
1 parent 2f3b741 commit 8783918

File tree

10 files changed

+734
-37
lines changed

10 files changed

+734
-37
lines changed

client-v2/src/main/java/com/clickhouse/client/api/Client.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import com.clickhouse.client.api.data_formats.RowBinaryFormatReader;
88
import com.clickhouse.client.api.data_formats.RowBinaryWithNamesAndTypesFormatReader;
99
import com.clickhouse.client.api.data_formats.RowBinaryWithNamesFormatReader;
10-
import com.clickhouse.client.api.data_formats.internal.AbstractBinaryFormatReader;
1110
import com.clickhouse.client.api.data_formats.internal.BinaryStreamReader;
1211
import com.clickhouse.client.api.data_formats.internal.MapBackedRecord;
1312
import com.clickhouse.client.api.data_formats.internal.ProcessParser;
@@ -37,7 +36,6 @@
3736
import com.clickhouse.client.api.transport.Endpoint;
3837
import com.clickhouse.client.api.transport.HttpEndpoint;
3938
import com.clickhouse.client.config.ClickHouseClientOption;
40-
import com.clickhouse.config.ClickHouseOption;
4139
import com.clickhouse.data.ClickHouseColumn;
4240
import com.clickhouse.data.ClickHouseDataType;
4341
import com.clickhouse.data.ClickHouseFormat;
@@ -1575,7 +1573,9 @@ public CompletableFuture<QueryResponse> query(String sqlQuery, Map<String, Objec
15751573
Supplier<QueryResponse> responseSupplier;
15761574

15771575
if (queryParams != null) {
1578-
settings.setOption("statement_params", queryParams);
1576+
settings.setOption(
1577+
HttpAPIClientHelper.KEY_STATEMENT_PARAMS,
1578+
formatQueryParameters(queryParams));
15791579
}
15801580
final QuerySettings finalSettings = new QuerySettings(buildRequestSettings(settings.getAllSettings()));
15811581
responseSupplier = () -> {
@@ -2100,4 +2100,12 @@ private Map<String, Object> buildRequestSettings(Map<String, Object> opSettings)
21002100
requestSettings.putAll(opSettings);
21012101
return requestSettings;
21022102
}
2103+
2104+
private Map<String, String> formatQueryParameters(Map<String, Object> queryParams) {
2105+
HashMap<String, String> newMap = new HashMap<>(queryParams.size());
2106+
for (String key : queryParams.keySet()) {
2107+
newMap.put(key, DataTypeUtils.format(queryParams.get(key)));
2108+
}
2109+
return newMap;
2110+
}
21032111
}

client-v2/src/main/java/com/clickhouse/client/api/DataTypeUtils.java

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package com.clickhouse.client.api;
22

3+
import java.time.Instant;
4+
import java.time.ZoneId;
35
import java.time.format.DateTimeFormatter;
6+
import java.util.Arrays;
7+
import java.util.Objects;
8+
9+
import com.clickhouse.data.ClickHouseDataType;
410

511
public class DataTypeUtils {
612

@@ -19,4 +25,116 @@ public class DataTypeUtils {
1925
*/
2026
public static DateTimeFormatter DATETIME_WITH_NANOS_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.nnnnnnnnn");
2127

28+
/**
29+
* Formats a Java object for use in SQL statements or as query parameter.
30+
*
31+
* Note, that this method returns the empty {@link java.lang.String}
32+
* &quot;&quot; for {@code null} objects.
33+
*
34+
* @param object
35+
* the Java object to format
36+
* @return a suitable String representation of {@code object}, or the empty
37+
* String for {@code null} objects
38+
*/
39+
public static String format(Object object) {
40+
return format(object, null);
41+
}
42+
43+
/**
44+
* Formats a Java object for use in SQL statements or as query parameter.
45+
*
46+
* This method uses the {@code dataTypeHint} parameter to find be best
47+
* suitable format for the object.
48+
*
49+
* Note, that this method returns the empty {@link java.lang.String}
50+
* &quot;&quot; for {@code null} objects. This might not be the correct
51+
* default value for the {@code dataTypeHint}
52+
*
53+
* @param object
54+
* the Java object to format
55+
* @param dataTypeHint
56+
* the ClickHouse data type {@code object} should be used for
57+
* @return a suitable String representation of {@code object}, or the empty
58+
* String for {@code null} objects
59+
*/
60+
public static String format(Object object, ClickHouseDataType dataTypeHint) {
61+
return format(object, dataTypeHint, null);
62+
}
63+
64+
/**
65+
* Formats a Java object for use in SQL statements or as query parameter.
66+
*
67+
* This method uses the {@code dataTypeHint} parameter to find be best
68+
* suitable format for the object.
69+
*
70+
* For <em>some</em> formatting operations, providing a {@code timeZone} is
71+
* mandatory: When formatting time-zone-based values (e.g.
72+
* {@link java.time.OffsetDateTime}, {@link java.time.ZonedDateTime}, etc.)
73+
* for use as ClickHouse data types which are not time-zone-based, e.g.
74+
* {@link ClickHouseDataType#Date}, or vice-versa when formatting
75+
* non-time-zone-based Java objects (e.g. {@link java.time.LocalDateTime} or
76+
* any {@link java.util.Calendar} based objects without time-zone) for use
77+
* as time-zone-based ClickHouse data types, e.g.
78+
* {@link ClickHouseDataType#DateTime64}. Although the ClickHouse server
79+
* might understand simple wall-time Strings (&quot;2025-08-20 13:37:42&quot;)
80+
* even for those data types, it is preferable to use timestamp values.
81+
*
82+
* Note, that this method returns the empty {@link java.lang.String}
83+
* &quot;&quot; for {@code null} objects. This might not be the correct
84+
* default value for {@code dataTypeHint}.
85+
*
86+
* @param object
87+
* the Java object to format
88+
* @param dataTypeHint
89+
* the ClickHouse data type {@code object} should be used for
90+
* @param timeZone
91+
* the time zone to be used when formatting time-zone-based Java
92+
* objects for use in non-time-zone-based ClickHouse data types
93+
* and vice versa
94+
* @return a suitable String representation of {@code object}, or the empty
95+
* String for {@code null} objects
96+
*/
97+
public static String format(Object object, ClickHouseDataType dataTypeHint,
98+
ZoneId timeZone)
99+
{
100+
if (object == null) {
101+
return "";
102+
}
103+
if (object instanceof Instant) {
104+
return formatInstant((Instant) object, dataTypeHint, timeZone);
105+
}
106+
return String.valueOf(object);
107+
}
108+
109+
private static String formatInstant(Instant instant, ClickHouseDataType dataTypeHint,
110+
ZoneId timeZone)
111+
{
112+
if (dataTypeHint == null) {
113+
return formatInstantDefault(instant);
114+
}
115+
switch (dataTypeHint) {
116+
case Date:
117+
case Date32:
118+
Objects.requireNonNull(
119+
timeZone,
120+
"TimeZone required for formatting Instant for '" + dataTypeHint + "' use");
121+
return DATE_FORMATTER.format(
122+
instant.atZone(timeZone).toLocalDate());
123+
case DateTime:
124+
case DateTime32:
125+
return String.valueOf(instant.getEpochSecond());
126+
default:
127+
return formatInstantDefault(instant);
128+
}
129+
}
130+
131+
private static String formatInstantDefault(Instant instant) {
132+
String nanos = String.valueOf(instant.getNano());
133+
char[] n = new char[9];
134+
Arrays.fill(n, '0');
135+
int nanosLength = Math.min(9, nanos.length());
136+
nanos.getChars(0, nanosLength, n, 9 - nanosLength);
137+
return String.valueOf(instant.getEpochSecond()) + "." + new String(n);
138+
}
139+
22140
}

client-v2/src/main/java/com/clickhouse/client/api/data_formats/RowBinaryFormatSerializer.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import com.clickhouse.client.api.data_formats.internal.SerializerUtils;
44
import com.clickhouse.data.ClickHouseColumn;
55
import com.clickhouse.data.ClickHouseDataType;
6-
import com.clickhouse.data.ClickHouseFormat;
76
import com.clickhouse.data.format.BinaryStreamUtils;
87

98
import java.io.IOException;
@@ -132,7 +131,7 @@ public void writeFixedString(String value, int len) throws IOException {
132131
}
133132

134133
public void writeDate(ZonedDateTime value) throws IOException {
135-
SerializerUtils.writeDate(out, value, ZoneId.of("UTC"));
134+
SerializerUtils.writeDate(out, value, value.getZone());
136135
}
137136

138137
public void writeDate32(ZonedDateTime value, ZoneId targetTz) throws IOException {

client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/BinaryStreamReader.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public <T> T readValue(ClickHouseColumn column, Class<?> typeHint) throws IOExce
104104
if (column.isNullable()) {
105105
int isNull = readByteOrEOF(input);
106106
if (isNull == 1) { // is Null?
107-
return (T) null;
107+
return null;
108108
}
109109
}
110110

@@ -588,7 +588,7 @@ public static byte[] readNBytesLE(InputStream input, byte[] buffer, int offset,
588588

589589
return bytes;
590590
}
591-
591+
592592
/**
593593
* Reads a array into an ArrayValue object.
594594
* @param column - column information
@@ -964,7 +964,7 @@ private ZonedDateTime readDateTime32(TimeZone tz) throws IOException {
964964
*/
965965
public static ZonedDateTime readDateTime32(InputStream input, byte[] buff, TimeZone tz) throws IOException {
966966
long time = readUnsignedIntLE(input, buff);
967-
return LocalDateTime.ofInstant(Instant.ofEpochSecond(Math.max(time, 0L)), tz.toZoneId()).atZone(tz.toZoneId());
967+
return Instant.ofEpochSecond(Math.max(time, 0L)).atZone(tz.toZoneId());
968968
}
969969

970970
/**

client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@
1717
import org.objectweb.asm.MethodVisitor;
1818
import org.objectweb.asm.Opcodes;
1919
import org.objectweb.asm.Type;
20-
import org.slf4j.Logger;
21-
import org.slf4j.LoggerFactory;
22-
2320
import java.io.IOException;
2421
import java.io.OutputStream;
2522
import java.lang.reflect.Array;
@@ -29,7 +26,14 @@
2926
import java.net.Inet4Address;
3027
import java.net.Inet6Address;
3128
import java.sql.Timestamp;
32-
import java.time.*;
29+
import java.time.Duration;
30+
import java.time.Instant;
31+
import java.time.LocalDate;
32+
import java.time.LocalDateTime;
33+
import java.time.OffsetDateTime;
34+
import java.time.Period;
35+
import java.time.ZoneId;
36+
import java.time.ZonedDateTime;
3337
import java.util.Arrays;
3438
import java.util.Collections;
3539
import java.util.HashMap;
@@ -54,8 +58,6 @@
5458

5559
public class SerializerUtils {
5660

57-
private static final Logger LOG = LoggerFactory.getLogger(SerializerUtils.class);
58-
5961
public static void serializeData(OutputStream stream, Object value, ClickHouseColumn column) throws IOException {
6062
//Serialize the value to the stream based on the data type
6163
switch (column.getDataType()) {
@@ -1070,6 +1072,9 @@ public static void writeDate(OutputStream output, Object value, ZoneId targetTz)
10701072
} else if (value instanceof ZonedDateTime) {
10711073
ZonedDateTime dt = (ZonedDateTime) value;
10721074
epochDays = (int)dt.withZoneSameInstant(targetTz).toLocalDate().toEpochDay();
1075+
} else if (value instanceof OffsetDateTime) {
1076+
OffsetDateTime dt = (OffsetDateTime) value;
1077+
epochDays = (int) dt.atZoneSameInstant(targetTz).toLocalDate().toEpochDay();
10731078
} else {
10741079
throw new IllegalArgumentException("Cannot convert " + value + " to Long");
10751080
}

client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@
9898
import java.util.regex.Pattern;
9999

100100
public class HttpAPIClientHelper {
101-
private static final Logger LOG = LoggerFactory.getLogger(Client.class);
101+
102+
public static final String KEY_STATEMENT_PARAMS = "statement_params";
103+
104+
private static final Logger LOG = LoggerFactory.getLogger(HttpAPIClientHelper.class);
102105

103106
private static final int ERROR_BODY_BUFFER_SIZE = 1024; // Error messages are usually small
104107

@@ -567,11 +570,9 @@ private void addQueryParams(URIBuilder req, Map<String, Object> requestConfig) {
567570
if (requestConfig.containsKey(ClientConfigProperties.QUERY_ID.getKey())) {
568571
req.addParameter(ClickHouseHttpProto.QPARAM_QUERY_ID, requestConfig.get(ClientConfigProperties.QUERY_ID.getKey()).toString());
569572
}
570-
if (requestConfig.containsKey("statement_params")) {
571-
Map<String, Object> params = (Map<String, Object>) requestConfig.get("statement_params");
572-
for (Map.Entry<String, Object> entry : params.entrySet()) {
573-
req.addParameter("param_" + entry.getKey(), String.valueOf(entry.getValue()));
574-
}
573+
if (requestConfig.containsKey(KEY_STATEMENT_PARAMS)) {
574+
Map<?, ?> params = (Map<?, ?>) requestConfig.get(KEY_STATEMENT_PARAMS);
575+
params.forEach((k, v) -> req.addParameter("param_" + k, (String.valueOf(v))));
575576
}
576577

577578
boolean clientCompression = ClientConfigProperties.COMPRESS_CLIENT_REQUEST.getOrDefault(requestConfig);

0 commit comments

Comments
 (0)