⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,8 @@ private String encodeObject(Object x, Long length) throws SQLException {
return "fromUnixTimestamp64Nano(" + (((Instant) x).getEpochSecond() * 1_000_000_000L + ((Instant) x).getNano()) + ")";
} else if (x instanceof InetAddress) {
return QUOTE + ((InetAddress) x).getHostAddress() + QUOTE;
} else if (x instanceof byte[]) {
return JdbcUtils.convertToUnhexExpression((byte[]) x);
} else if (x instanceof java.sql.Array) {
com.clickhouse.jdbc.types.Array array = (com.clickhouse.jdbc.types.Array) x;
int nestedLevel = Math.max(1, array.getNestedLevel());
Expand Down
27 changes: 14 additions & 13 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,19 @@ public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException

@Override
public byte[] getBytes(int columnIndex) throws SQLException {
return getBytes(columnIndexToName(columnIndex));
checkClosed();
try {
if (reader.hasValue(columnIndex)) {
wasNull = false;
return reader.getByteArray(columnIndex);
} else {
wasNull = true;
return null;
}
} catch (Exception e) {
throw ExceptionUtils.toSqlState(String.format("Method: getBytes(\"%d\") encountered an exception.", columnIndex),
String.format("SQL: [%s]", parentStatement.getLastStatementSql()), e);
}
}

@Override
Expand Down Expand Up @@ -406,18 +418,7 @@ public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLExcepti

@Override
public byte[] getBytes(String columnLabel) throws SQLException {
checkClosed();
try {
if (reader.hasValue(columnLabel)) {
wasNull = false;
return reader.getByteArray(columnLabel);
} else {
wasNull = true;
return null;
}
} catch (Exception e) {
throw ExceptionUtils.toSqlState(String.format("Method: getBytes(\"%s\") encountered an exception.", columnLabel), String.format("SQL: [%s]", parentStatement.getLastStatementSql()), e);
}
return getBytes(getSchema().nameToColumnIndex(columnLabel));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.sql.Date;
import java.sql.JDBCType;
import java.sql.SQLException;
Expand Down Expand Up @@ -569,4 +570,66 @@ public static Object[] arrayToObjectArray(Object array) {
}
throw new IllegalArgumentException("Cannot convert " + array.getClass().getName() + " to an Object[]");
}

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 = "()";

private static final byte[] UNHEX_PREFIX = "unhex('".getBytes(StandardCharsets.US_ASCII);
private static final byte[] UNHEX_SUFFIX = "')".getBytes(StandardCharsets.US_ASCII);
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);


/**
* 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.US_ASCII);
}

/**
* Decodes a hex string into a byte array.
* Each pair of characters in the input is interpreted as a hexadecimal byte value.
*
* @param hexString hex-encoded string (must have even length)
* @return decoded byte array, or empty array if input is null or empty
* @throws IllegalArgumentException if the string has odd length or contains non-hex characters
*/
public static byte[] decodeHexString(String hexString) {
if (hexString == null || hexString.isEmpty()) {
return new byte[0];
}
int len = hexString.length();
if (len % 2 != 0) {
throw new IllegalArgumentException("Hex string must have even length, got " + len);
}
byte[] result = new byte[len / 2];
for (int i = 0; i < result.length; i++) {
int hi = Character.digit(hexString.charAt(i * 2), 16);
int lo = Character.digit(hexString.charAt(i * 2 + 1), 16);
if (hi == -1 || lo == -1) {
throw new IllegalArgumentException("Invalid hex character at index " + (hi == -1 ? i * 2 : i * 2 + 1));
}
result[i] = (byte) ((hi << 4) | lo);
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,9 @@ public void testIpAddressTypes() throws SQLException, UnknownHostException {
assertEquals(rs.getString("ipv6"), ipv6Address.getHostAddress());
assertEquals(rs.getObject("ipv4_as_ipv6"), ipv4AsIpv6);
assertEquals(rs.getObject("ipv4_as_ipv6", Inet4Address.class), ipv4AsIpv6);
assertEquals(rs.getBytes("ipv4_ip"), ipv4AddressByIp.getAddress());
assertEquals(rs.getBytes("ipv6"), ipv6Address.getAddress());

assertFalse(rs.next());
}
}
Expand Down Expand Up @@ -1175,6 +1178,34 @@ public void testArrayTypes() throws SQLException {
}
}

@Test(groups = { "integration" })
public void testStringsUsedAsBytes() throws Exception {
runQuery("CREATE TABLE test_strings_as_bytes (order Int8, str String, fixed FixedString(10)) ENGINE = MergeTree ORDER BY ()");

String[][] testData = {{"Hello, World!", "FixedStr"}, {"Test String 123", "ABC"}};

try (Connection conn = getJdbcConnection();
PreparedStatement insert = conn.prepareStatement("INSERT INTO test_strings_as_bytes VALUES (?, ?, ?)")) {
for (int i = 0; i < testData.length; i++) {
insert.setInt(1, i + 1);
insert.setBytes(2, testData[i][0].getBytes("UTF-8"));
insert.setBytes(3, testData[i][1].getBytes("UTF-8"));
insert.executeUpdate();
}
}

try (Connection conn = getJdbcConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM test_strings_as_bytes ORDER BY order")) {
for (String[] expected : testData) {
assertTrue(rs.next());
assertEquals(new String(rs.getBytes("str"), "UTF-8"), expected[0]);
assertEquals(new String(rs.getBytes("fixed"), "UTF-8").replace("\0", ""), expected[1]);
}
assertFalse(rs.next());
}
}

@Test(groups = { "integration" })
public void testNestedArrays() throws Exception {
try (Connection conn = getJdbcConnection()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,11 @@ public void testSetString() throws Exception {

@Test(groups = { "integration" })
public void testSetBytes() throws Exception {
// see com.clickhouse.jdbc.JdbcDataTypeTests.testStringsUsedAsBytes
// setBytes is dedicated for binary strings (see spec).
// arrays are set via Array object.
try (Connection conn = getJdbcConnection()) {
try (PreparedStatement stmt = conn.prepareStatement("SELECT ?::Array(Int8)")) {
try (PreparedStatement stmt = conn.prepareStatement("SELECT ?")) {
stmt.setBytes(1, new byte[] { 1, 2, 3 });
try (ResultSet rs = stmt.executeQuery()) {
assertTrue(rs.next());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
import org.testng.Assert;
import org.testng.annotations.*;

import java.io.InputStream;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.List;
import java.util.zip.CRC32;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
Expand Down Expand Up @@ -109,4 +111,35 @@ public void testAllDataTypesMapped() {
Assert.assertNotNull(JdbcUtils.convertToJavaClass(dt), "Data type " + dt + " has no mapping to java class");
}
}

@Test(groups = {"unit"})
public void testConvertToUnhexExpression() throws Exception {
// Load binary file from test resources
byte[] originalBytes;
try (InputStream is = getClass().getResourceAsStream("/ch_logo.png")) {
Assert.assertNotNull(is, "ch_logo.png not found in test resources");
originalBytes = is.readAllBytes();
}

// Calculate checksum of original bytes
CRC32 originalChecksum = new CRC32();
originalChecksum.update(originalBytes);
long expectedChecksum = originalChecksum.getValue();

// Convert to unhex expression
String unhexExpr = JdbcUtils.convertToUnhexExpression(originalBytes);
Assert.assertTrue(unhexExpr.startsWith("unhex('"), "Expression should start with unhex('");
Assert.assertTrue(unhexExpr.endsWith("')"), "Expression should end with ')");

// Extract hex string and decode back to bytes
String hexString = unhexExpr.substring("unhex('".length(), unhexExpr.length() - "')".length());
assertEquals(hexString.length(), originalBytes.length * 2, "Hex string length should be twice the byte array length");

byte[] decodedBytes = JdbcUtils.decodeHexString(hexString);

// Verify checksum of decoded bytes matches original
CRC32 decodedChecksum = new CRC32();
decodedChecksum.update(decodedBytes);
assertEquals(decodedChecksum.getValue(), expectedChecksum, "Checksum of decoded bytes should match original");
}
}
Binary file added jdbc-v2/src/test/resources/ch_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions performance/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<slf4j.version>2.0.17</slf4j.version>
<ch.jdbc.revision>0.9.6-SNAPSHOT</ch.jdbc.revision>
<jmh.version>1.37</jmh.version>
<testcontainers.version>1.20.6</testcontainers.version>
<testcontainers.version>2.0.2</testcontainers.version>

<antrun-plugin.version>3.1.0</antrun-plugin.version>
<assembly-plugin.version>3.6.0</assembly-plugin.version>
Expand Down Expand Up @@ -82,7 +82,7 @@
<!-- https://mvnrepository.com/artifact/org.testcontainers/clickhouse -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>clickhouse</artifactId>
<artifactId>testcontainers-clickhouse</artifactId>
<version>${testcontainers.version}</version>
</dependency>

Expand Down
Loading