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
25 changes: 3 additions & 22 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
java: [8, 11]
java: [8, 11, 15]
# most recent LTS releases as well as latest stable builds
clickhouse: ["19.14", "20.3", "20.8", "20.10", "20.12", "21.2", "latest"]
clickhouse: ["20.8", "20.10", "20.12", "21.2", "latest"]
name: Build using JDK ${{ matrix.java }} against ClickHouse ${{ matrix.clickhouse }}
steps:
- name: Check out Git repository
Expand All @@ -56,25 +56,6 @@ jobs:
key: ${{ runner.os }}-build-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-build-
- name: Generate build properties
uses: actions/github-script@v3
id: props
env:
CURRENT_VERSION: ${{ steps.version.outputs.value }}
with:
script: |
const timezones = [
'Asia/Chongqing', 'America/Los_Angeles', 'Etc/UTC', 'Europe/Berlin', 'Europe/Moscow'
];
// surprise me
return {
clickhouse: timezones[Math.floor(Math.random() * Math.floor(timezones.length))] || '',
java: timezones[Math.floor(Math.random() * Math.floor(timezones.length))] || ''
};
- name: Build with Maven
run: |
find . -type f -name "pom.xml" -exec sed -i -e 's|.*argLine.*timezone=.*||g' '{}' \;
mvn --batch-mode --update-snapshots \
-DclickhouseVersion=${{ matrix.clickhouse }} \
-DclickhouseTimezone=${{ fromJSON(steps.props.outputs.result).clickhouse }} \
-Duser.timezone=${{ fromJSON(steps.props.outputs.result).java }} verify
mvn --batch-mode --update-snapshots -DclickhouseVersion=${{ matrix.clickhouse }} verify
62 changes: 62 additions & 0 deletions .github/workflows/timezone.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: TimeZone Test

on:
push:
branches:
- master
- develop
paths-ignore:
- "**.md"
- "docs/**"
- "**/CHANGELOG"

pull_request:
types:
- opened
- synchronize
- reopened
paths-ignore:
- "**.md"
- "docs/**"
- "**/CHANGELOG"

workflow_dispatch:
inputs:
pr:
description: "Pull request#"
required: false

jobs:
timezone:
runs-on: ubuntu-latest
strategy:
matrix:
serverTz: ["Asia/Chongqing", "America/Los_Angeles", "Etc/UTC", "Europe/Berlin", "Europe/Moscow"]
clientTz: ["Asia/Chongqing", "America/Los_Angeles", "Etc/UTC", "Europe/Berlin", "Europe/Moscow"]
fail-fast: false
name: "Test TimeZones - Server: ${{ matrix.serverTz }}, Client: ${{ matrix.clientTz }}"
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- name: Check out PR
run: |
git fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 \
origin pull/${{ github.event.inputs.pr }}/merge:merged-pr && git checkout merged-pr
if: github.event.inputs.pr != ''
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Cache maven dependencies
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-build-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-build-
- name: Test using Maven
run: |
find . -type f -name "pom.xml" -exec sed -i -e 's|.*argLine.*timezone=.*||g' '{}' \;
mvn --batch-mode --update-snapshots \
-DclickhouseTimezone=${{ matrix.serverTz }} \
-Duser.timezone=${{ matrix.clientTz }} verify
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ClickHouse JDBC driver
===============
[![clickhouse-jdbc](https://maven-badges.herokuapp.com/maven-central/ru.yandex.clickhouse/clickhouse-jdbc/badge.svg)](https://maven-badges.herokuapp.com/maven-central/ru.yandex.clickhouse/clickhouse-jdbc) ![Build Status(https://github.com/ClickHouse/clickhouse-jdbc/workflows/Build/badge.svg)](https://github.com/ClickHouse/clickhouse-jdbc/workflows/Build/badge.svg)
[![clickhouse-jdbc](https://maven-badges.herokuapp.com/maven-central/ru.yandex.clickhouse/clickhouse-jdbc/badge.svg)](https://maven-badges.herokuapp.com/maven-central/ru.yandex.clickhouse/clickhouse-jdbc) ![Build Status(https://github.com/ClickHouse/clickhouse-jdbc/workflows/Build/badge.svg)](https://github.com/ClickHouse/clickhouse-jdbc/workflows/Build/badge.svg) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=ClickHouse_clickhouse-jdbc&metric=coverage)](https://sonarcloud.io/dashboard?id=ClickHouse_clickhouse-jdbc)

This is a basic and restricted implementation of jdbc driver for ClickHouse.
It has support of a minimal subset of features to be usable.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public interface ClickHouseConnection extends Connection {
@Deprecated
ClickHouseStatement createClickHouseStatement() throws SQLException;

TimeZone getServerTimeZone();

TimeZone getTimeZone();

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ public class ClickHouseConnectionImpl implements ClickHouseConnection {

private boolean closed = false;

private TimeZone serverTimeZone;
private TimeZone timezone;
private volatile String serverVersion;
private String serverVersion;

public ClickHouseConnectionImpl(String url) throws SQLException {
this(url, new ClickHouseProperties());
Expand All @@ -74,25 +75,35 @@ public ClickHouseConnectionImpl(String url, ClickHouseProperties properties) thr
}catch (Exception e) {
throw new IllegalStateException("cannot initialize http client", e);
}
initTimeZone(this.properties);
initConnection(this.properties);
}

private void initTimeZone(ClickHouseProperties properties) throws SQLException {
private void initConnection(ClickHouseProperties properties) throws SQLException {
// timezone
if (properties.isUseServerTimeZone() && !Utils.isNullOrEmptyString(properties.getUseTimeZone())) {
throw new IllegalArgumentException(String.format("only one of %s or %s must be enabled", ClickHouseConnectionSettings.USE_SERVER_TIME_ZONE.getKey(), ClickHouseConnectionSettings.USE_TIME_ZONE.getKey()));
}
if (!properties.isUseServerTimeZone() && Utils.isNullOrEmptyString(properties.getUseTimeZone())) {
throw new IllegalArgumentException(String.format("one of %s or %s must be enabled", ClickHouseConnectionSettings.USE_SERVER_TIME_ZONE.getKey(), ClickHouseConnectionSettings.USE_TIME_ZONE.getKey()));
}
if (properties.isUseServerTimeZone()) {
timezone = TimeZone.getTimeZone("UTC"); // just for next query
try (ResultSet rs = createStatement().executeQuery("select timezone()")) {
if (rs.next()) {
timezone = TimeZone.getTimeZone(rs.getString(1));
}

serverTimeZone = TimeZone.getTimeZone("UTC"); // just for next query
try (Statement s = createStatement(); ResultSet rs = s.executeQuery("select timezone(), version()")) {
if (rs.next()) {
serverTimeZone = TimeZone.getTimeZone(rs.getString(1));
serverVersion = rs.getString(2);
}
} else if (!Utils.isNullOrEmptyString(properties.getUseTimeZone())) {
timezone = TimeZone.getTimeZone(properties.getUseTimeZone());
}

timezone = serverTimeZone;
if (!properties.isUseServerTimeZone()) {
timezone = Utils.isNullOrEmptyString(properties.getUseTimeZone())
? TimeZone.getDefault()
: TimeZone.getTimeZone(properties.getUseTimeZone());
}

if (serverVersion == null) {
serverVersion = "";
}
}

Expand Down Expand Up @@ -122,6 +133,11 @@ public TimeZone getTimeZone() {
return timezone;
}

@Override
public TimeZone getServerTimeZone() {
return serverTimeZone;
}

private ClickHouseStatement createClickHouseStatement(CloseableHttpClient httpClient) throws SQLException {
return LogProxy.wrap(
ClickHouseStatement.class,
Expand Down Expand Up @@ -169,12 +185,6 @@ public ClickHouseStatement createStatement(int resultSetType, int resultSetConcu
*/
@Override
public String getServerVersion() throws SQLException {
if (serverVersion == null) {
ResultSet rs = createStatement().executeQuery("select version()");
rs.next();
serverVersion = rs.getString(1);
rs.close();
}
return serverVersion;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,8 @@ public ResultSet getColumns(String catalog, String schemaPattern, String tableNa
//column name
ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse(
descTable.getString("type"),
descTable.getString("name"));
descTable.getString("name"),
connection.getServerTimeZone());
row.add(columnInfo.getColumnName());
//data type
row.add(String.valueOf(columnInfo.getClickHouseDataType().getSqlType()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
import java.sql.Date;
import java.sql.JDBCType;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
Expand All @@ -19,7 +23,14 @@
* modifiers for the underlying base data types.
*/
public enum ClickHouseDataType {

// aliases:
// https://clickhouse.tech/docs/en/sql-reference/data-types/multiword-types/
// https://github.com/ClickHouse/ClickHouse/blob/master/src/DataTypes/DataTypeCustomIPv4AndIPv6.cpp
// https://github.com/ClickHouse/ClickHouse/blob/master/src/DataTypes/registerDataTypeDateTime.cpp
// https://github.com/ClickHouse/ClickHouse/blob/master/src/DataTypes/DataTypesDecimal.cpp
// https://github.com/ClickHouse/ClickHouse/blob/master/src/DataTypes/DataTypeFixedString.cpp
// https://github.com/ClickHouse/ClickHouse/blob/master/src/DataTypes/DataTypesNumber.cpp
// https://github.com/ClickHouse/ClickHouse/blob/master/src/DataTypes/DataTypeString.cpp
IntervalYear (JDBCType.INTEGER, Integer.class, true, 19, 0),
IntervalQuarter (JDBCType.INTEGER, Integer.class, true, 19, 0),
IntervalMonth (JDBCType.INTEGER, Integer.class, true, 19, 0),
Expand All @@ -28,54 +39,83 @@ public enum ClickHouseDataType {
IntervalHour (JDBCType.INTEGER, Integer.class, true, 19, 0),
IntervalMinute (JDBCType.INTEGER, Integer.class, true, 19, 0),
IntervalSecond (JDBCType.INTEGER, Integer.class, true, 19, 0),
UInt64 (JDBCType.BIGINT, BigInteger.class, false, 19, 0),
UInt32 (JDBCType.BIGINT, Long.class, false, 10, 0),
UInt16 (JDBCType.SMALLINT, Integer.class, false, 5, 0),
UInt8 (JDBCType.TINYINT, Integer.class, false, 3, 0),
UInt256 (JDBCType.NUMERIC, BigInteger.class, true, 39, 0),
UInt128 (JDBCType.NUMERIC, BigInteger.class, true, 20, 0),
UInt64 (JDBCType.BIGINT, BigInteger.class, false, 19, 0,
"BIGINT UNSIGNED"),
UInt32 (JDBCType.BIGINT, Long.class, false, 10, 0,
"INT UNSIGNED", "INTEGER UNSIGNED", "MEDIUMINT UNSIGNED"),
UInt16 (JDBCType.SMALLINT, Integer.class, false, 5, 0,
"SMALLINT UNSIGNED"),
UInt8 (JDBCType.TINYINT, Integer.class, false, 3, 0,
"TINYINT UNSIGNED", "INT1 UNSIGNED"),
Int256 (JDBCType.NUMERIC, BigInteger.class, true, 40, 0),
Int128 (JDBCType.NUMERIC, BigInteger.class, true, 20, 0),
Int64 (JDBCType.BIGINT, Long.class, true, 20, 0,
"BIGINT"),
"BIGINT", "BIGINT SIGNED"),
Int32 (JDBCType.INTEGER, Integer.class, true, 11, 0,
"INTEGER",
"INT"),
"INT", "INTEGER", "MEDIUMINT", "INT SIGNED", "INTEGER SIGNED", "MEDIUMINT SIGNED"),
Int16 (JDBCType.SMALLINT, Integer.class, true, 6, 0,
"SMALLINT"),
"SMALLINT", "SMALLINT SIGNED"),
Int8 (JDBCType.TINYINT, Integer.class, true, 4, 0,
"TINYINT"),
"TINYINT", "BOOL", "BOOLEAN", "INT1", "BYTE", "TINYINT SIGNED", "INT1 SIGNED"),
Date (JDBCType.DATE, Date.class, false, 10, 0),
DateTime (JDBCType.TIMESTAMP, Timestamp.class, false, 19, 0,
"TIMESTAMP"),
Enum8 (JDBCType.VARCHAR, String.class, false, 0, 0),
DateTime32 (JDBCType.TIMESTAMP, Timestamp.class, false, 19, 0),
DateTime64 (JDBCType.TIMESTAMP, Timestamp.class, false, 38, 3), // scale up to 18
Enum8 (JDBCType.VARCHAR, String.class, false, 0, 0,
"ENUM"),
Enum16 (JDBCType.VARCHAR, String.class, false, 0, 0),
Float32 (JDBCType.REAL, Float.class, true, 8, 8,
"REAL"),
"SINGLE", "REAL"),
Float64 (JDBCType.DOUBLE, Double.class, true, 17, 17,
"DOUBLE"),
"DOUBLE", "DOUBLE PRECISION"),
Decimal32 (JDBCType.DECIMAL, BigDecimal.class, true, 9, 9),
Decimal64 (JDBCType.DECIMAL, BigDecimal.class, true, 18, 18),
Decimal128 (JDBCType.DECIMAL, BigDecimal.class, true, 38, 38),
Decimal256 (JDBCType.DECIMAL, BigDecimal.class, true, 76, 20),
Decimal (JDBCType.DECIMAL, BigDecimal.class, true, 0, 0,
"DEC"),
"DEC", "NUMERIC", "FIXED"),
UUID (JDBCType.OTHER, UUID.class, false, 36, 0),
IPv4 (JDBCType.VARCHAR, String.class, false, 10, 0),
IPv6 (JDBCType.VARCHAR, String.class, false, 0, 0),
String (JDBCType.VARCHAR, String.class, false, 0, 0,
"LONGBLOB",
"MEDIUMBLOB",
"TINYBLOB",
"MEDIUMTEXT",
"CHAR",
"VARCHAR",
"TEXT",
"TINYTEXT",
"LONGTEXT",
"BLOB"),
"CHAR", "NCHAR", "CHARACTER", "VARCHAR", "NVARCHAR", "VARCHAR2",
"TEXT", "TINYTEXT", "MEDIUMTEXT", "LONGTEXT",
"BLOB", "CLOB", "TINYBLOB", "MEDIUMBLOB", "LONGBLOB", "BYTEA",
"CHARACTER LARGE OBJECT", "CHARACTER VARYING", "CHAR LARGE OBJECT", "CHAR VARYING",
"NATIONAL CHAR", "NATIONAL CHARACTER", "NATIONAL CHARACTER LARGE OBJECT",
"NATIONAL CHARACTER VARYING", "NATIONAL CHAR VARYING",
"NCHAR VARYING", "NCHAR LARGE OBJECT", "BINARY LARGE OBJECT", "BINARY VARYING"),
FixedString (JDBCType.CHAR, String.class, false, -1, 0,
"BINARY"),
Nothing (JDBCType.NULL, Object.class, false, 0, 0),
Nested (JDBCType.STRUCT, String.class, false, 0, 0),
// TODO use list/collection for Tuple
Tuple (JDBCType.OTHER, String.class, false, 0, 0),
Array (JDBCType.ARRAY, Array.class, false, 0, 0),
Map (JDBCType.OTHER, Map.class, false, 0, 0),
AggregateFunction (JDBCType.OTHER, String.class, false, 0, 0),
Unknown (JDBCType.OTHER, String.class, false, 0, 0);

private static final Map<String, ClickHouseDataType> name2type;

static {
Map<String, ClickHouseDataType> map = new HashMap<>();
for (ClickHouseDataType t : ClickHouseDataType.values()) {
assert map.put(t.name(), t) == null;
String nameInUpperCase = t.name().toUpperCase();
if (!nameInUpperCase.equals(t.name())) {
assert map.put(nameInUpperCase, t) == null;
}
for (String alias: t.aliases) {
assert map.put(alias.toUpperCase(), t) == null;
}
}
name2type = Collections.unmodifiableMap(map);
}

private final JDBCType jdbcType;
private final Class<?> javaClass;
private final boolean signed;
Expand All @@ -85,8 +125,7 @@ public enum ClickHouseDataType {

ClickHouseDataType(JDBCType jdbcType, Class<?> javaClass,
boolean signed, int defaultPrecision, int defaultScale,
String... aliases)
{
String... aliases) {
this.jdbcType = jdbcType;
this.javaClass = javaClass;
this.signed = signed;
Expand Down Expand Up @@ -120,27 +159,10 @@ public int getDefaultScale() {
}

public static ClickHouseDataType fromTypeString(String typeString) {
String s = typeString.trim();
for (ClickHouseDataType dataType : values()) {
if (s.equalsIgnoreCase(dataType.name())) {
return dataType;
}
for (String alias : dataType.aliases) {
if (s.equalsIgnoreCase(alias)) {
return dataType;
}
}
}
return ClickHouseDataType.Unknown;
return name2type.getOrDefault(typeString.trim().toUpperCase(), ClickHouseDataType.Unknown);
}

public static ClickHouseDataType resolveDefaultArrayDataType(String typeName) {
for (ClickHouseDataType chDataType : values()) {
if (chDataType.name().equals(typeName)) {
return chDataType;
}
}
return ClickHouseDataType.String;
return name2type.getOrDefault(typeName, ClickHouseDataType.String);
}

}
Loading