diff --git a/src/main/java/ru/yandex/metrika/clickhouse/CHStatement.java b/src/main/java/ru/yandex/metrika/clickhouse/CHStatement.java index 482507302d0b86eefaf2bb935ed15f40d1eb9a6b..43397fa22c62d38262b92c390db24b4c9623b755 100644 --- a/src/main/java/ru/yandex/metrika/clickhouse/CHStatement.java +++ b/src/main/java/ru/yandex/metrika/clickhouse/CHStatement.java @@ -43,6 +43,7 @@ public class CHStatement implements Statement { @Override public ResultSet executeQuery(String sql) throws SQLException { + log.debug("Ex: " + sql); InputStream is = getInputStream(sql, null, false); try { return new CHResultSet(properties.isCompress() diff --git a/src/main/java/ru/yandex/metrika/clickhouse/copypaste/ByteFragment.java b/src/main/java/ru/yandex/metrika/clickhouse/copypaste/ByteFragment.java index 28426c4512380ded4d6e6d685c5d3c60a8ba26d9..bb911f401eb66207de6c0193cd4b2a46a1c75d49 100644 --- a/src/main/java/ru/yandex/metrika/clickhouse/copypaste/ByteFragment.java +++ b/src/main/java/ru/yandex/metrika/clickhouse/copypaste/ByteFragment.java @@ -31,12 +31,18 @@ public class ByteFragment { public String asString(boolean unescape) { if(unescape) { + if (isNull()) return null; return new String(unescape(), CopypasteUtils.UTF_8); } else { return asString(); } } + public boolean isNull() { + // \N + return len == 2 && buf[start] == '\\' && buf[start + 1] == 'N'; + } + @Override public String toString() { return "ByteFragment{" + @@ -95,6 +101,8 @@ public class ByteFragment { // "\b" => 8 // "\f" => 12 // "\t" => 9 + //null + // "\N" => 0 private static final byte[] convert = { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0.. 9 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, //10..19 @@ -103,7 +111,7 @@ public class ByteFragment { -1,-1,-1,-1,-1,-1,-1,-1, 0,-1, //40..49 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, //50..59 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, //60..69 - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, //70..79 + -1,-1,-1,-1,-1,-1,-1,-1, 0,-1, //70..79 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, //80..89 -1,-1,92,-1,-1,-1,-1,-1, 8,-1, //90..99 -1,-1,12,-1,-1,-1,-1,-1,-1,-1, //100..109 diff --git a/src/main/java/ru/yandex/metrika/clickhouse/copypaste/ByteFragmentUtils.java b/src/main/java/ru/yandex/metrika/clickhouse/copypaste/ByteFragmentUtils.java index 705c8de077b5176b93e4cdac39548a1897f58ade..11d802bf5fc3d6a5f80e318cf0366d2ceebe3ecf 100644 --- a/src/main/java/ru/yandex/metrika/clickhouse/copypaste/ByteFragmentUtils.java +++ b/src/main/java/ru/yandex/metrika/clickhouse/copypaste/ByteFragmentUtils.java @@ -15,6 +15,10 @@ public final class ByteFragmentUtils { throw new NumberFormatException("null"); } + if (s.isNull()) { + return 0; //jdbc spec + } + int result = 0; boolean negative = false; int i = 0, max = s.length(); @@ -74,6 +78,10 @@ public final class ByteFragmentUtils { throw new NumberFormatException("null"); } + if (s.isNull()) { + return 0; //jdbc spec + } + long result = 0; boolean negative = false; int i = 0, max = s.length(); diff --git a/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultBuilder.java b/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultBuilder.java index c50a408813ac4dbb68e250cea156a73a16d5ba4c..b3588b9fadb890e218389f4f4117c2626874dbad 100644 --- a/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultBuilder.java +++ b/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultBuilder.java @@ -85,7 +85,12 @@ public class CHResultBuilder { } private void appendObject(Object o, ByteArrayOutputStream baos) throws IOException { - ByteFragment.escape(o.toString().getBytes(), baos); + if (o == null) { + baos.write('\\'); + baos.write('N'); + } else { + ByteFragment.escape(o.toString().getBytes(), baos); + } } } diff --git a/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultSet.java b/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultSet.java index 42b0a5a006731265873878ae2f778d1a134e708b..2f7ef8046a6d9aa6ece1bda71392746ad36f7292 100644 --- a/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultSet.java +++ b/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultSet.java @@ -32,6 +32,9 @@ public class CHResultSet extends AbstractResultSet { // current line private ByteFragment[] values; + // 1-based + private int lastReadColumn; + // next line private ByteFragment nextLine; @@ -125,8 +128,8 @@ public class CHResultSet extends AbstractResultSet { @Override public boolean wasNull() throws SQLException { - // no nulls in clickhouse - return false; + if (lastReadColumn == 0) throw new IllegalStateException("You should get something before check nullability"); + return values[lastReadColumn - 1].isNull(); } @Override @@ -212,6 +215,7 @@ public class CHResultSet extends AbstractResultSet { @Override public Timestamp getTimestamp(int columnIndex) throws SQLException { + if (values[columnIndex - 1].isNull()) return null; return new Timestamp(getTimestampAsLong(columnIndex)); } @@ -237,14 +241,17 @@ public class CHResultSet extends AbstractResultSet { } private static short toShort(ByteFragment value) { + if (value.isNull()) return 0; return Short.parseShort(value.asString()); } private static boolean toBoolean(ByteFragment value) { + if (value.isNull()) return false; return "1".equals(value.asString()); //вроде бы там 1/0 } private static byte[] toBytes(ByteFragment value) { + if (value.isNull()) return null; return value.unescape(); } @@ -253,6 +260,7 @@ public class CHResultSet extends AbstractResultSet { } private static long[] toLongArray(ByteFragment value) { + if (value.isNull()) return null; if (value.charAt(0) != '[' || value.charAt(value.length()-1) != ']') { throw new IllegalArgumentException("not an array: "+value); } @@ -266,6 +274,7 @@ public class CHResultSet extends AbstractResultSet { } private static long toTimestamp(ByteFragment value) { + if (value.isNull()) return 0; try { return sdf.parse(value.asString()).getTime(); } catch (ParseException e) { diff --git a/src/test/java/ru/yandex/metrika/clickhouse/copypaste/CHResultBuilderTest.java b/src/test/java/ru/yandex/metrika/clickhouse/copypaste/CHResultBuilderTest.java index f444b24efde8933e20977530da9b49595a6cbf47..2f76c225e32c0f37cbc18317f22e8ee8779bb9a0 100644 --- a/src/test/java/ru/yandex/metrika/clickhouse/copypaste/CHResultBuilderTest.java +++ b/src/test/java/ru/yandex/metrika/clickhouse/copypaste/CHResultBuilderTest.java @@ -12,6 +12,7 @@ public class CHResultBuilderTest { .types("String", "UInt32") .addRow("ololo", 1000) .addRow("o\tlo\nlo", 1000) + .addRow(null, null) .build(); Assert.assertEquals("string", resultSet.getColumnNames()[0]); @@ -28,6 +29,10 @@ public class CHResultBuilderTest { Assert.assertEquals("o\tlo\nlo", resultSet.getString(1)); Assert.assertEquals(1000, resultSet.getInt(2)); + Assert.assertTrue(resultSet.next()); + Assert.assertNull(resultSet.getString(1)); + Assert.assertEquals(0, resultSet.getInt(2)); + Assert.assertFalse(resultSet.next()); }