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
69 changes: 69 additions & 0 deletions docs/datetime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# ClickHouse Server

ClickHouse has dedicated data types for [Date](https://clickhouse.tech/docs/en/data_types/date) and [DateTime](https://clickhouse.tech/docs/en/data_types/datetime). A DateTime value is usually to be interpreted in the server time zone, but a DateTime value may also be formatted for a different time zone. Note that the explicit time zone per column only affects inserting and displaying values, _not_ the predicates (see one example [here](https://github.com/ClickHouse/ClickHouse/issues/5206)).


# Setting Values

When setting values via the PreparedStatement setter methods, the JDBC driver does not have any knowledge about the target columns. Its job is to serialize any date time values into a textual representation. One of the aims of this driver is to use a serialization format that makes it easy to use ClickHouse Date or DateTime fields from a Java application.

In some cases the driver cannot perform the serialization without referring to a relevant time zone. This is how it works:

If the client supplies a valid `Calendar` object as optional argument, the driver will use the time zone contained therein (`tz_calendar`).

Two regular time zones are initialized like this:

* `tz_datetime`: value from `ru.yandex.clickhouse.settings.ClickHouseConnectionSettings.USE_TIME_ZONE`. If null, either ClickHouse server time zone (`ru.yandex.clickhouse.settings.ClickHouseConnectionSettings.USE_SERVER_TIME_ZONE` is `true`) or JVM time zone (else)

* `tz_date`: same as `tz_datetime` if `ru.yandex.clickhouse.settings.ClickHouseConnectionSettings.USE_SERVER_TIME_ZONE_FOR_DATES` is `true`, JVM time zone else

The JDBC driver supports all explicit methods, e.g. setDate, setTimestamp etc. with their optional Calendar argument. Providing hints via target SQL type does not have any effect.

The following table illustrates the serialization format for some popular date time data types, which we consider the most convenient. Clients are of course free to take care of serialization themselves by supplying a String or an Integer parameter, optionally using one of the server's utility methods (e.g. [parseDateTimeBestEffort](https://clickhouse.tech/docs/en/query_language/functions/type_conversion_functions/#type_conversion_functions-parsedatetimebesteffort)).

Method | Format | Relevant time zone |
------ | ------ | -------------------
[setDate(int, Date)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setDate-int-java.sql.Date-) | yyyy-MM-dd | tz_date
[setDate(int, Date, Calendar)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setDate-int-java.sql.Date-java.util.Calendar-) | yyyy-MM-dd | tz_calendar
[setObject(int, Date)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setObject-int-java.lang.Object-) | yyyy-MM-dd | tz_date
[setTime(int, Time)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setTime-int-java.sql.Time-) | HH:mm:ss | tz_datetime
[setTime(int, Time, Calendar)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setTime-int-java.sql.Time-java.util.Calendar-) | HH:mm:ss | tz_calendar
[setObject(int, Time)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setObject-int-java.lang.Object-) | HH:mm:ss | tz_datetime
[setTimestamp(int, Timestamp)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setTimestamp-int-java.sql.Timestamp-) | yyyy-MM-dd HH:mm:ss | tz_datetime
[setTimestamp(int, Timestamp, Calendar)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setTimestamp-int-java.sql.Timestamp-java.util.Calendar-) | yyyy-MM-dd HH:mm:ss | tz_calendar
[setObject(int, Timestamp)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setObject-int-java.lang.Object-) | yyyy-MM-dd HH:mm:ss | tz_datetime
[setObject(int, LocalTime)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setObject-int-java.lang.Object-) | HH:mm:ss | _none_
[setObject(int, OffsetTime)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setObject-int-java.lang.Object-) | HH:mm:ssZZZZ | _none_
[setObject(int, LocalDate)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setObject-int-java.lang.Object-) | yyyy-MM-dd | _none_
[setObject(int, LocalDateTime)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setObject-int-java.lang.Object-) | yyyy-MM-dd HH:mm:ss | _none_
[setObject(int, OffsetDateTime)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setObject-int-java.lang.Object-) | yyyy-MM-dd HH:mm:ss | tz_datetime
[setObject(int, ZonedDateTime)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setObject-int-java.lang.Object-) | yyyy-MM-dd HH:mm:ss | tz_datetime
[setObject(int, Instant)](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setObject-int-java.lang.Object-) | yyyy-MM-dd HH:mm:ss | tz_datetime

# Retrieving Values

When retrieving values via the ResultSet's getter methods, the JDBC driver will try to accomodate for some obvious options. If the underlying data field is of type Date or DateTime, the driver knows the implied time zone. This helps during the interpretation of the values retrieved from the server. Users may configure the driver to use a different time zone when reporting results back to the client (via `tz_date` or`tz_datetime`, see above).

The methods which take a [Calendar]((https://docs.oracle.com/javase/8/docs/api/java.base/java/util/Calendar.html) argument behave the same as the corresponding methods without such an argument. The API documentation says something like

> This method uses the given calendar to construct an appropriate millisecond value for the _x_ if the underlying database does not store timezone information.

For Date and DateTime fields, the JDBC driver has enough time zone related information available, so these methods would only be relevant for String or other typed fields. There might be valid use cases, but for now we think that adding such an option would make things even more complicated.

Requested Type | Number | Date | DateTime | Other
---------------| ----------------------------------
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing |

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @pan3793 ! After almost a year, someone stumbles upon wrongly formatted tables in the documentation... 😃 I added a couple of pipes, and the table looks better now in the preview.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are so many tricks about timezone, especially in legacy Java Date API. I also encountered some issues when migrated to java.time API from legacy Date API in another ClickHouse JDBC driver implement in native protocol. And would you mind me pick up some code and doc from this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, life would be boring without time zones and leap seconds. Feel free to use the code in this PR, but please let me know when you find any error, so I can fix it. Have a good weekend!

[Date](https://docs.oracle.com/javase/8/docs/api/java/sql/Date.html) | Seconds or milliseconds past epoch truncated to day in relevant time zone | Date in relevant time zone, midnight | Date time in relevant time zone, rewind to midnight | Try number, date time (with or without offset) truncated to day, date
[Time](https://docs.oracle.com/javase/8/docs/api/java/sql/Time.html) | Local time at 1970-01-01 (e.g. “1337” is “13:37:00” at TZ) | Midnight on 1970-01-01 in relevant time zone | Local time in relevant time zone | Local time in relevant time zone via ISO format or via number, at 1970-01-01
[Timestamp](https://docs.oracle.com/javase/8/docs/api/java/sql/Timestamp.html) | Seconds or milliseconds past epoch | Local date at midnight in relevant time zone | Local date and time in relevant time zone | Number, date time with or without offset
[LocalTime](https://docs.oracle.com/javase/8/docs/api/java/time/LocalTime.html) | Local time (e.g. "430" is "04:30:00") | Midnight | Local time | ISO format with or without offset, number
[OffsetTime](https://docs.oracle.com/javase/8/docs/api/java/time/OffsetTime.html) | Local time with current (!) offset of relevant time zone | Midnight with offset of relevant time zone on that date | Local time with offset of relevant time zone at value's date | ISO format, number
[LocalDate](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html) | Seconds or milliseconds past epoch as local date in relevant time zone | Local date | Local date (no conversion) | Local date, local time, number
[LocalDateTime](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html) | Seconds or milliseconds past epoch as local date time in relevant time zone | Local date midnight | Local date time | Local date time, number
[OffsetDateTime](https://docs.oracle.com/javase/8/docs/api/java/time/OffsetDateTime.html) | Seconds or milliseconds past epoch, offset from relevant time zone | Date midnight in relevant time zone | Date time in relevant time zone | Local date time in relevant time zone, ISO formats, number
[ZonedDdateTime](https://docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html) | Seconds or milliseconds past epoch, offset from relevant time zone | Date midnight in relevant time zone | Date time in relevant time zone | Local date, local date time in relevant time zone, ISO formats, number

# Summary

Life as a developer would be boring without time zones: [xkcd Super Villain Plan](https://xkcd.com/1883) Have fun!

If you think the ClickHouse JDBC driver behaves wrong, please file an issue. Make sure to include some time zone information of your ClickHouse server, the JVM, and the relevant driver settings.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
<jackson-databind.version>2.9.10.8</jackson-databind.version>
<guava.version>29.0-jre</guava.version>
<jaxb.version>2.3.1</jaxb.version>
<jdk.version>1.7</jdk.version>
<jdk.version>1.8</jdk.version>
<testcontainers.version>1.15.1</testcontainers.version>
<testng.version>6.14.3</testng.version>
<mockito.version>1.10.19</mockito.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

import ru.yandex.clickhouse.jdbc.parser.ClickHouseSqlStatement;
import ru.yandex.clickhouse.jdbc.parser.StatementType;

import ru.yandex.clickhouse.response.ClickHouseResponse;
import ru.yandex.clickhouse.settings.ClickHouseProperties;
import ru.yandex.clickhouse.settings.ClickHouseQueryParam;
Expand Down Expand Up @@ -452,17 +453,38 @@ public ResultSetMetaData getMetaData() throws SQLException {

@Override
public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
throw new SQLFeatureNotSupportedException();
if (x != null && cal != null && cal.getTimeZone() != null) {
setBind(
parameterIndex,
ClickHouseValueFormatter.formatDate(x, cal.getTimeZone()),
true);
} else {
setDate(parameterIndex, x);
}
}

@Override
public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
throw new SQLFeatureNotSupportedException();
if (x != null && cal != null && cal.getTimeZone() != null) {
setBind(
parameterIndex,
ClickHouseValueFormatter.formatTime(x, cal.getTimeZone()),
true);
} else {
setTime(parameterIndex, x);
}
}

@Override
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
throw new SQLFeatureNotSupportedException();
if (x != null && cal != null && cal.getTimeZone() != null) {
setBind(
parameterIndex,
ClickHouseValueFormatter.formatTimestamp(x, cal.getTimeZone()),
true);
} else {
setTimestamp(parameterIndex, x);
}
}

@Override
Expand Down
36 changes: 5 additions & 31 deletions src/main/java/ru/yandex/clickhouse/ClickHouseStatement.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,68 +42,42 @@ ResultSet executeQuery(String sql,
List<ClickHouseExternalData> externalData,
Map<String, String> additionalRequestParams) throws SQLException;

/**
* @see #write()
*/
@Deprecated
void sendStream(InputStream content, String table, Map<ClickHouseQueryParam, String> additionalDBParams) throws SQLException;

/**
* @see #write()
*/
@Deprecated
void sendStream(InputStream content, String table) throws SQLException;

/**
* @see #write()
*/
@Deprecated
void sendRowBinaryStream(String sql, Map<ClickHouseQueryParam, String> additionalDBParams, ClickHouseStreamCallback callback) throws SQLException;

/**
* @see #write()
*/
@Deprecated
void sendRowBinaryStream(String sql, ClickHouseStreamCallback callback) throws SQLException;

/**
* @see #write()
*/
@Deprecated
void sendNativeStream(String sql, Map<ClickHouseQueryParam, String> additionalDBParams, ClickHouseStreamCallback callback) throws SQLException;

/**
* @see #write()
*/
@Deprecated
void sendNativeStream(String sql, ClickHouseStreamCallback callback) throws SQLException;

/**
* @see #write()
*/
@Deprecated
void sendCSVStream(InputStream content, String table, Map<ClickHouseQueryParam, String> additionalDBParams) throws SQLException;

/**
* @see #write()
*/
@Deprecated
void sendCSVStream(InputStream content, String table) throws SQLException;

/**
* @see #write()
*/
@Deprecated
void sendStreamSQL(InputStream content, String sql, Map<ClickHouseQueryParam, String> additionalDBParams) throws SQLException;

/**
* @see #write()
*/
@Deprecated
void sendStreamSQL(InputStream content, String sql) throws SQLException;

/**
* Returns extended write-API
* Returns extended write-API, which simplifies uploading larger files or
* data streams
*
* @return a new {@link Writer} builder object which can be used to
* construct a request to the server
*/
Writer write();

Expand Down
70 changes: 57 additions & 13 deletions src/main/java/ru/yandex/clickhouse/Writer.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
import java.io.InputStream;
import java.sql.SQLException;

import static ru.yandex.clickhouse.domain.ClickHouseFormat.*;
import static ru.yandex.clickhouse.domain.ClickHouseFormat.Native;
import static ru.yandex.clickhouse.domain.ClickHouseFormat.RowBinary;
import static ru.yandex.clickhouse.domain.ClickHouseFormat.TabSeparated;

public class Writer extends ConfigurableApi<Writer> {
public final class Writer extends ConfigurableApi<Writer> {

private ClickHouseFormat format = TabSeparated;
private ClickHouseCompression compression = null;
Expand All @@ -29,6 +31,10 @@ public class Writer extends ConfigurableApi<Writer> {

/**
* Specifies format for further insert of data via send()
*
* @param format
* the format of the data to upload
* @return this writer instance
*/
public Writer format(ClickHouseFormat format) {
if (null == format) {
Expand All @@ -41,8 +47,9 @@ public Writer format(ClickHouseFormat format) {
/**
* Set table name for data insertion
*
* @param table table name
* @return this
* @param table
* name of the table to upload the data to
* @return this writer instance
*/
public Writer table(String table) {
this.sql = null;
Expand All @@ -53,8 +60,9 @@ public Writer table(String table) {
/**
* Set SQL for data insertion
*
* @param sql in a form "INSERT INTO table_name [(X,Y,Z)] VALUES "
* @return this
* @param sql
* in a form "INSERT INTO table_name [(X,Y,Z)] VALUES "
* @return this writer instance
*/
public Writer sql(String sql) {
this.sql = sql;
Expand All @@ -64,18 +72,35 @@ public Writer sql(String sql) {

/**
* Specifies data input stream
*
* @param stream
* a stream providing the data to upload
* @return this writer instance
*/
public Writer data(InputStream stream) {
streamProvider = new HoldingInputProvider(stream);
return this;
}

/**
* Specifies data input stream, and the format to use
*
* @param stream
* a stream providing the data to upload
* @param format
* the format of the data to upload
* @return this writer instance
*/
public Writer data(InputStream stream, ClickHouseFormat format) {
return format(format).data(stream);
}

/**
* Shortcut method for specifying a file as an input
*
* @param input
* the file to upload
* @return this writer instance
*/
public Writer data(File input) {
streamProvider = new FileInputProvider(input);
Expand Down Expand Up @@ -126,10 +151,14 @@ private void send(HttpEntity entity) throws SQLException {
/**
* Allows to send stream of data to ClickHouse
*
* @param sql in a form of "INSERT INTO table_name (X,Y,Z) VALUES "
* @param data where to read data from
* @param format format of data in InputStream
* @param sql
* in a form of "INSERT INTO table_name (X,Y,Z) VALUES "
* @param data
* where to read data from
* @param format
* format of data in InputStream
* @throws SQLException
* if the upload fails
*/
public void send(String sql, InputStream data, ClickHouseFormat format) throws SQLException {
sql(sql).data(data).format(format).send();
Expand All @@ -138,17 +167,32 @@ public void send(String sql, InputStream data, ClickHouseFormat format) throws S
/**
* Convenient method for importing the data into table
*
* @param table table name
* @param data source data
* @param format format of data in InputStream
* @param table
* table name
* @param data
* source data
* @param format
* format of data in InputStream
* @throws SQLException
* if the upload fails
*/
public void sendToTable(String table, InputStream data, ClickHouseFormat format) throws SQLException {
table(table).data(data).format(format).send();
}

/**
* Sends the data in RowBinary or in Native formats
* Sends the data in {@link ClickHouseFormat#RowBinary RowBinary} or in
* {@link ClickHouseFormat#Native Native} format
*
* @param sql
* the SQL statement to execute
* @param callback
* data source for the upload
* @param format
* the format to use, either {@link ClickHouseFormat#RowBinary
* RowBinary} or {@link ClickHouseFormat#Native Native}
* @throws SQLException
* if the upload fails
*/
public void send(String sql, ClickHouseStreamCallback callback, ClickHouseFormat format) throws SQLException {
if (!(RowBinary.equals(format) || Native.equals(format))) {
Expand Down
Loading