com.clickhouse.data.ClickHouseValues Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of clickhouse-data Show documentation
Show all versions of clickhouse-data Show documentation
Data processing utilities for ClickHouse
package com.clickhouse.data;
import java.io.IOException;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;
import java.util.Map.Entry;
import com.clickhouse.data.value.ClickHouseArrayValue;
import com.clickhouse.data.value.ClickHouseByteValue;
import com.clickhouse.data.value.ClickHouseIpv4Value;
import com.clickhouse.data.value.ClickHouseIpv6Value;
/**
* Help class for dealing with values.
*/
public final class ClickHouseValues {
public static final BigInteger BIGINT_HL_BOUNDARY = BigInteger.ONE.shiftLeft(64); // 2^64
public static final BigInteger BIGINT_SL_BOUNDARY = BigInteger.valueOf(Long.MAX_VALUE);
public static final LocalDate DATE_ZERO = LocalDate.ofEpochDay(0L);
public static final LocalDateTime DATETIME_ZERO = LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC);
public static final LocalTime TIME_ZERO = LocalTime.ofSecondOfDay(0L);
public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
public static final Object[][] EMPTY_OBJECT_ARRAY2 = new Object[0][];
public static final boolean[] EMPTY_BOOL_ARRAY = new boolean[0];
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
public static final short[] EMPTY_SHORT_ARRAY = new short[0];
public static final int[] EMPTY_INT_ARRAY = new int[0];
public static final long[] EMPTY_LONG_ARRAY = new long[0];
public static final float[] EMPTY_FLOAT_ARRAY = new float[0];
public static final double[] EMPTY_DOUBLE_ARRAY = new double[0];
public static final ClickHouseValue[] EMPTY_VALUES = new ClickHouseValue[0];
public static final String EMPTY_ARRAY_EXPR = "[]";
public static final String EMPTY_MAP_EXPR = "{}";
public static final String EMPTY_STRING_EXPR = "''";
public static final String EMPTY_TUPLE_EXPR = "()";
public static final BigDecimal NANOS = new BigDecimal(BigInteger.TEN.pow(9));
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static final DateTimeFormatter TIME_FORMATTER = new DateTimeFormatterBuilder().appendPattern("HH:mm:ss")
.appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true).toFormatter();
public static final DateTimeFormatter DATETIME_FORMATTER = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd HH:mm:ss").appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true).toFormatter();
public static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC");
public static final TimeZone SYS_TIMEZONE = TimeZone.getDefault();
public static final ZoneId UTC_ZONE = UTC_TIMEZONE.toZoneId();
public static final ZoneId SYS_ZONE = SYS_TIMEZONE.toZoneId();
public static final String NULL_EXPR = "NULL";
public static final String NAN_EXPR = "NaN";
public static final String INF_EXPR = "Inf";
public static final String NINF_EXPR = "-Inf";
public static final String ERROR_INF_OR_NAN = "Infinite or NaN";
public static final String ERROR_INVALID_POINT = "A point should have two and only two double values, but we got: ";
public static final String ERROR_SINGLETON_ARRAY = "Only singleton array is allowed, but we got: ";
public static final String ERROR_SINGLETON_COLLECTION = "Only singleton collection is allowed, but we got: ";
public static final String ERROR_SINGLETON_MAP = "Only singleton map is allowed, but we got: ";
public static final String PARAM_PRECISION = "precision";
public static final String PARAM_SCALE = "scale";
public static final String TYPE_BOOLEAN = "boolean";
public static final String TYPE_CHAR = "char";
public static final String TYPE_BYTE = "byte";
public static final String TYPE_SHORT = "short";
public static final String TYPE_INT = "int";
public static final String TYPE_LONG = "long";
public static final String TYPE_FLOAT = "float";
public static final String TYPE_DOUBLE = "double";
public static final String TYPE_BIG_DECIMAL = "BigDecimal";
public static final String TYPE_BIG_INTEGER = "BigInteger";
public static final String TYPE_DATE = "Date";
public static final String TYPE_TIME = "Time";
public static final String TYPE_DATE_TIME = "DateTime";
public static final String TYPE_ENUM = "Enum";
public static final String TYPE_IPV4 = "Inet4Address";
public static final String TYPE_IPV6 = "Inet6Address";
public static final String TYPE_STRING = "String";
public static final String TYPE_UUID = "UUID";
public static final String TYPE_OBJECT = "Object";
public static final String TYPE_ARRAY = "Array";
public static final String TYPE_MAP = "Map";
public static final String TYPE_NESTED = "Nested";
public static final String TYPE_TUPLE = "Tuple";
public static final String TYPE_POINT = "Point";
public static final String TYPE_RING = "Ring";
public static final String TYPE_POLYGON = "Polygon";
public static final String TYPE_MULTI_POLYGON = "MultiPolygon";
public static final String TYPE_CLASS = "Class";
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
private static final byte[] UNHEX_PREFIX = "unhex('".getBytes(StandardCharsets.US_ASCII);
private static final byte[] UNHEX_SUFFIX = "')".getBytes(StandardCharsets.US_ASCII);
/**
* Converts IP address to big integer.
*
* @param value IP address
* @return big integer
*/
public static BigInteger convertToBigInteger(Inet4Address value) {
return value == null ? null : new BigInteger(1, value.getAddress());
}
/**
* Converts IP address to big integer.
*
* @param value IP address
* @return big integer
*/
public static BigInteger convertToBigInteger(Inet6Address value) {
return value == null ? null : new BigInteger(1, value.getAddress());
}
/**
* Converts UUID to big integer.
*
* @param value UUID
* @return big integer
*/
public static BigInteger convertToBigInteger(UUID value) {
if (value == null) {
return null;
}
BigInteger high = BigInteger.valueOf(value.getMostSignificantBits());
BigInteger low = BigInteger.valueOf(value.getLeastSignificantBits());
if (high.signum() < 0) {
high = high.add(BIGINT_HL_BOUNDARY);
}
if (low.signum() < 0) {
low = low.add(BIGINT_HL_BOUNDARY);
}
return low.add(high.multiply(BIGINT_HL_BOUNDARY));
}
/**
* Converts given character to boolean value.
*
* @param value character represents a boolean value
* @return boolean value
*/
public static boolean convertToBoolean(char value) {
// not going to support X/V, ✓/✗ and 是/否 as they're less common
if (value == '1' || value == 'T' || value == 't' || value == 'Y' || value == 'y') {
return true;
} else if (value == '0' || value == 'F' || value == 'f' || value == 'N' || value == 'n') {
return false;
} else {
throw new IllegalArgumentException("Invalid boolean value, please use 1/0, T/F, Y/N");
}
}
/**
* Converts given string to boolean value.
*
* @param value string represents a boolean value
* @return boolean value
*/
public static boolean convertToBoolean(String value) {
if (value == null || value.isEmpty() || "0".equals(value) || "false".equalsIgnoreCase(value)
|| "no".equalsIgnoreCase(value)) {
return false;
} else if ("1".equals(value) || "true".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value)) {
return true;
} else {
throw new IllegalArgumentException("Invalid boolean value, please use 1/0, true/false, yes/no");
}
}
/**
* Converts given byte array to string in hexadecimal format.
*
* @param bytes byte array
* @return non-null string
*/
public static String convertToHexString(byte[] bytes) {
int len = bytes != null ? bytes.length : 0;
if (len == 0) {
return "";
}
byte[] hexChars = new byte[len * 2];
for (int i = 0; i < len; i++) {
int v = bytes[i] & 0xFF;
int j = i * 2;
hexChars[j] = HEX_ARRAY[v >>> 4];
hexChars[j + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars, StandardCharsets.UTF_8);
}
/**
* Converts given byte array to unhex() expression.
*
* @param bytes byte array
* @return non-null expression
*/
public static String convertToUnhexExpression(byte[] bytes) {
int len = bytes != null ? bytes.length : 0;
if (len == 0) {
return EMPTY_STRING_EXPR;
}
int offset = UNHEX_PREFIX.length;
byte[] hexChars = new byte[len * 2 + offset + UNHEX_SUFFIX.length];
System.arraycopy(UNHEX_PREFIX, 0, hexChars, 0, offset);
System.arraycopy(UNHEX_SUFFIX, 0, hexChars, hexChars.length - UNHEX_SUFFIX.length, UNHEX_SUFFIX.length);
for (int i = 0; i < len; i++) {
int v = bytes[i] & 0xFF;
hexChars[offset++] = HEX_ARRAY[v >>> 4];
hexChars[offset++] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars, StandardCharsets.UTF_8);
}
/**
* Converts big decimal to instant.
*
* @param value big decimal
* @return instant
*/
public static Instant convertToInstant(BigDecimal value) {
if (value == null) {
return null;
} else if (value.scale() == 0) {
return Instant.ofEpochSecond(value.longValue());
} else if (value.signum() >= 0) {
return Instant.ofEpochSecond(value.longValue(),
value.remainder(BigDecimal.ONE).multiply(NANOS).intValue());
}
long v = NANOS.add(value.remainder(BigDecimal.ONE).multiply(NANOS)).longValue();
int nanoSeconds = v < 1000000000L ? (int) v : 0;
return Instant.ofEpochSecond(value.longValue() - (nanoSeconds > 0 ? 1 : 0), nanoSeconds);
}
/**
* Converts big decimal to date time.
*
* @param value big decimal
* @return date time
*/
public static LocalDateTime convertToDateTime(BigDecimal value) {
return value != null ? LocalDateTime.ofInstant(convertToInstant(value), ZoneOffset.UTC) : null;
}
/**
* Converts big decimal to date time.
*
* @param value big decimal
* @param tz time zone, null is treated as UTC
* @return date time
*/
public static ZonedDateTime convertToDateTime(BigDecimal value, TimeZone tz) {
if (value == null) {
return null;
}
return convertToDateTime(value, tz != null ? tz.toZoneId() : UTC_ZONE);
}
/**
* Converts big decimal to date time.
*
* @param value big decimal
* @param zone zone id, null is treated as UTC
* @return date time
*/
public static ZonedDateTime convertToDateTime(BigDecimal value, ZoneId zone) {
if (value == null) {
return null;
}
return convertToInstant(value).atZone(zone != null ? zone : UTC_ZONE);
}
/**
* Converts big decimal to date time.
*
* @param value big decimal
* @param offset zone offset, null is treated as {@code ZoneOffset.UTC}
* @return date time
*/
public static OffsetDateTime convertToDateTime(BigDecimal value, ZoneOffset offset) {
if (value == null) {
return null;
}
return convertToInstant(value).atOffset(offset != null ? offset : ZoneOffset.UTC);
}
/**
* Converts IPv6 address to IPv4 address if applicable.
*
* @param value IPv6 address
* @return IPv4 address
* @throws IllegalArgumentException when failed to convert to IPv4 address
*/
public static Inet4Address convertToIpv4(Inet6Address value) {
if (value == null) {
return null;
}
byte[] bytes = value.getAddress();
boolean invalid = false;
for (int i = 0; i < 10; i++) {
if (bytes[i] != (byte) 0) {
invalid = true;
break;
}
}
if (!invalid) {
invalid = bytes[10] != 0xFF || bytes[11] != 0xFF;
}
if (invalid) {
throw new IllegalArgumentException("Failed to convert IPv6 to IPv4");
}
byte[] addr = new byte[4];
System.arraycopy(bytes, 12, addr, 0, 4);
try {
return (Inet4Address) InetAddress.getByAddress(addr);
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Converts integer to IPv4 address.
*
* @param value integer
* @return IPv4 address
*/
public static Inet4Address convertToIpv4(int value) {
byte[] bytes = new byte[] { (byte) ((value >> 24) & 0xFF), (byte) ((value >> 16) & 0xFF),
(byte) ((value >> 8) & 0xFF), (byte) (value & 0xFF) };
try {
return (Inet4Address) InetAddress.getByAddress(bytes);
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Converts string to IPv4 address.
*
* @param value string
* @return IPv4 address
* @throws IllegalArgumentException when failed to convert to IPv4 address
*/
public static Inet4Address convertToIpv4(String value) {
if (value == null) {
return null;
} else if (value.isEmpty()) {
return ClickHouseIpv4Value.DEFAULT;
}
try {
for (InetAddress addr : InetAddress.getAllByName(value)) {
if (addr instanceof Inet4Address) {
return (Inet4Address) addr;
} else if (addr instanceof Inet6Address) {
return convertToIpv4((Inet6Address) addr);
}
}
} catch (UnknownHostException e) {
throw new IllegalArgumentException(ClickHouseUtils.format("Failed to convert [%s] to Inet4Address", value),
e);
}
throw new IllegalArgumentException(ClickHouseUtils.format("No Inet4Address for [%s]", value));
}
/**
* Converts big integer to IPv6 address.
*
* @param value big integer
* @return IPv6 address
* @throws IllegalArgumentException when failed to convert to IPv6 address
*/
public static Inet6Address convertToIpv6(BigInteger value) {
if (value == null) {
return null;
}
byte[] bytes = ClickHouseChecker.nonNull(value, "value").toByteArray();
int len = bytes.length;
if (len > 16) {
throw new IllegalArgumentException("The number is too large to be converted to IPv6: " + value);
} else if (len < 16) {
byte[] addr = new byte[16];
int diff = 16 - len;
for (int i = len - 1; i >= 0; i--) {
addr[i + diff] = bytes[i];
}
bytes = addr;
}
try {
return Inet6Address.getByAddress(null, bytes, null);
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Converts IPv4 address to IPv6 address.
*
* @param value IPv4 address
* @return IPv6 address
* @throws IllegalArgumentException when failed to convert to IPv6 address
*/
public static Inet6Address convertToIpv6(Inet4Address value) {
if (value == null) {
return null;
}
// https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses
byte[] bytes = new byte[16];
bytes[10] = (byte) 0xFF;
bytes[11] = (byte) 0xFF;
System.arraycopy(value.getAddress(), 0, bytes, 12, 4);
try {
return Inet6Address.getByAddress(null, bytes, null);
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Converts string to IPv6 address.
*
* @param value string
* @return IPv6 address
* @throws IllegalArgumentException when failed to convert to IPv6 address
*/
public static Inet6Address convertToIpv6(String value) {
if (value == null) {
return null;
} else if (value.isEmpty()) {
return ClickHouseIpv6Value.DEFAULT;
}
try {
for (InetAddress addr : InetAddress.getAllByName(value)) {
if (addr instanceof Inet6Address) {
return (Inet6Address) addr;
} else if (addr instanceof Inet4Address) {
return convertToIpv6((Inet4Address) addr);
}
}
} catch (UnknownHostException e) {
throw new IllegalArgumentException(ClickHouseUtils.format("Failed to convert [%s] to Inet6Address", value),
e);
}
throw new IllegalArgumentException(ClickHouseUtils.format("No Inet6Address for [%s]", value));
}
/**
* Converts abitrary object to an expression that can be used in SQL query.
*
* @param value value may or may not be null
* @return escaped SQL expression
*/
@SuppressWarnings({ "unchecked", "squid:S3776" })
public static String convertToSqlExpression(Object value) {
if (value == null) {
return NULL_EXPR;
}
String s;
if (value instanceof String) {
s = convertToQuotedString(value);
} else if (value instanceof ClickHouseValue) {
s = ((ClickHouseValue) value).toSqlExpression();
} else if (value instanceof UUID) {
s = new StringBuilder().append('\'').append(value).append('\'').toString();
} else if (value instanceof LocalDate) {
s = new StringBuilder().append('\'').append(((LocalDate) value).format(DATE_FORMATTER)).append('\'')
.toString();
} else if (value instanceof LocalTime) { // currently not supported in ClickHouse
s = new StringBuilder().append('\'').append(((LocalTime) value).format(TIME_FORMATTER)).append('\'')
.toString();
} else if (value instanceof LocalDateTime) {
s = new StringBuilder().append('\'').append(((LocalDateTime) value).format(DATETIME_FORMATTER)).append('\'')
.toString();
} else if (value instanceof OffsetDateTime) {
s = new StringBuilder().append('\'').append(((OffsetDateTime) value).format(DATETIME_FORMATTER))
.append('\'').toString();
} else if (value instanceof ZonedDateTime) {
s = new StringBuilder().append('\'').append(((ZonedDateTime) value).format(DATETIME_FORMATTER)).append('\'')
.toString();
} else if (value instanceof InetAddress) {
s = new StringBuilder().append('\'').append(((InetAddress) value).getHostAddress()).append('\'').toString();
} else if (value instanceof Enum) {
s = String.valueOf(((Enum>) value).ordinal()); // faster than escaped name
} else if (value instanceof Object[]) { // array & nested
StringBuilder builder = new StringBuilder().append('[');
for (Object o : (Object[]) value) {
builder.append(convertToSqlExpression(o)).append(',');
}
if (builder.length() > 1) {
builder.setLength(builder.length() - 1);
}
s = builder.append(']').toString();
} else if (value instanceof Collection) { // treat as tuple
StringBuilder builder = new StringBuilder().append('(');
for (Object v : (Collection