Skip to content

Commit 4e6a6bd

Browse files
committed
implemnted base collection writer to iterate over nested arrays and collections
1 parent 9f7be1d commit 4e6a6bd

File tree

4 files changed

+361
-30
lines changed

4 files changed

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

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

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

3+
import com.clickhouse.client.api.ClickHouseException;
34
import com.clickhouse.client.api.DataTypeUtils;
4-
import com.clickhouse.client.api.data_formats.internal.AbstractBinaryFormatReader;
55
import com.clickhouse.client.api.data_formats.internal.BinaryStreamReader;
66
import com.clickhouse.data.ClickHouseColumn;
77
import com.clickhouse.data.ClickHouseDataType;
@@ -13,13 +13,26 @@
1313
import java.time.ZonedDateTime;
1414
import java.time.format.DateTimeFormatter;
1515
import java.time.temporal.TemporalAccessor;
16-
import java.util.Arrays;
1716
import java.util.Date;
17+
import java.util.List;
1818

19+
/**
20+
* Class designed to convert different data types to Java objects.
21+
* First use-case is to convert ClickHouse data types to String representation.
22+
* Note: class is not thread-safe to avoid extra object creation.
23+
*/
1924
public class DataTypeConverter {
2025

26+
private static final char QUOTE = '\'';
27+
28+
private static final String NULL = "NULL";
29+
2130
public static final DataTypeConverter INSTANCE = new DataTypeConverter();
2231

32+
private final ListAsStringWriter listAsStringWriter = new ListAsStringWriter();
33+
34+
private final ArrayAsStringWriter arrayAsStringWriter = new ArrayAsStringWriter();
35+
2336
public String convertToString(Object value, ClickHouseColumn column) {
2437
if (value == null) {
2538
return null;
@@ -56,7 +69,21 @@ public String convertToString(Object value, ClickHouseColumn column) {
5669
}
5770

5871
public String stringToString(Object bytesOrString, ClickHouseColumn column) {
59-
return bytesOrString instanceof byte[] ? new String((byte[]) bytesOrString) : (String) bytesOrString;
72+
StringBuilder sb = new StringBuilder();
73+
if (column.isArray()) {
74+
sb.append(QUOTE);
75+
}
76+
if (bytesOrString instanceof CharSequence) {
77+
sb.append(((CharSequence) bytesOrString));
78+
} else if (bytesOrString instanceof byte[]) {
79+
sb.append(bytesOrString);
80+
} else {
81+
sb.append(bytesOrString);
82+
}
83+
if (column.isArray()) {
84+
sb.append(QUOTE);
85+
}
86+
return sb.toString();
6087
}
6188

6289
public String dateToString(Object value, ClickHouseColumn column) {
@@ -149,33 +176,94 @@ public String ipvToString(Object value, ClickHouseColumn column) {
149176

150177
public String arrayToString(Object value, ClickHouseColumn column) {
151178
if (value instanceof BinaryStreamReader.ArrayValue) {
152-
return ((BinaryStreamReader.ArrayValue) value).asList().toString();
153-
} else if (value instanceof byte[]) {
154-
return Arrays.toString((byte[]) value);
155-
} else if (value instanceof short[]) {
156-
return Arrays.toString((short[]) value);
157-
} else if (value instanceof int[]) {
158-
return Arrays.toString((int[]) value);
159-
} else if (value instanceof long[]) {
160-
return Arrays.toString((long[]) value);
161-
} else if (value instanceof float[]) {
162-
return Arrays.toString((float[]) value);
163-
} else if (value instanceof double[]) {
164-
return Arrays.toString((double[]) value);
165-
} else if (value instanceof boolean[]) {
166-
return Arrays.toString((boolean[]) value);
167-
} else if (value instanceof char[]) {
168-
return Arrays.toString((char[]) value);
169-
} else if (value instanceof Object[]) {
170-
return Arrays.deepToString((Object[]) value);
179+
return listAsStringWriter.convertAndReset(((BinaryStreamReader.ArrayValue) value).asList(), new StringBuilder(), column);
180+
} else if (value.getClass().isArray()) {
181+
return arrayAsStringWriter.convertAndReset(value, new StringBuilder(), column);
182+
} else if (value instanceof List<?>) {
183+
return listAsStringWriter.convertAndReset((List<?>) value, new StringBuilder(), column);
171184
}
172185
return value.toString();
173186
}
174187

188+
/**
189+
*
190+
* @param value not null object value to convert
191+
* @param column column describing the DB value
192+
* @return String representing the value
193+
*/
175194
public String variantOrDynamicToString(Object value, ClickHouseColumn column) {
176195
if (value instanceof BinaryStreamReader.ArrayValue) {
177196
return arrayToString(value, column);
178197
}
179198
return value.toString();
180199
}
200+
201+
private final class ArrayAsStringWriter extends BaseCollectionConverter.BaseArrayWriter {
202+
private ClickHouseColumn column;
203+
204+
ArrayAsStringWriter() {
205+
super();
206+
}
207+
208+
public void setColumn(ClickHouseColumn column) {
209+
this.column = column;
210+
}
211+
212+
213+
@Override
214+
protected void onItem(Object item, ListConversionState<Object> state) {
215+
if (item == null) {
216+
append(NULL);
217+
return;
218+
}
219+
String str = DataTypeConverter.this.convertToString(item, column.getArrayBaseColumn() == null ? column : column.getArrayBaseColumn());
220+
try {
221+
if (column.getArrayBaseColumn().getDataType() == ClickHouseDataType.String) {
222+
appendable.append(QUOTE).append(str).append(QUOTE);
223+
} else {
224+
appendable.append(str);
225+
}
226+
} catch (Exception ex) {
227+
throw new ClickHouseException(ex.getMessage(), ex);
228+
}
229+
}
230+
231+
public String convertAndReset(Object list, Appendable acc, ClickHouseColumn column) {
232+
try {
233+
setColumn(column);
234+
return super.convert(list, acc);
235+
} finally {
236+
this.column = null;
237+
setAccumulator(null);
238+
}
239+
}
240+
}
241+
242+
private final class ListAsStringWriter extends BaseCollectionConverter.BaseListWriter {
243+
244+
private ClickHouseColumn column;
245+
246+
public void setColumn(ClickHouseColumn column) {
247+
this.column = column;
248+
}
249+
250+
@Override
251+
protected void onItem(Object item, ListConversionState<List<?>> state) {
252+
if (item == null) {
253+
append(NULL);
254+
return;
255+
}
256+
append(DataTypeConverter.this.convertToString(item, column.getArrayBaseColumn() == null ? column : column.getArrayBaseColumn()));
257+
}
258+
259+
public String convertAndReset(List<?> list, Appendable acc, ClickHouseColumn column) {
260+
try {
261+
setColumn(column);
262+
return super.convert(list, acc);
263+
} finally {
264+
this.column = null;
265+
setAccumulator(null);
266+
}
267+
}
268+
}
181269
}

0 commit comments

Comments
 (0)