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());
 
     }