Skip to content

Commit bddb45f

Browse files
authored
Merge pull request #2580 from ClickHouse/fix_ip_address_to_string
[JDBC-V2, Client-V2] Fixes data type conversion to string
2 parents fb75c2f + 9cb45a5 commit bddb45f

File tree

11 files changed

+715
-82
lines changed

11 files changed

+715
-82
lines changed

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package com.clickhouse.client.api;
22

3-
import java.time.Instant;
4-
import java.time.ZoneId;
53
import com.clickhouse.client.api.data_formats.internal.BinaryStreamReader;
4+
import com.clickhouse.data.ClickHouseDataType;
65

76
import java.time.Instant;
87
import java.time.ZoneId;
@@ -11,8 +10,6 @@
1110
import java.time.temporal.ChronoField;
1211
import java.util.Objects;
1312

14-
import com.clickhouse.data.ClickHouseDataType;
15-
1613
import static com.clickhouse.client.api.data_formats.internal.BinaryStreamReader.BASES;
1714

1815
public class DataTypeUtils {
@@ -39,6 +36,10 @@ public class DataTypeUtils {
3936
.appendFraction(ChronoField.NANO_OF_SECOND, 9, 9, true)
4037
.toFormatter();
4138

39+
public static final DateTimeFormatter TIME_WITH_NANOS_FORMATTER = INSTANT_FORMATTER;
40+
41+
public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
42+
4243
/**
4344
* Formats an {@link Instant} object for use in SQL statements or as query
4445
* parameter.

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

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.clickhouse.client.api.ClientException;
55
import com.clickhouse.client.api.DataTypeUtils;
66
import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader;
7+
import com.clickhouse.client.api.internal.DataTypeConverter;
78
import com.clickhouse.client.api.internal.MapUtils;
89
import com.clickhouse.client.api.internal.ServerSettings;
910
import com.clickhouse.client.api.metadata.NoSuchColumnException;
@@ -61,6 +62,8 @@ public abstract class AbstractBinaryFormatReader implements ClickHouseBinaryForm
6162

6263
protected BinaryStreamReader binaryStreamReader;
6364

65+
protected DataTypeConverter dataTypeConverter;
66+
6467
private TableSchema schema;
6568
private ClickHouseColumn[] columns;
6669
private Map[] convertions;
@@ -84,6 +87,7 @@ protected AbstractBinaryFormatReader(InputStream inputStream, QuerySettings quer
8487
if (schema != null) {
8588
setSchema(schema);
8689
}
90+
this.dataTypeConverter = DataTypeConverter.INSTANCE; // singleton while no need to customize conversion
8791
}
8892

8993
protected Object[] currentRecord;
@@ -326,52 +330,7 @@ public TableSchema getSchema() {
326330

327331
@Override
328332
public String getString(String colName) {
329-
return readAsString(readValue(colName), schema.getColumnByName(colName));
330-
}
331-
332-
/**
333-
* Converts value in to a string representation. Does some formatting for selected data types
334-
* @return string representation of a value for specified column
335-
*/
336-
public static String readAsString(Object value, ClickHouseColumn column) {
337-
if (value == null) {
338-
return null;
339-
} else if (value instanceof String) {
340-
return (String) value;
341-
} else if (value instanceof ZonedDateTime) {
342-
ClickHouseDataType dataType = column.getDataType();
343-
ZonedDateTime zdt = (ZonedDateTime) value;
344-
switch (dataType) { // should not be null
345-
case Date:
346-
case Date32:
347-
return zdt.format(DataTypeUtils.DATE_FORMATTER);
348-
case DateTime:
349-
case DateTime32:
350-
return zdt.format(DataTypeUtils.DATETIME_FORMATTER);
351-
case DateTime64:
352-
return zdt.format(DataTypeUtils.DATETIME_WITH_NANOS_FORMATTER);
353-
default:
354-
return value.toString();
355-
}
356-
} else if (value instanceof BinaryStreamReader.EnumValue) {
357-
return ((BinaryStreamReader.EnumValue)value).name;
358-
} else if (value instanceof Number ) {
359-
ClickHouseDataType dataType = column.getDataType();
360-
int num = ((Number) value).intValue();
361-
if (column.getDataType() == ClickHouseDataType.Variant) {
362-
for (ClickHouseColumn c : column.getNestedColumns()) {
363-
// TODO: will work only if single enum listed as variant
364-
if (c.getDataType() == ClickHouseDataType.Enum8 || c.getDataType() == ClickHouseDataType.Enum16) {
365-
return c.getEnumConstants().name(num);
366-
}
367-
}
368-
} else if (dataType == ClickHouseDataType.Enum8 || dataType == ClickHouseDataType.Enum16) {
369-
return column.getEnumConstants().name(num);
370-
}
371-
} else if (value instanceof BinaryStreamReader.ArrayValue) {
372-
return ((BinaryStreamReader.ArrayValue)value).asList().toString();
373-
}
374-
return value.toString();
333+
return dataTypeConverter.convertToString(readValue(colName), schema.getColumnByName(colName));
375334
}
376335

377336
@Override

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

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

33
import com.clickhouse.client.api.ClientException;
44
import com.clickhouse.client.api.DataTypeUtils;
5+
import com.clickhouse.client.api.internal.DataTypeConverter;
56
import com.clickhouse.client.api.metadata.TableSchema;
67
import com.clickhouse.client.api.query.GenericRecord;
78
import com.clickhouse.client.api.query.NullValueException;
@@ -38,10 +39,13 @@ public class MapBackedRecord implements GenericRecord {
3839

3940
private Map[] columnConverters;
4041

42+
private DataTypeConverter dataTypeConverter;
43+
4144
public MapBackedRecord(Map<String, Object> record, Map[] columnConverters, TableSchema schema) {
4245
this.record = new HashMap<>(record);
4346
this.schema = schema;
4447
this.columnConverters = columnConverters;
48+
this.dataTypeConverter = DataTypeConverter.INSTANCE;
4549
}
4650

4751
public <T> T readValue(int colIndex) {
@@ -58,7 +62,7 @@ public <T> T readValue(String colName) {
5862

5963
@Override
6064
public String getString(String colName) {
61-
return AbstractBinaryFormatReader.readAsString(readValue(colName), schema.getColumnByName(colName));
65+
return dataTypeConverter.convertToString(readValue(colName), schema.getColumnByName(colName));
6266
}
6367

6468
@Override
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package com.clickhouse.client.api.internal;
2+
3+
import com.clickhouse.client.api.ClickHouseException;
4+
5+
import java.io.IOException;
6+
import java.lang.reflect.Array;
7+
import java.util.ArrayDeque;
8+
import java.util.Deque;
9+
import java.util.List;
10+
11+
public abstract class BaseCollectionConverter<ACC, LIST> {
12+
public static final String ARRAY_START = "[";
13+
public static final String ARRAY_END = "]";
14+
15+
private final String itemDelimiter;
16+
17+
protected BaseCollectionConverter(String itemDelimiter) {
18+
this.itemDelimiter = itemDelimiter;
19+
}
20+
21+
protected abstract void setAccumulator(ACC acc);
22+
23+
protected abstract void append(String str);
24+
25+
protected abstract String buildString();
26+
27+
protected abstract void onStart(ListConversionState<LIST> state);
28+
29+
protected abstract void onEnd(ListConversionState<LIST> state);
30+
31+
protected abstract void onItem(Object item, ListConversionState<LIST> state);
32+
33+
protected abstract String onEmptyCollection();
34+
35+
protected abstract boolean isEmpty(LIST list);
36+
37+
protected abstract boolean isSubList(Object list);
38+
39+
protected abstract int listSize(LIST list);
40+
41+
protected abstract Object getNext(ListConversionState<LIST> state);
42+
43+
public final String convert(LIST value, ACC acc) {
44+
if (isEmpty(value)) {
45+
return onEmptyCollection();
46+
}
47+
setAccumulator(acc);
48+
49+
Deque<ListConversionState<LIST>> stack = new ArrayDeque<>();
50+
ListConversionState<LIST> state = new ListConversionState<>(value, listSize(value));
51+
while (state != null) {
52+
if (state.isFirst()) {
53+
onStart(state);
54+
}
55+
if (state.hasNext()) {
56+
Object item = getNext(state);
57+
state.incPos();
58+
if (isSubList(item)) {
59+
stack.push(state);
60+
LIST list = (LIST) item;
61+
state = new ListConversionState<>(list, listSize(list));
62+
} else {
63+
onItem(item, state);
64+
if (state.hasNext()) {
65+
append(itemDelimiter);
66+
}
67+
}
68+
} else {
69+
onEnd(state);
70+
state = stack.isEmpty() ? null : stack.pop();
71+
if (state != null && state.hasNext()) {
72+
append(itemDelimiter);
73+
}
74+
}
75+
}
76+
77+
return buildString();
78+
}
79+
80+
public static final class ListConversionState<LIST> {
81+
82+
final LIST list;
83+
int position;
84+
int size;
85+
86+
private ListConversionState(LIST list, int size) {
87+
this.list = list;
88+
this.position = 0;
89+
this.size = size;
90+
}
91+
92+
public LIST getList() {
93+
return list;
94+
}
95+
96+
public int getPosition() {
97+
return position;
98+
}
99+
100+
public void incPos() {
101+
this.position++;
102+
}
103+
104+
public boolean hasNext() {
105+
return position < size;
106+
}
107+
108+
public boolean isFirst() {
109+
return position == 0;
110+
}
111+
}
112+
113+
public abstract static class BaseArrayWriter extends BaseCollectionWriter<Object> {
114+
115+
protected BaseArrayWriter() {
116+
super(", ");
117+
}
118+
119+
@Override
120+
protected boolean isEmpty(Object objects) {
121+
return listSize(objects) == 0;
122+
}
123+
124+
@Override
125+
protected boolean isSubList(Object list) {
126+
return list != null && list.getClass().isArray();
127+
}
128+
129+
@Override
130+
protected int listSize(Object objects) {
131+
return Array.getLength(objects);
132+
}
133+
134+
@Override
135+
protected Object getNext(ListConversionState<Object> state) {
136+
return Array.get(state.getList(), state.getPosition());
137+
}
138+
}
139+
140+
public abstract static class BaseListWriter
141+
extends BaseCollectionWriter<List<?>> {
142+
protected BaseListWriter() {
143+
super(", ");
144+
}
145+
146+
@Override
147+
protected boolean isEmpty(List<?> objects) {
148+
return objects.isEmpty();
149+
}
150+
151+
@Override
152+
protected boolean isSubList(Object list) {
153+
return list instanceof List<?>;
154+
}
155+
156+
@Override
157+
protected int listSize(List<?> objects) {
158+
return objects.size();
159+
}
160+
161+
@Override
162+
protected Object getNext(ListConversionState<List<?>> state) {
163+
return state.getList().get(state.getPosition());
164+
}
165+
}
166+
167+
public abstract static class BaseCollectionWriter<T> extends
168+
BaseCollectionConverter<Appendable, T> {
169+
170+
protected Appendable appendable;
171+
172+
protected BaseCollectionWriter(String itemDelimiter) {
173+
super(itemDelimiter);
174+
}
175+
176+
@Override
177+
protected void setAccumulator(Appendable appendable) {
178+
this.appendable = appendable;
179+
}
180+
181+
@Override
182+
protected void append(String str) {
183+
try {
184+
appendable.append(str);
185+
} catch (IOException e) {
186+
throw new ClickHouseException(e.getMessage(), e);
187+
}
188+
}
189+
190+
@Override
191+
protected String buildString() {
192+
return appendable.toString();
193+
}
194+
195+
@Override
196+
protected void onStart(ListConversionState<T> state) {
197+
try {
198+
appendable.append(ARRAY_START);
199+
} catch (IOException e) {
200+
throw new ClickHouseException(e.getMessage(), e);
201+
}
202+
}
203+
204+
@Override
205+
protected void onEnd(ListConversionState<T> state) {
206+
try {
207+
appendable.append(ARRAY_END);
208+
} catch (IOException e) {
209+
throw new ClickHouseException(e.getMessage(), e);
210+
}
211+
}
212+
213+
@Override
214+
protected String onEmptyCollection() {
215+
return ARRAY_START + ARRAY_END;
216+
}
217+
}
218+
}

0 commit comments

Comments
 (0)