Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package com.clickhouse.client.api;

import java.time.Instant;
import java.time.ZoneId;
import com.clickhouse.client.api.data_formats.internal.BinaryStreamReader;
import com.clickhouse.data.ClickHouseDataType;

import java.time.Instant;
import java.time.ZoneId;
Expand All @@ -11,8 +10,6 @@
import java.time.temporal.ChronoField;
import java.util.Objects;

import com.clickhouse.data.ClickHouseDataType;

import static com.clickhouse.client.api.data_formats.internal.BinaryStreamReader.BASES;

public class DataTypeUtils {
Expand All @@ -39,6 +36,10 @@ public class DataTypeUtils {
.appendFraction(ChronoField.NANO_OF_SECOND, 9, 9, true)
.toFormatter();

public static final DateTimeFormatter TIME_WITH_NANOS_FORMATTER = INSTANT_FORMATTER;

public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");

/**
* Formats an {@link Instant} object for use in SQL statements or as query
* parameter.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.clickhouse.client.api.ClientException;
import com.clickhouse.client.api.DataTypeUtils;
import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader;
import com.clickhouse.client.api.internal.DataTypeConverter;
import com.clickhouse.client.api.internal.MapUtils;
import com.clickhouse.client.api.internal.ServerSettings;
import com.clickhouse.client.api.metadata.NoSuchColumnException;
Expand Down Expand Up @@ -61,6 +62,8 @@ public abstract class AbstractBinaryFormatReader implements ClickHouseBinaryForm

protected BinaryStreamReader binaryStreamReader;

protected DataTypeConverter dataTypeConverter;

private TableSchema schema;
private ClickHouseColumn[] columns;
private Map[] convertions;
Expand All @@ -84,6 +87,7 @@ protected AbstractBinaryFormatReader(InputStream inputStream, QuerySettings quer
if (schema != null) {
setSchema(schema);
}
this.dataTypeConverter = DataTypeConverter.INSTANCE; // singleton while no need to customize conversion
}

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

@Override
public String getString(String colName) {
return readAsString(readValue(colName), schema.getColumnByName(colName));
}

/**
* Converts value in to a string representation. Does some formatting for selected data types
* @return string representation of a value for specified column
*/
public static String readAsString(Object value, ClickHouseColumn column) {
if (value == null) {
return null;
} else if (value instanceof String) {
return (String) value;
} else if (value instanceof ZonedDateTime) {
ClickHouseDataType dataType = column.getDataType();
ZonedDateTime zdt = (ZonedDateTime) value;
switch (dataType) { // should not be null
case Date:
case Date32:
return zdt.format(DataTypeUtils.DATE_FORMATTER);
case DateTime:
case DateTime32:
return zdt.format(DataTypeUtils.DATETIME_FORMATTER);
case DateTime64:
return zdt.format(DataTypeUtils.DATETIME_WITH_NANOS_FORMATTER);
default:
return value.toString();
}
} else if (value instanceof BinaryStreamReader.EnumValue) {
return ((BinaryStreamReader.EnumValue)value).name;
} else if (value instanceof Number ) {
ClickHouseDataType dataType = column.getDataType();
int num = ((Number) value).intValue();
if (column.getDataType() == ClickHouseDataType.Variant) {
for (ClickHouseColumn c : column.getNestedColumns()) {
// TODO: will work only if single enum listed as variant
if (c.getDataType() == ClickHouseDataType.Enum8 || c.getDataType() == ClickHouseDataType.Enum16) {
return c.getEnumConstants().name(num);
}
}
} else if (dataType == ClickHouseDataType.Enum8 || dataType == ClickHouseDataType.Enum16) {
return column.getEnumConstants().name(num);
}
} else if (value instanceof BinaryStreamReader.ArrayValue) {
return ((BinaryStreamReader.ArrayValue)value).asList().toString();
}
return value.toString();
return dataTypeConverter.convertToString(readValue(colName), schema.getColumnByName(colName));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.clickhouse.client.api.ClientException;
import com.clickhouse.client.api.DataTypeUtils;
import com.clickhouse.client.api.internal.DataTypeConverter;
import com.clickhouse.client.api.metadata.TableSchema;
import com.clickhouse.client.api.query.GenericRecord;
import com.clickhouse.client.api.query.NullValueException;
Expand Down Expand Up @@ -38,10 +39,13 @@ public class MapBackedRecord implements GenericRecord {

private Map[] columnConverters;

private DataTypeConverter dataTypeConverter;

public MapBackedRecord(Map<String, Object> record, Map[] columnConverters, TableSchema schema) {
this.record = new HashMap<>(record);
this.schema = schema;
this.columnConverters = columnConverters;
this.dataTypeConverter = DataTypeConverter.INSTANCE;
}

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

@Override
public String getString(String colName) {
return AbstractBinaryFormatReader.readAsString(readValue(colName), schema.getColumnByName(colName));
return dataTypeConverter.convertToString(readValue(colName), schema.getColumnByName(colName));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package com.clickhouse.client.api.internal;

import com.clickhouse.client.api.ClickHouseException;

import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;

public abstract class BaseCollectionConverter<ACC, LIST> {
public static final String ARRAY_START = "[";
public static final String ARRAY_END = "]";

private final String itemDelimiter;

protected BaseCollectionConverter(String itemDelimiter) {
this.itemDelimiter = itemDelimiter;
}

protected abstract void setAccumulator(ACC acc);

protected abstract void append(String str);

protected abstract String buildString();

protected abstract void onStart(ListConversionState<LIST> state);

protected abstract void onEnd(ListConversionState<LIST> state);

protected abstract void onItem(Object item, ListConversionState<LIST> state);

protected abstract String onEmptyCollection();

protected abstract boolean isEmpty(LIST list);

protected abstract boolean isSubList(Object list);

protected abstract int listSize(LIST list);

protected abstract Object getNext(ListConversionState<LIST> state);

public final String convert(LIST value, ACC acc) {
if (isEmpty(value)) {
return onEmptyCollection();
}
setAccumulator(acc);

Deque<ListConversionState<LIST>> stack = new ArrayDeque<>();
ListConversionState<LIST> state = new ListConversionState<>(value, listSize(value));
while (state != null) {
if (state.isFirst()) {
onStart(state);
}
if (state.hasNext()) {
Object item = getNext(state);
state.incPos();
if (isSubList(item)) {
stack.push(state);
LIST list = (LIST) item;
state = new ListConversionState<>(list, listSize(list));
} else {
onItem(item, state);
if (state.hasNext()) {
append(itemDelimiter);
}
}
} else {
onEnd(state);
state = stack.isEmpty() ? null : stack.pop();
if (state != null && state.hasNext()) {
append(itemDelimiter);
}
}
}

return buildString();
}

public static final class ListConversionState<LIST> {

final LIST list;
int position;
int size;

private ListConversionState(LIST list, int size) {
this.list = list;
this.position = 0;
this.size = size;
}

public LIST getList() {
return list;
}

public int getPosition() {
return position;
}

public void incPos() {
this.position++;
}

public boolean hasNext() {
return position < size;
}

public boolean isFirst() {
return position == 0;
}
}

public abstract static class BaseArrayWriter extends BaseCollectionWriter<Object> {

protected BaseArrayWriter() {
super(", ");
}

@Override
protected boolean isEmpty(Object objects) {
return listSize(objects) == 0;
}

@Override
protected boolean isSubList(Object list) {
return list != null && list.getClass().isArray();
}

@Override
protected int listSize(Object objects) {
return Array.getLength(objects);
}

@Override
protected Object getNext(ListConversionState<Object> state) {
return Array.get(state.getList(), state.getPosition());
}
}

public abstract static class BaseListWriter
extends BaseCollectionWriter<List<?>> {
protected BaseListWriter() {
super(", ");
}

@Override
protected boolean isEmpty(List<?> objects) {
return objects.isEmpty();
}

@Override
protected boolean isSubList(Object list) {
return list instanceof List<?>;
}

@Override
protected int listSize(List<?> objects) {
return objects.size();
}

@Override
protected Object getNext(ListConversionState<List<?>> state) {
return state.getList().get(state.getPosition());
}
}

public abstract static class BaseCollectionWriter<T> extends
BaseCollectionConverter<Appendable, T> {

protected Appendable appendable;

protected BaseCollectionWriter(String itemDelimiter) {
super(itemDelimiter);
}

@Override
protected void setAccumulator(Appendable appendable) {
this.appendable = appendable;
}

@Override
protected void append(String str) {
try {
appendable.append(str);
} catch (IOException e) {
throw new ClickHouseException(e.getMessage(), e);
}
}

@Override
protected String buildString() {
return appendable.toString();
}

@Override
protected void onStart(ListConversionState<T> state) {
try {
appendable.append(ARRAY_START);
} catch (IOException e) {
throw new ClickHouseException(e.getMessage(), e);
}
}

@Override
protected void onEnd(ListConversionState<T> state) {
try {
appendable.append(ARRAY_END);
} catch (IOException e) {
throw new ClickHouseException(e.getMessage(), e);
}
}

@Override
protected String onEmptyCollection() {
return ARRAY_START + ARRAY_END;
}
}
}
Loading
Loading