) parsers.get(clazz);
+ if (p == null) {
+ throw new ClickHouseUnknownException(
+ "No value parser for class '" + clazz.getName() + "'", null);
+ }
+ return p;
+ }
+
+ public static final int parseInt(ByteFragment value, ClickHouseColumnInfo columnInfo)
+ throws SQLException
+ {
+ Integer i = getParser(Integer.class).parse(value, columnInfo, null);
+ return i != null ? i.intValue() : 0;
+ }
+
+ public static final long parseLong(ByteFragment value, ClickHouseColumnInfo columnInfo)
+ throws SQLException
+ {
+ Long l = getParser(Long.class).parse(value, columnInfo, null);
+ return l != null ? l.longValue() : 0L;
+ }
+
+ public static final boolean parseBoolean(ByteFragment value, ClickHouseColumnInfo columnInfo)
+ throws SQLException
+ {
+ Boolean b = getParser(Boolean.class).parse(value, columnInfo, null);
+ return b != null ? b.booleanValue() : false;
+ }
+
+ public static final short parseShort(ByteFragment value, ClickHouseColumnInfo columnInfo)
+ throws SQLException
+ {
+ Short s = getParser(Short.class).parse(value, columnInfo, null);
+ return s != null ? s.shortValue() : 0;
+ }
+
+ public static final double parseDouble(ByteFragment value, ClickHouseColumnInfo columnInfo)
+ throws SQLException
+ {
+ Double d = getParser(Double.class).parse(value, columnInfo, null);
+ return d != null ? d.doubleValue() : 0.0;
+ }
+
+ public static final float parseFloat(ByteFragment value, ClickHouseColumnInfo columnInfo)
+ throws SQLException
+ {
+ Double d = getParser(Double.class).parse(value, columnInfo, null);
+ return d != null ? d.floatValue() : 0.0f;
+ }
+
+ /**
+ * Parses the supplied byte fragment {@code value} using meta data contained
+ * in {@code columnInfo}. Date / time parsing uses {@code resultTimeZone},
+ * unless only local values are involved.
+ *
+ * @param value
+ * value as returned from the server
+ * @param columnInfo
+ * meta data of the column
+ * @param timeZone
+ * time zone to be used when parsing dates or times
+ * @return the result of parsing {@code value} as an object of type
+ * {@code T}
+ * @throws SQLException
+ * if the value cannot be parsed under the given circumstances
+ */
+ public abstract T parse(ByteFragment value, ClickHouseColumnInfo columnInfo,
+ TimeZone timeZone) throws SQLException;
+
+ /**
+ * Parses the supplied byte fragment {@code value} using meta data contained
+ * in {@code columnInfo}. Date / time parsing uses {@code resultTimeZone},
+ * unless only local values are involved.
+ *
+ * If the result would be null, this method will check if there is a default
+ * value in place which should be returned instead. The default value
+ * depends on the class. This method is intended to be used when parsing
+ * numeric values which later need to be converted to primitive, e.g. int or
+ * float.
+ *
+ * @param value
+ * value as returned from the server or a default value
+ * @param columnInfo
+ * meta data of the column
+ * @param resultTimeZone
+ * time zone to be used when parsing dates or times
+ * @return the result of parsing {@code value} as an object of type
+ * {@code T}
+ * @throws SQLException
+ * if the value cannot be parsed under the given circumstances
+ */
+ public T parseWithDefault(ByteFragment value, ClickHouseColumnInfo columnInfo,
+ TimeZone resultTimeZone) throws SQLException
+ {
+ T t = parse(value, columnInfo, resultTimeZone);
+ return t == null ? getDefaultValue() : t;
+ }
+
+ protected T getDefaultValue() {
+ return null;
+ }
+
+ private static final class ClickHouseValueParserFunctionWrapper
+ extends ClickHouseValueParser
+ {
+
+ private final Function f;
+ private final T nanValue;
+ private final T defaultValue;
+ private final Class clazz;
+
+ private ClickHouseValueParserFunctionWrapper(Function f,
+ T defaultValue, T nanValue, Class clazz)
+ {
+ this.f = Objects.requireNonNull(f);
+ this.nanValue = nanValue;
+ this.defaultValue = defaultValue;
+ this.clazz = Objects.requireNonNull(clazz);
+ }
+
+ @Override
+ public T parse(ByteFragment value, ClickHouseColumnInfo columnInfo,
+ TimeZone resultTimeZone) throws SQLException
+ {
+ if (value.isNull() || value.isEmpty()) {
+ return null;
+ }
+ if (nanValue != null && value.isNaN()) {
+ return nanValue;
+ }
+ try {
+ return f.apply(value.asString());
+ } catch (Exception e) {
+ throw new ClickHouseUnknownException(
+ "Error parsing '" + value.asString() + "' as " + clazz.getName(),
+ e);
+ }
+ }
+
+ @Override
+ protected T getDefaultValue() {
+ return defaultValue;
+ }
+
+ }
+
+}
diff --git a/src/main/java/ru/yandex/clickhouse/response/parser/ClickHouseZonedDateTimeParser.java b/src/main/java/ru/yandex/clickhouse/response/parser/ClickHouseZonedDateTimeParser.java
new file mode 100644
index 000000000..2d438380f
--- /dev/null
+++ b/src/main/java/ru/yandex/clickhouse/response/parser/ClickHouseZonedDateTimeParser.java
@@ -0,0 +1,72 @@
+package ru.yandex.clickhouse.response.parser;
+
+import java.time.OffsetDateTime;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.TimeZone;
+
+import ru.yandex.clickhouse.response.ClickHouseColumnInfo;
+
+final class ClickHouseZonedDateTimeParser extends ClickHouseDateValueParser {
+
+ private static ClickHouseZonedDateTimeParser instance;
+
+ static ClickHouseZonedDateTimeParser getInstance() {
+ if (instance == null) {
+ instance = new ClickHouseZonedDateTimeParser();
+ }
+ return instance;
+ }
+
+ private ClickHouseZonedDateTimeParser() {
+ super(ZonedDateTime.class);
+ }
+
+ @Override
+ ZonedDateTime parseDate(String value, ClickHouseColumnInfo columnInfo,
+ TimeZone timeZone)
+ {
+ return parseAsLocalDate(value)
+ .atStartOfDay(effectiveTimeZone(columnInfo, timeZone));
+ }
+
+ @Override
+ ZonedDateTime parseDateTime(String value, ClickHouseColumnInfo columnInfo,
+ TimeZone timeZone)
+ {
+ return parseAsLocalDateTime(value)
+ .atZone(effectiveTimeZone(columnInfo, timeZone));
+ }
+
+ @Override
+ ZonedDateTime parseNumber(long value, ClickHouseColumnInfo columnInfo,
+ TimeZone timeZone)
+ {
+ return parseAsInstant(value)
+ .atZone(effectiveTimeZone(columnInfo, timeZone));
+ }
+
+ @Override
+ ZonedDateTime parseOther(String value, ClickHouseColumnInfo columnInfo,
+ TimeZone timeZone)
+ {
+ try {
+ return parseAsLocalDateTime(value)
+ .atZone(effectiveTimeZone(columnInfo, timeZone));
+ } catch(DateTimeParseException dtpe) {
+ // try next candidate
+ }
+
+ try {
+ return OffsetDateTime.parse(value, DateTimeFormatter.ISO_OFFSET_DATE_TIME)
+ .toZonedDateTime();
+ } catch (DateTimeParseException dtpe) {
+ // try another way
+ }
+
+ return parseAsInstant(value)
+ .atZone(effectiveTimeZone(columnInfo, timeZone));
+ }
+
+}
diff --git a/src/main/java/ru/yandex/clickhouse/util/ClickHouseArrayUtil.java b/src/main/java/ru/yandex/clickhouse/util/ClickHouseArrayUtil.java
index c6e3533ce..08f0aa62e 100644
--- a/src/main/java/ru/yandex/clickhouse/util/ClickHouseArrayUtil.java
+++ b/src/main/java/ru/yandex/clickhouse/util/ClickHouseArrayUtil.java
@@ -1,15 +1,28 @@
package ru.yandex.clickhouse.util;
+import java.sql.Date;
+import java.sql.SQLException;
+import java.sql.Timestamp;
import java.util.Collection;
import java.util.TimeZone;
+import com.google.common.primitives.Primitives;
+
import ru.yandex.clickhouse.ClickHouseUtil;
+import ru.yandex.clickhouse.response.ArrayByteFragment;
+import ru.yandex.clickhouse.response.ByteFragment;
+import ru.yandex.clickhouse.response.ClickHouseColumnInfo;
+import ru.yandex.clickhouse.response.parser.ClickHouseValueParser;
/**
* @author Dmitry Andreev
*/
public class ClickHouseArrayUtil {
+ private static final char ARRAY_ELEMENTS_SEPARATOR = ',';
+ private static final char STRING_QUOTATION = '\'';
+ private static final int MAX_ARRAY_DEPTH = 32;
+
private ClickHouseArrayUtil() {
// NOP
}
@@ -63,6 +76,131 @@ public static String toString(Collection> collection, TimeZone dateTimeZone,
return toString(collection.toArray(), dateTimeZone, dateTimeTimeZone);
}
+ public static Object parseArray(ByteFragment value, boolean useObjects,
+ TimeZone timeZone, int arrayLevel, Class> elementClass, ClickHouseColumnInfo columnInfo) throws SQLException
+ {
+ if (arrayLevel > ClickHouseArrayUtil.MAX_ARRAY_DEPTH) {
+ throw new IllegalArgumentException("Maximum parse depth exceeded");
+ }
+
+ if (value.isNull()) {
+ return null;
+ }
+
+ if (value.charAt(0) != '[' || value.charAt(value.length() - 1) != ']') {
+ throw new IllegalArgumentException("not an array: " + value);
+ }
+
+ if ((elementClass == Date.class || elementClass == Timestamp.class) && timeZone == null) {
+ throw new IllegalArgumentException("Time zone must be provided for date/dateTime array");
+ }
+
+ ByteFragment trim = value.subseq(1, value.length() - 2);
+
+ int index = 0;
+ Object array;
+ if (arrayLevel > 1) {
+ int[] dimensions = new int[arrayLevel];
+ dimensions[0] = ClickHouseArrayUtil.getArrayLength(trim);
+ array = java.lang.reflect.Array.newInstance(
+ useObjects ? elementClass : Primitives.unwrap(elementClass),
+ dimensions
+ );
+ } else {
+ array = java.lang.reflect.Array.newInstance(
+ useObjects ? elementClass : Primitives.unwrap(elementClass),
+ ClickHouseArrayUtil.getArrayLength(trim)
+ );
+ }
+ int fieldStart = 0;
+ int currentLevel = 0;
+ boolean inQuotation = false;
+ for (int chIdx = 0; chIdx < trim.length(); chIdx++) {
+ int ch = trim.charAt(chIdx);
+
+ if (ch == '\\') {
+ chIdx++;
+ }
+ inQuotation = ch == STRING_QUOTATION ^ inQuotation;
+ if (!inQuotation) {
+ if (ch == '[') {
+ currentLevel++;
+ } else if (ch == ']') {
+ currentLevel--;
+ }
+ }
+
+ if (!inQuotation && ch == ARRAY_ELEMENTS_SEPARATOR && currentLevel == 0 || chIdx == trim.length() - 1) {
+ int fieldEnd = chIdx == trim.length() - 1 ? chIdx + 1 : chIdx;
+ if (trim.charAt(fieldStart) == '\'') {
+ fieldStart++;
+ fieldEnd--;
+ }
+ ArrayByteFragment fragment = ArrayByteFragment.wrap(trim.subseq(fieldStart, fieldEnd - fieldStart));
+ if (arrayLevel > 1) {
+ Object arrayValue = parseArray(
+ fragment, useObjects, timeZone, arrayLevel - 1, elementClass, columnInfo);
+ java.lang.reflect.Array.set(array, index++, arrayValue);
+ } else {
+ Object o = useObjects
+ ? ClickHouseValueParser.getParser(elementClass).parse(
+ fragment, columnInfo, timeZone)
+ : ClickHouseValueParser.getParser(elementClass).parseWithDefault(
+ fragment, columnInfo, timeZone);
+ java.lang.reflect.Array.set(array, index++, o);
+ }
+ fieldStart = chIdx + 1;
+ }
+ }
+
+ return array;
+ }
+
+ public static int getArrayLength(ByteFragment value) {
+ if (value.length() == 0) {
+ return 0;
+ }
+
+ int length = 1;
+ boolean inQuotation = false;
+ int arrayLevel = 0;
+ for (int i = 0; i < value.length(); i++) {
+ int ch = value.charAt(i);
+
+ if (ch == '\\') {
+ i++;
+ }
+
+ inQuotation = ch == ClickHouseArrayUtil.STRING_QUOTATION ^ inQuotation;
+ if (!inQuotation) {
+ if (ch == '[') {
+ ++arrayLevel;
+ } else if (ch == ']') {
+ --arrayLevel;
+ } else if (ch == ClickHouseArrayUtil.ARRAY_ELEMENTS_SEPARATOR && arrayLevel == 0) {
+ ++length;
+ }
+ }
+ }
+ return length;
+ }
+
+ public static Object parseArray(ByteFragment value,
+ boolean useObjects, TimeZone timeZone, ClickHouseColumnInfo columnInfo)
+ throws SQLException
+ {
+ return parseArray(value, useObjects, timeZone, columnInfo.getArrayLevel(),
+ columnInfo.getEffectiveClickHouseDataType().getJavaClass(), columnInfo);
+ }
+
+ static Object parseArray(ByteFragment value, Class> clazz,
+ boolean useObjects, ClickHouseColumnInfo columnInfo)
+ throws SQLException
+ {
+ return parseArray(value, useObjects, null, columnInfo.getArrayLevel(),
+ clazz, columnInfo);
+ }
+
/**
* @deprecated convenience for unit tests
*/
@@ -230,4 +368,6 @@ private String build() {
return builder.toString();
}
}
+
+
}
diff --git a/src/main/java/ru/yandex/clickhouse/util/ClickHouseValueFormatter.java b/src/main/java/ru/yandex/clickhouse/util/ClickHouseValueFormatter.java
index 529450595..743221c07 100644
--- a/src/main/java/ru/yandex/clickhouse/util/ClickHouseValueFormatter.java
+++ b/src/main/java/ru/yandex/clickhouse/util/ClickHouseValueFormatter.java
@@ -1,22 +1,38 @@
package ru.yandex.clickhouse.util;
-import ru.yandex.clickhouse.ClickHouseArray;
-import ru.yandex.clickhouse.ClickHouseUtil;
-
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.time.OffsetTime;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.TimeZone;
import java.util.UUID;
+import ru.yandex.clickhouse.ClickHouseArray;
+import ru.yandex.clickhouse.ClickHouseUtil;
+import ru.yandex.clickhouse.domain.ClickHouseDataType;
+
public final class ClickHouseValueFormatter {
public static final String NULL_MARKER = "\\N";
+ private static final DateTimeFormatter DATE_FORMATTER =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd");
+ private static final DateTimeFormatter DATE_TIME_FORMATTER =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ private static final DateTimeFormatter TIME_FORMATTER =
+ DateTimeFormatter.ofPattern("HH:mm:ss");
+
private static ThreadLocal dateFormat = new ThreadLocal() {
@Override
protected SimpleDateFormat initialValue() {
@@ -99,8 +115,12 @@ public static String formatDate(Date date, TimeZone timeZone) {
}
public static String formatTime(Time time, TimeZone timeZone) {
- getDateTimeFormat().setTimeZone(timeZone);
- return getDateTimeFormat().format(time);
+ return TIME_FORMATTER.format(
+ Instant.ofEpochMilli(time.getTime())
+ .atZone(timeZone.toZoneId())
+ .toLocalTime());
+ // getDateTimeFormat().setTimeZone(timeZone);
+ // return getDateTimeFormat().format(time);
}
public static String formatTimestamp(Timestamp time, TimeZone timeZone) {
@@ -116,6 +136,51 @@ public static String formatBigInteger(BigInteger x) {
return x.toString();
}
+ public static String formatLocalDate(LocalDate x) {
+ return DATE_FORMATTER.format(x);
+ }
+
+ public static String formatLocalDateTime(LocalDateTime x) {
+ return DATE_TIME_FORMATTER.format(x);
+ }
+
+ /**
+ * Formats a {@link LocalTime} as "HH:mm:ss". There isn't any
+ * dedicated ClickHouse data type for times, so this is the most
+ * straightforward thing to do. It would be wrong for the JDBC driver to
+ * construct an artificial {@link ClickHouseDataType#DateTime DateTime}
+ * representation using a dummy date, e.g. 1970-01-01.
+ *
+ * @param x
+ * a {@link LocalTime} parameter
+ * @return {@code x} formatted as "HH:mm:ss"
+ */
+ public static String formatLocalTime(LocalTime x) {
+ return TIME_FORMATTER.format(x);
+ }
+
+ public static String formatOffsetTime(OffsetTime x) {
+ return DateTimeFormatter.ISO_OFFSET_TIME.format(x);
+ }
+
+ public static String formatOffsetDateTime(OffsetDateTime x, TimeZone timeZone) {
+ return DATE_TIME_FORMATTER
+ .withZone(timeZone.toZoneId())
+ .format(x);
+ }
+
+ public static String formatZonedDateTime(ZonedDateTime x, TimeZone timeZone) {
+ return DATE_TIME_FORMATTER
+ .withZone(timeZone.toZoneId())
+ .format(x);
+ }
+
+ public static String formatInstant(Instant x, TimeZone timeZone) {
+ return DATE_TIME_FORMATTER
+ .withZone(timeZone.toZoneId())
+ .format(x);
+ }
+
public static String formatObject(Object x, TimeZone dateTimeZone,
TimeZone dateTimeTimeZone)
{
@@ -142,10 +207,22 @@ public static String formatObject(Object x, TimeZone dateTimeZone,
return formatBytes((byte[]) x);
} else if (x instanceof Date) {
return formatDate((Date) x, dateTimeZone);
+ } else if (x instanceof LocalDate) {
+ return formatLocalDate((LocalDate) x);
} else if (x instanceof Time) {
return formatTime((Time) x, dateTimeTimeZone);
+ } else if (x instanceof LocalTime) {
+ return formatLocalTime((LocalTime) x);
+ } else if (x instanceof OffsetTime) {
+ return formatOffsetTime((OffsetTime) x);
} else if (x instanceof Timestamp) {
return formatTimestamp((Timestamp) x, dateTimeTimeZone);
+ } else if (x instanceof LocalDateTime) {
+ return formatLocalDateTime((LocalDateTime) x);
+ } else if (x instanceof OffsetDateTime) {
+ return formatOffsetDateTime((OffsetDateTime) x, dateTimeTimeZone);
+ } else if (x instanceof ZonedDateTime) {
+ return formatZonedDateTime((ZonedDateTime) x, dateTimeTimeZone);
} else if (x instanceof Boolean) {
return formatBoolean(((Boolean) x).booleanValue());
} else if (x instanceof UUID) {
@@ -153,7 +230,7 @@ public static String formatObject(Object x, TimeZone dateTimeZone,
} else if (x instanceof BigInteger) {
return formatBigInteger((BigInteger) x);
} else if (x instanceof Collection) {
- return ClickHouseArrayUtil.toString((Collection) x, dateTimeZone, dateTimeTimeZone);
+ return ClickHouseArrayUtil.toString((Collection>) x, dateTimeZone, dateTimeTimeZone);
} else if (x.getClass().isArray()) {
return ClickHouseArrayUtil.arrayToString(x, dateTimeZone, dateTimeTimeZone);
} else {
diff --git a/src/test/java/ru/yandex/clickhouse/ClickHousePreparedStatementTest.java b/src/test/java/ru/yandex/clickhouse/ClickHousePreparedStatementTest.java
index a18f183b6..ba9061123 100644
--- a/src/test/java/ru/yandex/clickhouse/ClickHousePreparedStatementTest.java
+++ b/src/test/java/ru/yandex/clickhouse/ClickHousePreparedStatementTest.java
@@ -6,6 +6,8 @@
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
import java.util.TimeZone;
import org.apache.http.impl.client.CloseableHttpClient;
@@ -176,6 +178,41 @@ public void testSetDateOtherTimeZoneServerTime() throws Exception {
assertParamMatches(s, "'2019-05-07'");
}
+ @Test
+ public void testSetDateCalendar() throws Exception {
+ ClickHouseProperties props = new ClickHouseProperties();
+ props.setUseServerTimeZoneForDates(true);
+ ClickHousePreparedStatement s = createStatement(
+ TimeZone.getTimeZone("Asia/Tokyo"),
+ props);
+ Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Europe/Berlin"));
+ s.setDate(1, new Date(1557168043000L), cal);
+ assertParamMatches(s, "'2019-05-06'");
+ }
+
+ @Test
+ public void testSetDateCalendarSameTimeZone() throws Exception {
+ ClickHouseProperties props = new ClickHouseProperties();
+ props.setUseServerTimeZoneForDates(true);
+ ClickHousePreparedStatement s = createStatement(
+ TimeZone.getTimeZone("Asia/Tokyo"),
+ props);
+ Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Asia/Tokyo"));
+ s.setDate(1, new Date(1557168043000L), cal);
+ assertParamMatches(s, "'2019-05-07'");
+ }
+
+ @Test
+ public void testSetDateCalendarNull() throws Exception {
+ ClickHouseProperties props = new ClickHouseProperties();
+ props.setUseServerTimeZoneForDates(true);
+ ClickHousePreparedStatement s = createStatement(
+ TimeZone.getTimeZone("Asia/Tokyo"),
+ props);
+ s.setDate(1, new Date(1557168043000L), null);
+ assertParamMatches(s, "'2019-05-07'");
+ }
+
@Test
public void testSetTimeNull() throws Exception {
ClickHousePreparedStatement s = createStatement();
@@ -187,7 +224,7 @@ public void testSetTimeNull() throws Exception {
public void testSetTimeNormal() throws Exception {
ClickHousePreparedStatement s = createStatement();
s.setTime(1, new Time(1557168043000L));
- assertParamMatches(s, "'2019-05-06 21:40:43'");
+ assertParamMatches(s, "'21:40:43'");
}
@Test
@@ -196,7 +233,36 @@ public void testSetTimeNormalOtherTimeZone() throws Exception {
TimeZone.getTimeZone("America/Los_Angeles"),
new ClickHouseProperties());
s.setTime(1, new Time(1557168043000L));
- assertParamMatches(s, "'2019-05-06 11:40:43'");
+ assertParamMatches(s, "'11:40:43'");
+ }
+
+ @Test
+ public void testSetTimeCalendar() throws Exception {
+ ClickHousePreparedStatement s = createStatement(
+ TimeZone.getTimeZone("America/Los_Angeles"),
+ new ClickHouseProperties());
+ Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Asia/Kuala_Lumpur"));
+ s.setTime(1, new Time(1557168043000L), cal);
+ assertParamMatches(s, "'02:40:43'");
+ }
+
+ @Test
+ public void testSetTimeCalendarNull() throws Exception {
+ ClickHousePreparedStatement s = createStatement(
+ TimeZone.getTimeZone("America/Los_Angeles"),
+ new ClickHouseProperties());
+ s.setTime(1, new Time(1557168043000L), null);
+ assertParamMatches(s, "'11:40:43'");
+ }
+
+ @Test
+ public void testSetTimeCalendarSameTimeZone() throws Exception {
+ ClickHousePreparedStatement s = createStatement(
+ TimeZone.getTimeZone("America/Los_Angeles"),
+ new ClickHouseProperties());
+ Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("America/Los_Angeles"));
+ s.setTime(1, new Time(1557168043000L), cal);
+ assertParamMatches(s, "'11:40:43'");
}
@Test
@@ -222,6 +288,35 @@ public void testSetTimestampNormalOtherTimeZone() throws Exception {
assertParamMatches(s, "'2019-05-06 11:40:43'");
}
+ @Test
+ public void testSetTimestampCalendar() throws Exception {
+ ClickHousePreparedStatement s = createStatement(
+ TimeZone.getTimeZone("America/Los_Angeles"),
+ new ClickHouseProperties());
+ Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Asia/Manila"));
+ s.setTimestamp(1, new Timestamp(1557168043000L), cal);
+ assertParamMatches(s, "'2019-05-07 02:40:43'");
+ }
+
+ @Test
+ public void testSetTimestampCalendarSameTimeZone() throws Exception {
+ ClickHousePreparedStatement s = createStatement(
+ TimeZone.getTimeZone("America/Los_Angeles"),
+ new ClickHouseProperties());
+ Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("America/Los_Angeles"));
+ s.setTimestamp(1, new Timestamp(1557168043000L), cal);
+ assertParamMatches(s, "'2019-05-06 11:40:43'");
+ }
+
+ @Test
+ public void testSetTimestampCalendarNull() throws Exception {
+ ClickHousePreparedStatement s = createStatement(
+ TimeZone.getTimeZone("America/Los_Angeles"),
+ new ClickHouseProperties());
+ s.setTimestamp(1, new Timestamp(1557168043000L), null);
+ assertParamMatches(s, "'2019-05-06 11:40:43'");
+ }
+
private static void assertParamMatches(ClickHousePreparedStatement stmt, String expected) {
assertEquals(stmt.asSql(), SQL_STATEMENT + expected + ")");
}
diff --git a/src/test/java/ru/yandex/clickhouse/domain/ClickHouseDataTypeTestDataProvider.java b/src/test/java/ru/yandex/clickhouse/domain/ClickHouseDataTypeTestDataProvider.java
index 7ebddf37f..776ff1752 100644
--- a/src/test/java/ru/yandex/clickhouse/domain/ClickHouseDataTypeTestDataProvider.java
+++ b/src/test/java/ru/yandex/clickhouse/domain/ClickHouseDataTypeTestDataProvider.java
@@ -20,7 +20,7 @@ public static Object[][] provideSimpleDataTypes() {
}
public static List provideDataTypes() {
- List filtered = new ArrayList();
+ List filtered = new ArrayList<>();
for (int i = 0; i < getTestData().length; i++) {
ClickHouseDataTypeTestData d = getTestData()[i];
if (d.isCheckValue()) {
@@ -86,7 +86,7 @@ private static ClickHouseDataTypeTestData[] initTestData() {
create("TINYINT", ClickHouseDataType.Int8),
create("DEC", ClickHouseDataType.Decimal),
create("BINARY", ClickHouseDataType.FixedString),
- create("FLOAT", ClickHouseDataType.Float32),
+ create("REAL", ClickHouseDataType.Float32),
create("CHAR", ClickHouseDataType.String),
create("VARCHAR", ClickHouseDataType.String),
create("TEXT", ClickHouseDataType.String),
diff --git a/src/test/java/ru/yandex/clickhouse/integration/ArrayTest.java b/src/test/java/ru/yandex/clickhouse/integration/ArrayTest.java
index bf868423f..25554094a 100644
--- a/src/test/java/ru/yandex/clickhouse/integration/ArrayTest.java
+++ b/src/test/java/ru/yandex/clickhouse/integration/ArrayTest.java
@@ -107,16 +107,17 @@ public void testInsertUIntArray() throws SQLException {
connection.createStatement().execute("DROP TABLE IF EXISTS test.unsigned_array");
connection.createStatement().execute(
"CREATE TABLE IF NOT EXISTS test.unsigned_array"
- + " (ua32 Array(UInt32), ua64 Array(UInt64), f64 Array(Float64)) ENGINE = TinyLog"
+ + " (ua32 Array(UInt32), ua64 Array(UInt64), f64 Array(Float64), a32 Array(Int32)) ENGINE = TinyLog"
);
- String insertSql = "INSERT INTO test.unsigned_array (ua32, ua64, f64) VALUES (?, ?, ?)";
+ String insertSql = "INSERT INTO test.unsigned_array (ua32, ua64, f64, a32) VALUES (?, ?, ?, ?)";
PreparedStatement statement = connection.prepareStatement(insertSql);
statement.setArray(1, new ClickHouseArray(ClickHouseDataType.UInt64, new long[]{4294967286L, 4294967287L}));
statement.setArray(2, new ClickHouseArray(ClickHouseDataType.UInt64, new BigInteger[]{new BigInteger("18446744073709551606"), new BigInteger("18446744073709551607")}));
statement.setArray(3, new ClickHouseArray(ClickHouseDataType.Float64, new double[]{1.23, 4.56}));
+ statement.setArray(4, new ClickHouseArray(ClickHouseDataType.Int32, new int[]{-2147483648, 2147483647}));
statement.execute();
statement = connection.prepareStatement(insertSql);
@@ -126,14 +127,15 @@ public void testInsertUIntArray() throws SQLException {
new BigInteger("18446744073709551606"),
new BigInteger("18446744073709551607"))));
statement.setObject(3, new ArrayList