Skip to content

Commit

Permalink
Support more data types
Browse files Browse the repository at this point in the history
  • Loading branch information
zhicwu committed Mar 5, 2021
1 parent b5e6c53 commit 6f69853
Show file tree
Hide file tree
Showing 18 changed files with 974 additions and 113 deletions.
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 @@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public final class ClickHouseColumnInfo {
private TimeZone timeZone;
private int precision;
private int scale;
private ClickHouseColumnInfo keyInfo;
private ClickHouseColumnInfo valueInfo;

public static ClickHouseColumnInfo parse(String typeInfo, String columnName) {
ClickHouseColumnInfo column = new ClickHouseColumnInfo(typeInfo, columnName);
Expand Down Expand Up @@ -61,14 +63,33 @@ public static ClickHouseColumnInfo parse(String typeInfo, String columnName) {

switch (dataType) {
case DateTime :
String[] argsTZ = splitArgs(typeInfo, currIdx);
if (argsTZ.length == 1) {
String[] argsDT = splitArgs(typeInfo, currIdx);
if (argsDT.length == 2) { // same as DateTime64
column.scale = Integer.parseInt(argsDT[0]);
column.timeZone = TimeZone.getTimeZone(argsDT[1].replace("'", ""));
} else if (argsDT.length == 1) { // same as DateTime32
// unfortunately this will fall back to GMT if the time zone
// cannot be resolved
TimeZone tz = TimeZone.getTimeZone(argsTZ[0].replace("'", ""));
TimeZone tz = TimeZone.getTimeZone(argsDT[0].replace("'", ""));
column.timeZone = tz;
}
break;
case DateTime32:
String[] argsD32 = splitArgs(typeInfo, currIdx);
if (argsD32.length == 1) {
// unfortunately this will fall back to GMT if the time zone
// cannot be resolved
TimeZone tz = TimeZone.getTimeZone(argsD32[0].replace("'", ""));
column.timeZone = tz;
}
break;
case DateTime64:
String[] argsD64 = splitArgs(typeInfo, currIdx);
if (argsD64.length == 2) {
column.scale = Integer.parseInt(argsD64[0]);
column.timeZone = TimeZone.getTimeZone(argsD64[1].replace("'", ""));
}
break;
case Decimal :
String[] argsDecimal = splitArgs(typeInfo, currIdx);
if (argsDecimal.length == 2) {
Expand All @@ -79,13 +100,21 @@ public static ClickHouseColumnInfo parse(String typeInfo, String columnName) {
case Decimal32 :
case Decimal64 :
case Decimal128 :
case Decimal256 :
String[] argsScale = splitArgs(typeInfo, currIdx);
column.scale = Integer.parseInt(argsScale[0]);
break;
case FixedString :
String[] argsPrecision = splitArgs(typeInfo, currIdx);
column.precision = Integer.parseInt(argsPrecision[0]);
break;
case Map:
String[] argsMap = splitArgs(typeInfo, currIdx);
if (argsMap.length == 2) {
column.keyInfo = ClickHouseColumnInfo.parse(argsMap[0], columnName + "Key");
column.valueInfo = ClickHouseColumnInfo.parse(argsMap[1], columnName + "Value");
}
break;
default :
break;
}
Expand Down Expand Up @@ -184,4 +213,11 @@ public int getScale() {
return scale;
}

public ClickHouseColumnInfo getKeyInfo() {
return this.keyInfo;
}

public ClickHouseColumnInfo getValueInfo() {
return this.valueInfo;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -537,8 +537,7 @@ public Object getObject(int columnIndex) throws SQLException {
return null;
}
ClickHouseDataType chType = getColumnInfo(columnIndex).getClickHouseDataType();
int type = chType.getSqlType();
switch (type) {
switch (chType.getSqlType()) {
case Types.BIGINT:
if (chType == ClickHouseDataType.UInt64) {
return getObject(columnIndex, BigInteger.class);
Expand All @@ -561,12 +560,16 @@ public Object getObject(int columnIndex) throws SQLException {
case Types.BLOB: return getString(columnIndex);
case Types.ARRAY: return getArray(columnIndex);
case Types.DECIMAL: return getBigDecimal(columnIndex);
case Types.NUMERIC: return getBigInteger(columnIndex);
default:
// do not return
}
switch (chType) {
// case Array:
// case Tuple:
case Map:
case UUID :
return getObject(columnIndex, UUID.class);
return getObject(columnIndex, chType.getJavaClass());
default :
return getString(columnIndex);
}
Expand Down Expand Up @@ -718,6 +721,15 @@ public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException
: null;
}

public BigInteger getBigInteger(String columnLabel) throws SQLException {
return getBigInteger(findColumn(columnLabel));
}

public BigInteger getBigInteger(int columnIndex) throws SQLException {
BigDecimal dec = getBigDecimal(columnIndex);
return dec == null ? null : dec.toBigInteger();
}

public String[] getColumnNames() {
String[] columnNames = new String[columns.size()];
for (int i = 0; i < columns.size(); ++i) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package ru.yandex.clickhouse.response.parser;

import java.sql.Array;
import java.sql.SQLException;
import java.util.TimeZone;

import ru.yandex.clickhouse.ClickHouseArray;
import ru.yandex.clickhouse.domain.ClickHouseDataType;
import ru.yandex.clickhouse.response.ByteFragment;
import ru.yandex.clickhouse.response.ClickHouseColumnInfo;
import ru.yandex.clickhouse.util.ClickHouseArrayUtil;

final class ClickHouseArrayParser extends ClickHouseValueParser<Array> {

private static ClickHouseArrayParser instance;

static ClickHouseArrayParser getInstance() {
if (instance == null) {
instance = new ClickHouseArrayParser();
}
return instance;
}

private ClickHouseArrayParser() {
// prevent instantiation
}

@Override
public Array parse(ByteFragment value, ClickHouseColumnInfo columnInfo, TimeZone resultTimeZone)
throws SQLException {
if (columnInfo.getClickHouseDataType() != ClickHouseDataType.Array) {
throw new SQLException("Column not an array");
}

if (value.isNull()) {
return null;
}

final Object array;
switch (columnInfo.getArrayBaseType()) {
case Date:
// FIXME: properties.isUseObjectsInArrays()
array = ClickHouseArrayUtil.parseArray(value, false, resultTimeZone, columnInfo);
break;
default:
// properties.isUseObjectsInArrays()
TimeZone timeZone = columnInfo.getTimeZone() != null ? columnInfo.getTimeZone() : resultTimeZone;
array = ClickHouseArrayUtil.parseArray(value, false, timeZone, columnInfo);
break;
}

return new ClickHouseArray(columnInfo.getArrayBaseType(), array);
}

@Override
protected Array getDefaultValue() {
return null;
}
}
Loading

0 comments on commit 6f69853

Please sign in to comment.