Repository: housepower/ClickHouse-Native-JDBC
Pull request #108 (2019-12-17 12:47:31)

refactor the insert

sundy-li: Round1, benchmark results on my macbook.

```
Benchmark                                           (batchSize)  (columnNum)  Mode  Cnt        Score   Error  Units
InsertIBenchmark.benchInsertHttp                          10000          N/A  avgt    2       21.181          ms/op
InsertIBenchmark.benchInsertHttp                         100000          N/A  avgt    2      160.174          ms/op
InsertIBenchmark.benchInsertHttp                        1000000          N/A  avgt    2     1601.598          ms/op
InsertIBenchmark.benchInsertNative                        10000          N/A  avgt    2        8.979          ms/op
InsertIBenchmark.benchInsertNative                       100000          N/A  avgt    2       51.049          ms/op
InsertIBenchmark.benchInsertNative                      1000000          N/A  avgt    2      467.123          ms/op
WideColumnDoubleInsertIBenchmark.benchInsertHttp         200000           20  avgt    2   744381.253          us/op
WideColumnDoubleInsertIBenchmark.benchInsertHttp         200000           50  avgt    2  1994787.865          us/op
WideColumnDoubleInsertIBenchmark.benchInsertHttp         200000          100  avgt    2  3508934.128          us/op
WideColumnDoubleInsertIBenchmark.benchInsertHttp         400000           20  avgt    2  1428203.984          us/op
WideColumnDoubleInsertIBenchmark.benchInsertHttp         400000           50  avgt    2  3775570.215          us/op
WideColumnDoubleInsertIBenchmark.benchInsertHttp         400000          100  avgt    2  7005023.592          us/op
WideColumnDoubleInsertIBenchmark.benchInsertNative       200000           20  avgt    2   396306.887          us/op
WideColumnDoubleInsertIBenchmark.benchInsertNative       200000           50  avgt    2  1036374.708          us/op
WideColumnDoubleInsertIBenchmark.benchInsertNative       200000          100  avgt    2  2291817.017          us/op
WideColumnDoubleInsertIBenchmark.benchInsertNative       400000           20  avgt    2   786330.212          us/op
WideColumnDoubleInsertIBenchmark.benchInsertNative       400000           50  avgt    2  2076479.972          us/op
WideColumnDoubleInsertIBenchmark.benchInsertNative       400000          100  avgt    2  4706939.867          us/op
WideColumnIntInsertIBenchmark.benchInsertHttp            200000           20  avgt    2   379327.061          us/op
WideColumnIntInsertIBenchmark.benchInsertHttp            200000           50  avgt    2   894758.623          us/op
WideColumnIntInsertIBenchmark.benchInsertHttp            200000          100  avgt    2  1879988.124          us/op
WideColumnIntInsertIBenchmark.benchInsertHttp            400000           20  avgt    2   834707.382          us/op
WideColumnIntInsertIBenchmark.benchInsertHttp            400000           50  avgt    2  1844512.257          us/op
WideColumnIntInsertIBenchmark.benchInsertHttp            400000          100  avgt    2  3374465.132          us/op
WideColumnIntInsertIBenchmark.benchInsertNative          200000           20  avgt    2   247408.121          us/op
WideColumnIntInsertIBenchmark.benchInsertNative          200000           50  avgt    2   707231.443          us/op
WideColumnIntInsertIBenchmark.benchInsertNative          200000          100  avgt    2  1410483.110          us/op
WideColumnIntInsertIBenchmark.benchInsertNative          400000           20  avgt    2   455184.958          us/op
WideColumnIntInsertIBenchmark.benchInsertNative          400000           50  avgt    2  1174600.929          us/op
WideColumnIntInsertIBenchmark.benchInsertNative          400000          100  avgt    2  2602777.234          us/op
WideColumnStringInsertIBenchmark.benchInsertHttp         200000           20  avgt    2   477805.607          us/op
WideColumnStringInsertIBenchmark.benchInsertHttp         200000           50  avgt    2  1392107.267          us/op
WideColumnStringInsertIBenchmark.benchInsertHttp         200000          100  avgt    2  2429868.947          us/op
WideColumnStringInsertIBenchmark.benchInsertHttp         400000           20  avgt    2  1112145.899          us/op
WideColumnStringInsertIBenchmark.benchInsertHttp         400000           50  avgt    2  2527808.052          us/op
WideColumnStringInsertIBenchmark.benchInsertHttp         400000          100  avgt    2  4900984.403          us/op
WideColumnStringInsertIBenchmark.benchInsertNative       200000           20  avgt    2   474380.295          us/op
WideColumnStringInsertIBenchmark.benchInsertNative       200000           50  avgt    2  1357331.238          us/op
WideColumnStringInsertIBenchmark.benchInsertNative       200000          100  avgt    2  2597380.351          us/op
WideColumnStringInsertIBenchmark.benchInsertNative       400000           20  avgt    2   935081.596          us/op
WideColumnStringInsertIBenchmark.benchInsertNative       400000           50  avgt    2  2329484.946          us/op
WideColumnStringInsertIBenchmark.benchInsertNative       400000          100  avgt    2  4933943.193          us/op
```

Patch:
diff --git a/.travis.yml b/.travis.yml
index ca2c279..dfb181c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,11 +5,12 @@ services:
   - docker
 jdk:
   - openjdk8
-  - openjdk9
-  - openjdk10
-  - oraclejdk8
-  - oraclejdk9
+#  - openjdk9
+#  - openjdk10
+#  - oraclejdk8
+#  - oraclejdk9
 script:
+  - mvn clean package -DskipTests=true
   - mvn clean verify
 
 
diff --git a/Benchmark.md b/Benchmark.md
new file mode 100644
index 0000000..f9c1011
--- /dev/null
+++ b/Benchmark.md
@@ -0,0 +1,83 @@
+## benchmarks
+
+Benchmarks are run by the `travis-ci` docker integration-test, eg [here](https://travis-ci.com/housepower/ClickHouse-Native-JDBC/builds/141995408#L5280).
+- All benchmark codes could be seen in the [test code directory](https://github.com/housepower/Clickhouse-Native-JDBC/tree/master/src/test/java/com/github/housepower/jdbc/benchmark), you can do the bench on your machine too.
+- Both  `Clickhouse-Native-JDBC` and `clickhouse-jdbc` are good jdbc clients. And `clickhouse-jdbc` are maintained better, 
+    this benchmarks only show your some difference, users could choose freely according to their needs.
+- The result's score means the avg_time to process one operation, lower is better.
+- With string type of JDBC batch insertion, `Clickhouse-Native-JDBC` and `clickhouse-jdbc` are almost the similar performance.
+- With other types of JDBC batch insertion or selection, `Clickhouse-Native-JDBC` may be 1~4 times better than `clickhouse-jdbc`.
+- `clickhouse-jdbc` RowBinary insert performs similar performance vs `Clickhouse-Native-JDBC`, 
+    it's not jdbc standared yet it's pretty efficient for the client because it sends data by row stream rather than one big block,
+    but it's not columnar based and may cause the clickhouse-server CPU higher load (On my machine, this's maybe 3 times overload by loop benchmark).
+- Here are the benchmark Params:
+    - `batchSize`: insert batch size.
+    - `columnNum`: insert column number, used in the WideColumn/RowBinary benchmarks.
+    - `selectNumber`: select row size.
+    
+```
+ InsertIBenchmark.benchInsertHttp                         200000           20             N/A  avgt    2    322.867          ms/op
+ InsertIBenchmark.benchInsertHttp                         200000           50             N/A  avgt    2    323.162          ms/op
+ InsertIBenchmark.benchInsertHttp                         500000           20             N/A  avgt    2    827.496          ms/op
+ InsertIBenchmark.benchInsertHttp                         500000           50             N/A  avgt    2    836.865          ms/op
+ InsertIBenchmark.benchInsertNative                       200000           20             N/A  avgt    2     72.309          ms/op
+ InsertIBenchmark.benchInsertNative                       200000           50             N/A  avgt    2     72.948          ms/op
+ InsertIBenchmark.benchInsertNative                       500000           20             N/A  avgt    2    168.628          ms/op
+ InsertIBenchmark.benchInsertNative                       500000           50             N/A  avgt    2    165.061          ms/op
+ 
+ 
+ SelectIBenchmark.benchSelectHTTP                            N/A          N/A          500000  avgt    2   1062.264          ms/op
+ SelectIBenchmark.benchSelectHTTP                            N/A          N/A        10000000  avgt    2  21549.043          ms/op
+ SelectIBenchmark.benchSelectNative                          N/A          N/A          500000  avgt    2    307.442          ms/op
+ SelectIBenchmark.benchSelectNative                          N/A          N/A        10000000  avgt    2   5794.785          ms/op
+ 
+ WideColumnDoubleInsertIBenchmark.benchInsertHttp         200000           20             N/A  avgt    2    717.190          ms/op
+ WideColumnDoubleInsertIBenchmark.benchInsertHttp         200000           50             N/A  avgt    2   1818.837          ms/op
+ WideColumnDoubleInsertIBenchmark.benchInsertHttp         500000           20             N/A  avgt    2   1829.937          ms/op
+ WideColumnDoubleInsertIBenchmark.benchInsertHttp         500000           50             N/A  avgt    2   4469.529          ms/op
+ WideColumnDoubleInsertIBenchmark.benchInsertNative       200000           20             N/A  avgt    2    339.103          ms/op
+ WideColumnDoubleInsertIBenchmark.benchInsertNative       200000           50             N/A  avgt    2    876.465          ms/op
+ WideColumnDoubleInsertIBenchmark.benchInsertNative       500000           20             N/A  avgt    2    784.621          ms/op
+ WideColumnDoubleInsertIBenchmark.benchInsertNative       500000           50             N/A  avgt    2   2200.515          ms/op
+ WideColumnIntInsertIBenchmark.benchInsertHttp            200000           20             N/A  avgt    2    366.501          ms/op
+ WideColumnIntInsertIBenchmark.benchInsertHttp            200000           50             N/A  avgt    2    874.524          ms/op
+ WideColumnIntInsertIBenchmark.benchInsertHttp            500000           20             N/A  avgt    2    930.941          ms/op
+ WideColumnIntInsertIBenchmark.benchInsertHttp            500000           50             N/A  avgt    2   2174.565          ms/op
+ WideColumnIntInsertIBenchmark.benchInsertNative          200000           20             N/A  avgt    2    196.612          ms/op
+ WideColumnIntInsertIBenchmark.benchInsertNative          200000           50             N/A  avgt    2    500.912          ms/op
+ WideColumnIntInsertIBenchmark.benchInsertNative          500000           20             N/A  avgt    2    511.506          ms/op
+ WideColumnIntInsertIBenchmark.benchInsertNative          500000           50             N/A  avgt    2   1266.273          ms/op
+ WideColumnStringInsertIBenchmark.benchInsertHttp         200000           20             N/A  avgt    2    454.479          ms/op
+ WideColumnStringInsertIBenchmark.benchInsertHttp         200000           50             N/A  avgt    2   1063.671          ms/op
+ WideColumnStringInsertIBenchmark.benchInsertHttp         500000           20             N/A  avgt    2   1143.546          ms/op
+ WideColumnStringInsertIBenchmark.benchInsertHttp         500000           50             N/A  avgt    2   2671.942          ms/op
+ WideColumnStringInsertIBenchmark.benchInsertNative       200000           20             N/A  avgt    2    415.649          ms/op
+ WideColumnStringInsertIBenchmark.benchInsertNative       200000           50             N/A  avgt    2   1117.887          ms/op
+ WideColumnStringInsertIBenchmark.benchInsertNative       500000           20             N/A  avgt    2   1040.238          ms/op
+ WideColumnStringInsertIBenchmark.benchInsertNative       500000           50             N/A  avgt    2   2805.116          ms/op
+ 
+ RowBinaryDoubleIBenchmark.benchInsertHttpRowBinary       200000           20             N/A  avgt    2    210.101          ms/op
+ RowBinaryDoubleIBenchmark.benchInsertHttpRowBinary       200000           50             N/A  avgt    2    512.881          ms/op
+ RowBinaryDoubleIBenchmark.benchInsertHttpRowBinary       500000           20             N/A  avgt    2    494.944          ms/op
+ RowBinaryDoubleIBenchmark.benchInsertHttpRowBinary       500000           50             N/A  avgt    2   1217.977          ms/op
+ RowBinaryDoubleIBenchmark.benchInsertNative              200000           20             N/A  avgt    2    317.598          ms/op
+ RowBinaryDoubleIBenchmark.benchInsertNative              200000           50             N/A  avgt    2    882.392          ms/op
+ RowBinaryDoubleIBenchmark.benchInsertNative              500000           20             N/A  avgt    2    786.638          ms/op
+ RowBinaryDoubleIBenchmark.benchInsertNative              500000           50             N/A  avgt    2   2178.728          ms/op
+ RowBinaryIntIBenchmark.benchInsertHttpRowBinary          200000           20             N/A  avgt    2    214.144          ms/op
+ RowBinaryIntIBenchmark.benchInsertHttpRowBinary          200000           50             N/A  avgt    2    520.860          ms/op
+ RowBinaryIntIBenchmark.benchInsertHttpRowBinary          500000           20             N/A  avgt    2    497.062          ms/op
+ RowBinaryIntIBenchmark.benchInsertHttpRowBinary          500000           50             N/A  avgt    2   1318.273          ms/op
+ RowBinaryIntIBenchmark.benchInsertNative                 200000           20             N/A  avgt    2    204.526          ms/op
+ RowBinaryIntIBenchmark.benchInsertNative                 200000           50             N/A  avgt    2    482.022          ms/op
+ RowBinaryIntIBenchmark.benchInsertNative                 500000           20             N/A  avgt    2    519.482          ms/op
+ RowBinaryIntIBenchmark.benchInsertNative                 500000           50             N/A  avgt    2   1323.276          ms/op
+ RowBinaryStringIBenchmark.benchInsertHttpRowBinary       200000           20             N/A  avgt    2    446.506          ms/op
+ RowBinaryStringIBenchmark.benchInsertHttpRowBinary       200000           50             N/A  avgt    2   1089.095          ms/op
+ RowBinaryStringIBenchmark.benchInsertHttpRowBinary       500000           20             N/A  avgt    2   1076.479          ms/op
+ RowBinaryStringIBenchmark.benchInsertHttpRowBinary       500000           50             N/A  avgt    2   2845.602          ms/op
+ RowBinaryStringIBenchmark.benchInsertNative              200000           20             N/A  avgt    2    418.727          ms/op
+ RowBinaryStringIBenchmark.benchInsertNative              200000           50             N/A  avgt    2   1079.312          ms/op
+ RowBinaryStringIBenchmark.benchInsertNative              500000           20             N/A  avgt    2   1016.174          ms/op
+ RowBinaryStringIBenchmark.benchInsertNative              500000           50             N/A  avgt    2   2785.120          ms/op
+```
\ No newline at end of file
diff --git a/README.md b/README.md
index 32798c0..f6d0864 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,9 @@ This is a native JDBC library for accessing [ClickHouse](https://clickhouse.yand
 ```
     git clone git@github.com:housepower/ClickHouse-Native-JDBC.git
     cd ClickHouse-Native-JDBC
-    mvn clean install
+    mvn clean package
+    #build single jar with dependencies
+    mvn clean package assembly:single -DskipTests=true
 ```
 
 ## Maven central
@@ -27,19 +29,17 @@ This is a native JDBC library for accessing [ClickHouse](https://clickhouse.yand
 
 ## Differences from [Yandex/Clickhouse-JDBC](https://github.com/yandex/clickhouse-jdbc)
 * Data is organized and compressed by columns
-* We implemented it using the TCP Protocol, with higher performance than HTTP
+* We implemented it using the TCP Protocol, with higher performance than HTTP, here is the [benchmark](./Benchmark.md).
 
 ## Not Supported
 * Non-values format
 * Complex values expression, Like `INSERT INTO test_table VALUES(toDate(123456))`
 * More compression method, like `ZSTD`
-* Array(T) only supports one-dimensional
 
 ## Example
 
 Select query, see also [SimpleQuery.java](./src/main/java/examples/SimpleQuery.java)
 ```java
-    Class.forName("com.github.housepower.jdbc.ClickHouseDriver");
     Connection connection = DriverManager.getConnection("jdbc:clickhouse://127.0.0.1:9000");
 
     Statement stmt = connection.createStatement();
@@ -53,24 +53,18 @@ Select query, see also [SimpleQuery.java](./src/main/java/examples/SimpleQuery.j
 All DDL,DML queries, see also [ExecuteQuery.java](./src/main/java/examples/ExecuteQuery.java)
 
 ```java
-    Class.forName("com.github.housepower.jdbc.ClickHouseDriver");
     Connection connection = DriverManager.getConnection("jdbc:clickhouse://127.0.0.1:9000");
 
     Statement stmt = connection.createStatement();
-    // drop table
     stmt.executeQuery("drop table if exists test_jdbc_example");
-    // create table
     stmt.executeQuery("create table test_jdbc_example(day default toDate( toDateTime(timestamp) ), timestamp UInt32, name String, impressions UInt32) Engine=MergeTree(day, (timestamp, name), 8192)");
-    // add column `costs`
     stmt.executeQuery("alter table test_jdbc_example add column costs Float32");
-    // drop the table
     stmt.executeQuery("drop table test_jdbc_example");
 ```
 
 Batch insert query, see also [BatchQuery.java](./src/main/java/examples/BatchQuery.java)
 
 ``` java
-    Class.forName("com.github.housepower.jdbc.ClickHouseDriver");
     Connection connection = DriverManager.getConnection("jdbc:clickhouse://127.0.0.1:9000");
 
     Statement stmt = connection.createStatement();
diff --git a/pom.xml b/pom.xml
index 7448205..51db56f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,15 +59,27 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>com.carrotsearch</groupId>
-            <artifactId>junit-benchmarks</artifactId>
-            <version>0.7.2</version>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+            <version>2.9.9</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.openjdk.jmh</groupId>
+            <artifactId>jmh-core</artifactId>
+            <version>1.21</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.openjdk.jmh</groupId>
+            <artifactId>jmh-generator-annprocess</artifactId>
+            <version>1.21</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>ru.yandex.clickhouse</groupId>
             <artifactId>clickhouse-jdbc</artifactId>
-            <version>0.1.40</version>
+            <version>0.2</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -76,16 +88,21 @@
             <version>1.10.19</version>
             <scope>test</scope>
         </dependency>
+        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple -->
         <dependency>
-            <groupId>joda-time</groupId>
-            <artifactId>joda-time</artifactId>
-            <version>2.9.9</version>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>1.7.28</version>
+            <scope>test</scope>
         </dependency>
+
     </dependencies>
     <properties>
         <ClickHousePort>9000</ClickHousePort>
+        <ClickHouseHTTPPort>8123</ClickHouseHTTPPort>
     </properties>
 
+
     <profiles>
         <profile>
             <id>release</id>
@@ -187,6 +204,7 @@
                             </systemPropertyVariables>
                             <includes>
                                 <include>**/*ITest*</include>
+                                <include>**/*IBenchmark*</include>
                             </includes>
                         </configuration>
                     </execution>
@@ -205,6 +223,39 @@
                     </execution>
                 </executions>
             </plugin>
+
+            <!-- Benchmark -->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>run-benchmarks</id>
+                        <phase>integration-test</phase>
+                        <goals>
+                            <goal>exec</goal>
+                        </goals>
+                        <configuration>
+                            <classpathScope>test</classpathScope>
+                            <executable>java</executable>
+                            <arguments>
+                                <argument>-classpath</argument>
+                                <classpath/>
+                                <argument>org.openjdk.jmh.Main</argument>
+                                <argument>-bm</argument>
+                                <argument>avgt</argument>
+                                <argument>-wi</argument>
+                                <argument>0</argument>
+                                <argument>-i</argument>
+                                <argument>1</argument>
+                                <argument>-f</argument>
+                                <argument>2</argument>
+                            </arguments>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
             <plugin>
                 <groupId>io.fabric8</groupId>
                 <artifactId>docker-maven-plugin</artifactId>
@@ -218,6 +269,7 @@
                             <run>
                                 <ports>
                                     <port>${ClickHousePort}:9000</port>
+                                    <port>${ClickHouseHTTPPort}:8123</port>
                                 </ports>
                                 <wait>
                                     <tcp>
@@ -226,7 +278,14 @@
                                             <port>${ClickHousePort}</port>
                                         </ports>
                                     </tcp>
-                                    <time>20000</time>
+
+                                    <http>
+                                        <url>http://127.0.0.1:${ClickHouseHTTPPort}</url>
+                                        <method>GET</method>
+                                        <status>200</status>
+                                    </http>
+
+                                    <time>50000</time>
                                 </wait>
                             </run>
                         </image>
@@ -249,6 +308,24 @@
                     </execution>
                 </executions>
             </plugin>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>3.1.1</version>
+                <configuration>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id> <!-- this is used for inheritance merges -->
+                        <phase>package</phase> <!-- bind to the packaging phase -->
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
             <plugin>
                 <artifactId>maven-assembly-plugin</artifactId>
                 <version>3.1.1</version>
diff --git a/src/main/java/com/github/housepower/jdbc/ClickHouseConnection.java b/src/main/java/com/github/housepower/jdbc/ClickHouseConnection.java
index 892539f..0edf2ce 100644
--- a/src/main/java/com/github/housepower/jdbc/ClickHouseConnection.java
+++ b/src/main/java/com/github/housepower/jdbc/ClickHouseConnection.java
@@ -12,7 +12,6 @@ import com.github.housepower.jdbc.settings.ClickHouseDefines;
 import com.github.housepower.jdbc.statement.ClickHousePreparedInsertStatement;
 import com.github.housepower.jdbc.statement.ClickHousePreparedQueryStatement;
 import com.github.housepower.jdbc.statement.ClickHouseStatement;
-import com.github.housepower.jdbc.stream.InputFormat;
 import com.github.housepower.jdbc.wrapper.SQLConnection;
 
 import java.net.InetSocketAddress;
@@ -122,33 +121,29 @@ public class ClickHouseConnection extends SQLConnection {
         }
     }
 
-    public QueryResponse sendQueryRequest(final String query) throws SQLException {
+    public Block getSampleBlock(final String insertQuery) throws SQLException {
         PhysicalConnection connection = getHealthyPhysicalConnection();
+        connection.sendQuery(insertQuery, atomicInfo.get().client(), configure.settings());
+        return connection.receiveSampleBlock(configure.queryTimeout(), atomicInfo.get().server());
+    }
 
+    public QueryResponse sendQueryRequest(final String query) throws SQLException {
+        PhysicalConnection connection = getHealthyPhysicalConnection();
         connection.sendQuery(query, atomicInfo.get().client(), configure.settings());
 
         return new QueryResponse(() -> connection.receiveResponse(configure.queryTimeout(), atomicInfo.get().server()));
     }
 
-    public Integer sendInsertRequest(final String insertQuery, final InputFormat input) throws SQLException {
-        PhysicalConnection connection = getHealthyPhysicalConnection();
-
-        int rows = 0;
-        connection.sendQuery(insertQuery, atomicInfo.get().client(), configure.settings());
-        Block header = connection.receiveSampleBlock(configure.queryTimeout(), atomicInfo.get().server());
-        while (true) {
-            Block block = input.next(header, ClickHouseDefines.MAX_BLOCK_SIZE);
-
-            if (block == null || block.rows() == 0) {
-                connection.sendData(new Block());
-                connection.receiveEndOfStream(configure.queryTimeout(), atomicInfo.get().server());
+    // when sendInsertRequest we must ensure the connection is healthy
+    // the sampleblock mus be called before this method
+    public Integer sendInsertRequest(Block block)
+        throws SQLException {
 
-                return rows;
-            }
-
-            connection.sendData(block);
-            rows += block.rows();
-        }
+        PhysicalConnection connection = getPhysicalConnection();
+        connection.sendData(block);
+        connection.sendData(new Block());
+        connection.receiveEndOfStream(configure.queryTimeout(), atomicInfo.get().server());
+        return block.rows();
     }
 
     private PhysicalConnection getHealthyPhysicalConnection() throws SQLException {
@@ -162,6 +157,10 @@ public class ClickHouseConnection extends SQLConnection {
         return atomicInfo.get().connection();
     }
 
+    private PhysicalConnection getPhysicalConnection() throws SQLException {
+        return atomicInfo.get().connection();
+    }
+
     public static ClickHouseConnection createClickHouseConnection(ClickHouseConfig configure) throws SQLException {
         return new ClickHouseConnection(configure, createPhysicalInfo(configure));
     }
diff --git a/src/main/java/com/github/housepower/jdbc/ClickHouseResultSet.java b/src/main/java/com/github/housepower/jdbc/ClickHouseResultSet.java
index 5a0fd9d..13d40f4 100644
--- a/src/main/java/com/github/housepower/jdbc/ClickHouseResultSet.java
+++ b/src/main/java/com/github/housepower/jdbc/ClickHouseResultSet.java
@@ -177,7 +177,7 @@ public class ClickHouseResultSet extends SQLResultSet {
         Validate.isTrue(row >= 0 && row < current.rows(),
             "No row information was obtained.You must call ResultSet.next() before that.");
         Column column = (lastFetchBlock = current).getByPosition((lastFetchColumn = index - 1));
-        Object rowData = column.data((lastFetchRow = row));
+        Object rowData = column.values((lastFetchRow = row));
         return rowData == null ? column.type().defaultValue() : rowData;
     }
 
@@ -205,7 +205,7 @@ public class ClickHouseResultSet extends SQLResultSet {
         Validate.isTrue(lastFetchBlock != null, "Please call Result.next()");
         Validate.isTrue(lastFetchColumn >= 0, "Please call Result.getXXX()");
         Validate.isTrue(lastFetchRow >= 0 && lastFetchRow < lastFetchBlock.rows(), "Please call Result.next()");
-        return lastFetchBlock.getByPosition(lastFetchColumn).data(lastFetchRow) == null;
+        return lastFetchBlock.getByPosition(lastFetchColumn).values(lastFetchRow) == null;
     }
 
     @Override
diff --git a/src/main/java/com/github/housepower/jdbc/buffer/ByteArrayWriter.java b/src/main/java/com/github/housepower/jdbc/buffer/ByteArrayWriter.java
new file mode 100644
index 0000000..77e0e22
--- /dev/null
+++ b/src/main/java/com/github/housepower/jdbc/buffer/ByteArrayWriter.java
@@ -0,0 +1,82 @@
+package com.github.housepower.jdbc.buffer;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ *
+ */
+public class ByteArrayWriter implements BuffedWriter{
+    private int blockSize;
+    private ByteBuffer buffer;
+
+    //TODO pooling
+    private List<ByteBuffer> byteBufferList = new LinkedList<>();
+
+    public ByteArrayWriter(int blockSize) {
+        this.blockSize = blockSize;
+        this.buffer = ByteBuffer.allocate(blockSize);
+        byteBufferList.add(buffer);
+    }
+
+    @Override
+    public void writeBinary(byte byt) throws IOException {
+        buffer.put(byt);
+        flushToTarget(false);
+    }
+
+    @Override
+    public void writeBinary(byte[] bytes) throws IOException {
+        writeBinary(bytes, 0, bytes.length);
+    }
+
+    @Override
+    public void writeBinary(byte[] bytes, int offset, int length) throws IOException {
+
+        while (buffer.remaining() < length) {
+            int num = buffer.remaining();
+            buffer.put(bytes, offset, num);
+            flushToTarget(true);
+
+            offset += num;
+            length -= num;
+        }
+
+        buffer.put(bytes, offset, length);
+        flushToTarget(false);
+    }
+
+    @Override
+    public void flushToTarget(boolean force) throws IOException {
+        if (buffer.hasRemaining() && !force) {
+            return;
+        }
+        buffer = ByteBuffer.allocate(blockSize);
+        byteBufferList.add(buffer);
+    }
+
+    @Deprecated
+    private void expend() throws IOException {
+        if (buffer.hasRemaining()) {
+            return;
+        }
+        int newCapacity = (int) (buffer.capacity() * expandFactor);
+        while (newCapacity < (buffer.capacity() + 1)) {
+            newCapacity *= expandFactor;
+        }
+        ByteBuffer expanded = ByteBuffer.allocate(newCapacity);
+        expanded.order(buffer.order());
+        buffer.flip();
+        expanded.put(buffer);
+        buffer = expanded;
+        byteBufferList.set(0, buffer);
+    }
+
+    private static final float expandFactor = 1.5f;
+
+    public List<ByteBuffer> getBufferList() {
+        return byteBufferList;
+    }
+}
diff --git a/src/main/java/com/github/housepower/jdbc/buffer/CompressedBuffedWriter.java b/src/main/java/com/github/housepower/jdbc/buffer/CompressedBuffedWriter.java
index 450a3da..10ace68 100644
--- a/src/main/java/com/github/housepower/jdbc/buffer/CompressedBuffedWriter.java
+++ b/src/main/java/com/github/housepower/jdbc/buffer/CompressedBuffedWriter.java
@@ -1,6 +1,7 @@
 package com.github.housepower.jdbc.buffer;
 
 import com.github.housepower.jdbc.misc.ClickHouseCityHash;
+
 import net.jpountz.lz4.LZ4Compressor;
 import net.jpountz.lz4.LZ4Factory;
 
@@ -35,28 +36,30 @@ public class CompressedBuffedWriter implements BuffedWriter {
 
     @Override
     public void writeBinary(byte[] bytes, int offset, int length) throws IOException {
-        for (int i = offset, max = offset + length; i < max; ) {
-            if (remaining()) {
-                int writtenNumber = Math.min(capacity - position, max - i);
-                System.arraycopy(bytes, i, writtenBuf, position, writtenNumber);
-                i += writtenNumber;
-                position += writtenNumber;
-            }
+        while (remaing() < length) {
+            int num = remaing();
+            System.arraycopy(bytes, offset, writtenBuf, position, remaing());
+            position += num;
+
             flushToTarget(false);
+            offset += num;
+            length -= num;
         }
+
+        System.arraycopy(bytes, offset, writtenBuf, position, length);
+        position += length;
+        flushToTarget(false);
     }
 
+
     private static final int COMPRESSION_HEADER_LENGTH = 9;
 
     @Override
     public void flushToTarget(boolean force) throws IOException {
-        if (force || !remaining()) {
-            ///TODO: None compressed method
-            ///TODO: use buf
+        if (position > 0 && (force || !hasRemaining())) {
             int maxLen = lz4Compressor.maxCompressedLength(position);
 
             byte[] compressedBuffer = new byte[maxLen + 9 + 16];
-
             int res = lz4Compressor.compress(writtenBuf, 0, position, compressedBuffer, 9 + 16);
 
             compressedBuffer[16] = (byte) (0x82 & 0xFF);
@@ -70,14 +73,16 @@ public class CompressedBuffedWriter implements BuffedWriter {
 
             writer.writeBinary(compressedBuffer, 0, compressedSize + 16);
             position = 0;
-            writer.flushToTarget(force);
         }
     }
 
-    private boolean remaining() {
+    private boolean hasRemaining() {
         return position < capacity;
     }
 
+    private int remaing() {
+        return capacity - position;
+    }
 
     private byte[] littleEndian(int x) {
         byte[] data = new byte[4];
diff --git a/src/main/java/com/github/housepower/jdbc/buffer/SocketBuffedWriter.java b/src/main/java/com/github/housepower/jdbc/buffer/SocketBuffedWriter.java
index 9a3f4d9..594323a 100644
--- a/src/main/java/com/github/housepower/jdbc/buffer/SocketBuffedWriter.java
+++ b/src/main/java/com/github/housepower/jdbc/buffer/SocketBuffedWriter.java
@@ -1,62 +1,35 @@
 package com.github.housepower.jdbc.buffer;
 
-import com.github.housepower.jdbc.settings.ClickHouseDefines;
-
 import java.io.IOException;
 import java.io.OutputStream;
 import java.net.Socket;
 
 public class SocketBuffedWriter implements BuffedWriter {
 
-    private int position;
-    private int capacity;
-
-    private final byte[] buf;
     private final OutputStream out;
 
     public SocketBuffedWriter(Socket socket) throws IOException {
-        this.position = 0;
-        this.capacity = ClickHouseDefines.SOCKET_BUFFER_SIZE;
-
-        this.buf = new byte[capacity];
         this.out = socket.getOutputStream();
     }
 
     @Override
     public void writeBinary(byte byt) throws IOException {
-        buf[position++] = byt;
-        flushToTarget(false);
+        out.write(byt);
     }
 
 
     @Override
     public void writeBinary(byte[] bytes) throws IOException {
-        writeBinary(bytes, 0, bytes.length);
+        out.write(bytes);
     }
 
     @Override
     public void writeBinary(byte[] bytes, int offset, int length) throws IOException {
-        for (int i = offset, max = offset + length; i < max; ) {
-            if (remaining()) {
-                int writtenNumber = Math.min(capacity - position, max - i);
-                System.arraycopy(bytes, i, buf, position, writtenNumber);
-                i += writtenNumber;
-                position += writtenNumber;
-            }
-            flushToTarget(false);
-        }
+        out.write(bytes, offset, length);
     }
 
     @Override
     public void flushToTarget(boolean force) throws IOException {
-        if (force || !remaining()) {
-            out.write(buf, 0, position);
-            position = 0;
-            out.flush();
-        }
-    }
-
-    private boolean remaining() {
-        return position < capacity;
+        out.flush();
     }
 }
diff --git a/src/main/java/com/github/housepower/jdbc/connect/PhysicalConnection.java b/src/main/java/com/github/housepower/jdbc/connect/PhysicalConnection.java
index 1b45d7c..a5f55fe 100644
--- a/src/main/java/com/github/housepower/jdbc/connect/PhysicalConnection.java
+++ b/src/main/java/com/github/housepower/jdbc/connect/PhysicalConnection.java
@@ -1,5 +1,6 @@
 package com.github.housepower.jdbc.connect;
 
+import com.github.housepower.jdbc.buffer.SocketBuffedWriter;
 import com.github.housepower.jdbc.data.Block;
 import com.github.housepower.jdbc.misc.Validate;
 import com.github.housepower.jdbc.protocol.DataRequest;
@@ -136,7 +137,7 @@ public class PhysicalConnection {
             socket.setReceiveBufferSize(ClickHouseDefines.SOCKET_BUFFER_SIZE);
             socket.connect(endpoint, configure.connectTimeout());
 
-            return new PhysicalConnection(socket, new BinarySerializer(socket), new BinaryDeserializer(socket));
+            return new PhysicalConnection(socket, new BinarySerializer(new SocketBuffedWriter(socket), true), new BinaryDeserializer(socket));
         } catch (IOException ex) {
             throw new SQLException(ex.getMessage(), ex);
         }
diff --git a/src/main/java/com/github/housepower/jdbc/data/Block.java b/src/main/java/com/github/housepower/jdbc/data/Block.java
index 58df398..630e5dd 100644
--- a/src/main/java/com/github/housepower/jdbc/data/Block.java
+++ b/src/main/java/com/github/housepower/jdbc/data/Block.java
@@ -1,11 +1,10 @@
 package com.github.housepower.jdbc.data;
 
 import com.github.housepower.jdbc.connect.PhysicalInfo;
-import com.github.housepower.jdbc.misc.Slice;
+import com.github.housepower.jdbc.data.BlockSettings.Setting;
 import com.github.housepower.jdbc.misc.Validate;
 import com.github.housepower.jdbc.serializer.BinaryDeserializer;
 import com.github.housepower.jdbc.serializer.BinarySerializer;
-import com.github.housepower.jdbc.data.BlockSettings.Setting;
 
 import java.io.IOException;
 import java.sql.SQLException;
@@ -13,11 +12,14 @@ import java.util.HashMap;
 import java.util.Map;
 
 public class Block {
-    private final int rows;
+
     private final Column[] columns;
     private final BlockSettings settings;
     private final Map<String, Integer> nameWithPosition;
 
+    private Object[]  objects;
+    private int[] columnIndexAdds;
+    private int rows;
 
     public Block() {
         this(0, new Column[0]);
@@ -32,9 +34,37 @@ public class Block {
         this.columns = columns;
         this.settings = settings;
 
-        nameWithPosition = new HashMap<String, Integer>();
+        this.objects = new Object[columns.length];
+        this.columnIndexAdds = new int[columns.length];
+        nameWithPosition = new HashMap<>();
         for (int i = 0; i < columns.length; i++) {
-            nameWithPosition.put(columns[i].name(), i+1);
+            nameWithPosition.put(columns[i].name(), i + 1);
+            columnIndexAdds[i] = i;
+        }
+    }
+
+    public void appendRow() throws SQLException {
+        try {
+            for (int i = 0; i < columns.length; i++) {
+                columns[i].write(objects[i]);
+            }
+            rows ++;
+        } catch (IOException e) {
+            throw new SQLException(e);
+        }
+    }
+
+    public void setObject(int i, Object object) throws SQLException {
+        objects[columnIndexAdds[i]] = object;
+    }
+
+    public void setConstObject(int i, Object object) throws SQLException {
+        objects[i] = object;
+    }
+
+    public void incrIndex(int i) {
+        for (int j = i; j < columnIndexAdds.length; j++) {
+            columnIndexAdds[j] += 1;
         }
     }
 
@@ -49,7 +79,7 @@ public class Block {
         }
     }
 
-    public long rows() {
+    public int rows() {
         return rows;
     }
 
@@ -59,7 +89,9 @@ public class Block {
 
     public Column getByPosition(int column) throws SQLException {
         Validate.isTrue(column < columns.length,
-            "Position " + column + " is out of bound in Block.getByPosition, max position = " + (columns.length - 1));
+                        "Position " + column
+                        + " is out of bound in Block.getByPosition, max position = " + (
+                            columns.length - 1));
         return columns[column];
     }
 
@@ -68,7 +100,8 @@ public class Block {
         return nameWithPosition.get(name);
     }
 
-    public static Block readFrom(BinaryDeserializer deserializer, PhysicalInfo.ServerInfo serverInfo)
+    public static Block readFrom(BinaryDeserializer deserializer,
+                                 PhysicalInfo.ServerInfo serverInfo)
         throws IOException, SQLException {
         BlockSettings info = BlockSettings.readFrom(deserializer);
 
@@ -82,10 +115,16 @@ public class Block {
             String type = deserializer.readStringBinary();
 
             IDataType dataType = DataTypeFactory.get(type, serverInfo);
-            Object []arr = dataType.deserializeBinaryBulk(rows, deserializer);
-            cols[i] = new Column(name, dataType, new Slice(arr));
+            Object[] arr = dataType.deserializeBinaryBulk(rows, deserializer);
+            cols[i] = new Column(name, dataType, arr);
         }
 
         return new Block(rows, cols, info);
     }
+
+    public void initWriteBuffer() {
+        for (Column column : columns) {
+            column.initWriteBuffer();
+        }
+    }
 }
diff --git a/src/main/java/com/github/housepower/jdbc/data/Column.java b/src/main/java/com/github/housepower/jdbc/data/Column.java
index 6b2eec0..1d9c92b 100644
--- a/src/main/java/com/github/housepower/jdbc/data/Column.java
+++ b/src/main/java/com/github/housepower/jdbc/data/Column.java
@@ -1,36 +1,41 @@
 package com.github.housepower.jdbc.data;
 
-import com.github.housepower.jdbc.misc.Slice;
+import com.github.housepower.jdbc.data.type.complex.DataTypeArray;
 import com.github.housepower.jdbc.serializer.BinarySerializer;
 
 import java.io.IOException;
 import java.sql.SQLException;
 import java.sql.Types;
+import java.util.ArrayList;
+import java.util.List;
 
 public class Column {
 
     private final String name;
     private final IDataType type;
-    private final Slice rowsData;
-    private final int rows;
 
-    private boolean isConstant;
+    private Object[] values;
+    private ColumnWriterBuffer buffer;
+    private boolean isArray;
+    private List<List<Integer>> offsets;
 
-    public Column(String name, IDataType type, Slice rowsData) {
+    public Column(String name, IDataType type) {
         this.name = name;
         this.type = type;
-        this.rowsData = rowsData;
-        this.rows = rowsData.size();
     }
 
-    // const column
-    public Column(String name, IDataType type, Object constData, int rows) {
+    public Column(String name, IDataType type, Object[] values) {
+        this.values = values;
         this.name = name;
         this.type = type;
-        this.isConstant = true;
-        this.rows = rows;
-        this.rowsData = new Slice(1);
-        this.rowsData.add(constData);
+        if (this.type.sqlTypeId() == Types.ARRAY) {
+            this.isArray = true;
+            this.offsets = new ArrayList<>();
+        }
+    }
+
+    public void initWriteBuffer() {
+        this.buffer = new ColumnWriterBuffer();
     }
 
     public String name() {
@@ -41,36 +46,37 @@ public class Column {
         return this.type;
     }
 
-    public Slice data() {
-        return rowsData;
+    public Object values(int rowIdx) {
+        return this.values[rowIdx];
     }
 
-    public Object data(int rows) {
-        if (isConstant) {
-            return rowsData.get(0);
+    public void write(Object object) throws IOException, SQLException {
+        if (isArray) {
+            DataTypeArray typ = (DataTypeArray)(type());
+            typ.serializeBinary(object, buffer.column, offsets, 1);
+        } else {
+            type().serializeBinary(object, buffer.column);
         }
-        return rowsData.get(rows);
     }
 
+
     public void serializeBinaryBulk(BinarySerializer serializer) throws SQLException, IOException {
         serializer.writeStringBinary(name);
         serializer.writeStringBinary(type.name());
 
-        if (type.sqlTypeId() == Types.ARRAY) {
-            Object[] objects = new Object[rows];
-            for (int i = 0; i < rows; i++) {
-                objects[i] = rowsData.get(isConstant ? 0 : i);
+        //writer offsets
+        if (offsets != null) {
+            for (List<Integer> offsetList : offsets) {
+                for (int offset : offsetList) {
+                    serializer.writeLong(offset);
+                }
             }
-            type.serializeBinaryBulk(objects, serializer);
-            return;
         }
 
-        if (isConstant) {
-            for (int i = 0; i < rows; i++) {
-                type.serializeBinary(rowsData.get(0), serializer);
-            }
-        } else {
-            type.serializeBinaryBulk(rowsData.iterator(), serializer);
-        }
+        buffer.writeTo(serializer);
+    }
+
+    public ColumnWriterBuffer getBuffer() {
+        return buffer;
     }
 }
diff --git a/src/main/java/com/github/housepower/jdbc/data/ColumnWriterBuffer.java b/src/main/java/com/github/housepower/jdbc/data/ColumnWriterBuffer.java
new file mode 100644
index 0000000..daaf136
--- /dev/null
+++ b/src/main/java/com/github/housepower/jdbc/data/ColumnWriterBuffer.java
@@ -0,0 +1,32 @@
+package com.github.housepower.jdbc.data;
+
+import com.github.housepower.jdbc.buffer.ByteArrayWriter;
+import com.github.housepower.jdbc.serializer.BinarySerializer;
+import com.github.housepower.jdbc.settings.ClickHouseDefines;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ *
+ */
+public class ColumnWriterBuffer {
+
+    public BinarySerializer column;
+    private ByteArrayWriter columnWriter;
+
+
+    public ColumnWriterBuffer() {
+        this.columnWriter = new ByteArrayWriter(ClickHouseDefines.COLUMN_BUFFER);
+        this.column = new BinarySerializer(columnWriter, false);
+    }
+
+    public void writeTo(BinarySerializer serializer) throws IOException {
+        for (ByteBuffer buffer : columnWriter.getBufferList()) {
+            buffer.flip();
+            while (buffer.hasRemaining()) {
+                serializer.writeByte(buffer.get());
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/housepower/jdbc/data/IDataType.java b/src/main/java/com/github/housepower/jdbc/data/IDataType.java
index 4833c31..618365d 100644
--- a/src/main/java/com/github/housepower/jdbc/data/IDataType.java
+++ b/src/main/java/com/github/housepower/jdbc/data/IDataType.java
@@ -20,11 +20,9 @@ public interface IDataType {
     boolean nullable();
 
     Object deserializeTextQuoted(SQLLexer lexer) throws SQLException;
-
-    void serializeBinary(Object data, BinarySerializer serializer) throws SQLException, IOException;
-
     Object deserializeBinary(BinaryDeserializer deserializer) throws SQLException, IOException;
 
+    void serializeBinary(Object data, BinarySerializer serializer) throws SQLException, IOException;
     default void serializeBinaryBulk(Iterator<Object> data, BinarySerializer serializer) throws SQLException, IOException {
         while(data.hasNext()) {
             serializeBinary(data.next(), serializer);
diff --git a/src/main/java/com/github/housepower/jdbc/data/type/complex/DataTypeArray.java b/src/main/java/com/github/housepower/jdbc/data/type/complex/DataTypeArray.java
index 16beb93..20457e9 100644
--- a/src/main/java/com/github/housepower/jdbc/data/type/complex/DataTypeArray.java
+++ b/src/main/java/com/github/housepower/jdbc/data/type/complex/DataTypeArray.java
@@ -15,6 +15,7 @@ import java.sql.Array;
 import java.sql.SQLException;
 import java.sql.Types;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 
 public class DataTypeArray implements IDataType {
@@ -72,33 +73,51 @@ public class DataTypeArray implements IDataType {
         return new ClickHouseArray(arrayData.toArray());
     }
 
+    public void serializeBinary(Object data, BinarySerializer dataBinarySerializer, List<List<Integer>> offsets, int level) throws SQLException, IOException {
+        int dataOffset = ((Object[]) ((Array) data).getArray()).length;
+        if (offsets.size() < level) {
+            List<Integer> offset = new ArrayList<>();
+            offset.add(dataOffset);
+            offsets.add(offset);
+        } else {
+            int lastIdx =  offsets.get(level - 1).size() - 1;
+            int lastOffset =  offsets.get(level - 1).get(lastIdx);
+            offsets.get(level - 1).add(lastOffset + dataOffset);
+        }
+
+        for (Object v : ((Object[]) ((Array) data).getArray())) {
+            if (elemDataType.sqlTypeId() == Types.ARRAY) {
+                ((DataTypeArray)(elemDataType)).serializeBinary(v, dataBinarySerializer, offsets, level + 1);
+            } else {
+                elemDataType.serializeBinary(v, dataBinarySerializer);
+            }
+        }
+    }
+
     @Override
-    public void serializeBinary(Object data, BinarySerializer serializer) throws SQLException, IOException {
-        offsetIDataType.serializeBinary(((Object[]) ((Array) data).getArray()).length, serializer);
-        elemDataType.serializeBinaryBulk(((Object[]) ((Array) data).getArray()), serializer);
+    public void serializeBinary(Object data, BinarySerializer serializer) throws SQLException, IOException{
+        throw new SQLException("DataTypeArray serializeBinary not supported");
     }
 
     @Override
-    public Object deserializeBinary(BinaryDeserializer deserializer) throws SQLException, IOException {
-        Long offset = (Long) offsetIDataType.deserializeBinary(deserializer);
-        return elemDataType.deserializeBinaryBulk(offset.intValue(), deserializer);
+    public void serializeBinaryBulk(Iterator<Object> data, BinarySerializer serializer)
+        throws SQLException, IOException {
+        throw new SQLException("DataTypeArray serializeBinaryBulk not supported");
     }
 
     @Override
-    public void serializeBinaryBulk(Object[] data, BinarySerializer serializer) throws SQLException, IOException {
-        long offset = 0;
-        for (Object datum : data) {
-            Object[] arrayData = (Object[]) ((Array) datum).getArray();
-            offset += arrayData.length;
-            offsetIDataType.serializeBinary(offset, serializer);
-        }
+    public void serializeBinaryBulk(Object[] data, BinarySerializer serializer)
+        throws SQLException, IOException {
+        throw new SQLException("DataTypeArray serializeBinaryBulk not supported");
+    }
 
-        for (Object datum : data) {
-            Object[] arrayData = (Object[]) ((Array) datum).getArray();
-            elemDataType.serializeBinaryBulk(arrayData, serializer);
-        }
+    @Override
+    public Object deserializeBinary(BinaryDeserializer deserializer) throws SQLException, IOException {
+        Long offset = (Long) offsetIDataType.deserializeBinary(deserializer);
+        return elemDataType.deserializeBinaryBulk(offset.intValue(), deserializer);
     }
 
+
     @Override
     public Object[] deserializeBinaryBulk(int rows, BinaryDeserializer deserializer) throws IOException, SQLException {
         ClickHouseArray[] data = new ClickHouseArray[rows];
@@ -118,6 +137,7 @@ public class DataTypeArray implements IDataType {
         return data;
     }
 
+
     public static IDataType createArrayType(SQLLexer lexer, PhysicalInfo.ServerInfo serverInfo) throws SQLException {
         Validate.isTrue(lexer.character() == '(');
         IDataType arrayNestedType = DataTypeFactory.get(lexer, serverInfo);
diff --git a/src/main/java/com/github/housepower/jdbc/misc/Container.java b/src/main/java/com/github/housepower/jdbc/misc/Container.java
index 694f081..8326ecc 100644
--- a/src/main/java/com/github/housepower/jdbc/misc/Container.java
+++ b/src/main/java/com/github/housepower/jdbc/misc/Container.java
@@ -12,7 +12,7 @@ public class Container<T> {
     }
 
     public void select(boolean isRight) {
-        isLeft = (isLeft == isRight) ? !isRight : isLeft;
+        isLeft = !isRight;
     }
 
     public T get() {
diff --git a/src/main/java/com/github/housepower/jdbc/serializer/BinarySerializer.java b/src/main/java/com/github/housepower/jdbc/serializer/BinarySerializer.java
index 0fa0fae..3478594 100644
--- a/src/main/java/com/github/housepower/jdbc/serializer/BinarySerializer.java
+++ b/src/main/java/com/github/housepower/jdbc/serializer/BinarySerializer.java
@@ -2,20 +2,22 @@ package com.github.housepower.jdbc.serializer;
 
 import com.github.housepower.jdbc.buffer.BuffedWriter;
 import com.github.housepower.jdbc.buffer.CompressedBuffedWriter;
-import com.github.housepower.jdbc.buffer.SocketBuffedWriter;
 import com.github.housepower.jdbc.misc.Container;
 import com.github.housepower.jdbc.settings.ClickHouseDefines;
 
 import java.io.IOException;
-import java.net.Socket;
 
 public class BinarySerializer {
     private final Container<BuffedWriter> container;
+    private final boolean enableCompress;
 
-    public BinarySerializer(Socket socket) throws IOException {
-        SocketBuffedWriter socketWriter = new SocketBuffedWriter(socket);
-        container = new Container<BuffedWriter>(socketWriter,
-            new CompressedBuffedWriter(ClickHouseDefines.SOCKET_BUFFER_SIZE, socketWriter));
+    public BinarySerializer(BuffedWriter writer, boolean enableCompress) {
+        this.enableCompress =enableCompress;
+        BuffedWriter compressBuffer = null;
+        if (enableCompress) {
+            compressBuffer = new CompressedBuffedWriter(ClickHouseDefines.SOCKET_BUFFER_SIZE, writer);
+        }
+        container = new Container<>(writer, compressBuffer);
     }
 
     public void writeVarInt(long x) throws IOException {
@@ -77,20 +79,20 @@ public class BinarySerializer {
     }
 
     public void maybeEnableCompressed() {
-        container.select(true);
+        if (enableCompress)
+            container.select(true);
     }
 
     public void maybeDisableCompressed() throws IOException {
-        container.get().flushToTarget(true);
-        container.select(false);
+        if (enableCompress) {
+            container.get().flushToTarget(true);
+            container.select(false);
+        }
     }
 
     public void writeFloat(float datum) throws IOException {
         int x = Float.floatToIntBits(datum);
-        container.get().writeBinary((byte) (x & 0xFF));
-        container.get().writeBinary((byte) ((x >>> 8) & 0xFF));
-        container.get().writeBinary((byte) ((x >>> 16) & 0xFF));
-        container.get().writeBinary((byte) ((x >>> 24) & 0xFF));
+        writeInt(x);
     }
 
     public void writeDouble(double datum) throws IOException {
diff --git a/src/main/java/com/github/housepower/jdbc/settings/ClickHouseDefines.java b/src/main/java/com/github/housepower/jdbc/settings/ClickHouseDefines.java
index 7182e29..f660bcb 100644
--- a/src/main/java/com/github/housepower/jdbc/settings/ClickHouseDefines.java
+++ b/src/main/java/com/github/housepower/jdbc/settings/ClickHouseDefines.java
@@ -12,5 +12,6 @@ public class ClickHouseDefines {
 
     public static final int MAX_BLOCK_SIZE = 1048576 * 10;
     public static int SOCKET_BUFFER_SIZE = 1048576;
-    public static int BUFFER_ROWS = 102400;
+
+    public static int COLUMN_BUFFER = 1048576;
 }
diff --git a/src/main/java/com/github/housepower/jdbc/statement/AbstractPreparedStatement.java b/src/main/java/com/github/housepower/jdbc/statement/AbstractPreparedStatement.java
index 2c56011..b4886b0 100644
--- a/src/main/java/com/github/housepower/jdbc/statement/AbstractPreparedStatement.java
+++ b/src/main/java/com/github/housepower/jdbc/statement/AbstractPreparedStatement.java
@@ -1,12 +1,17 @@
 package com.github.housepower.jdbc.statement;
 
 
-import com.github.housepower.jdbc.misc.Validate;
 import com.github.housepower.jdbc.ClickHouseConnection;
+import com.github.housepower.jdbc.misc.Validate;
 
 import java.math.BigDecimal;
 import java.net.URL;
-import java.sql.*;
+import java.sql.Array;
+import java.sql.Date;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Struct;
+import java.sql.Timestamp;
 import java.text.SimpleDateFormat;
 
 public abstract class AbstractPreparedStatement extends ClickHouseStatement implements PreparedStatement {
@@ -17,11 +22,11 @@ public abstract class AbstractPreparedStatement extends ClickHouseStatement impl
 
     protected Object[] parameters;
 
-    public AbstractPreparedStatement(ClickHouseConnection connection, String[] queryParts, int parameters) {
+    public AbstractPreparedStatement(ClickHouseConnection connection, String[] queryParts) {
         super(connection);
-
         this.queryParts = queryParts;
-        this.parameters = new Object[parameters];
+        if (queryParts != null && queryParts.length > 0)
+            this.parameters = new Object[queryParts.length];
     }
 
     @Override
@@ -79,12 +84,6 @@ public abstract class AbstractPreparedStatement extends ClickHouseStatement impl
         setObject(index, x);
     }
 
-    @Override
-    public void setObject(int index, Object x) throws SQLException {
-//        Validate.isTrue((index - 1) < parameters.length,
-//            "Index " + index + " is out of bound in PreparedStatement, max index " + (parameters.length + 1));
-        parameters[index - 1] = x;
-    }
 
     @Override
     public void setTimestamp(int index, Timestamp x) throws SQLException {
diff --git a/src/main/java/com/github/housepower/jdbc/statement/ClickHousePreparedInsertStatement.java b/src/main/java/com/github/housepower/jdbc/statement/ClickHousePreparedInsertStatement.java
index 142e420..f877296 100644
--- a/src/main/java/com/github/housepower/jdbc/statement/ClickHousePreparedInsertStatement.java
+++ b/src/main/java/com/github/housepower/jdbc/statement/ClickHousePreparedInsertStatement.java
@@ -1,9 +1,7 @@
 package com.github.housepower.jdbc.statement;
 
 import com.github.housepower.jdbc.ClickHouseConnection;
-import com.github.housepower.jdbc.misc.Slice;
 import com.github.housepower.jdbc.misc.Validate;
-import com.github.housepower.jdbc.settings.ClickHouseDefines;
 import com.github.housepower.jdbc.stream.ValuesWithParametersInputFormat;
 
 import java.sql.ResultSet;
@@ -15,24 +13,19 @@ public class ClickHousePreparedInsertStatement extends AbstractPreparedStatement
     private final int posOfData;
     private final String fullQuery;
     private final String insertQuery;
-    private final Slice []columns;
-
-	private int maxRows = 0;
 
     public ClickHousePreparedInsertStatement(int posOfData, String fullQuery, ClickHouseConnection conn)
         throws SQLException {
-        super(conn, null, computeQuestionMarkSize(fullQuery, posOfData));
+        super(conn, null);
 
-    	int parameterSize = computeQuestionMarkSize(fullQuery, posOfData);
         this.posOfData = posOfData;
         this.fullQuery = fullQuery;
         this.insertQuery = fullQuery.substring(0, posOfData);
 
-        this.columns = new Slice[parameterSize];
-        // TODO make a slicePool
-        for (int i = 0; i < parameterSize; ++i) {
-            this.columns[i] = new Slice(ClickHouseDefines.BUFFER_ROWS);
-		}
+        this.block = getSampleBlock(insertQuery);
+        this.block.initWriteBuffer();
+
+        new ValuesWithParametersInputFormat(fullQuery, posOfData).fillBlock(block);
     }
 
     @Override
@@ -43,8 +36,7 @@ public class ClickHousePreparedInsertStatement extends AbstractPreparedStatement
     @Override
     public int executeUpdate() throws SQLException {
 		addParameters();
-		return connection.sendInsertRequest(insertQuery,
-            new ValuesWithParametersInputFormat(fullQuery, posOfData, columns, maxRows));
+		return connection.sendInsertRequest(block);
     }
 
     @Override
@@ -58,26 +50,23 @@ public class ClickHousePreparedInsertStatement extends AbstractPreparedStatement
 		addParameters();
     }
 
+
+    @Override
+    public void setObject(int index, Object x) throws SQLException {
+        block.setObject(index - 1, x);
+    }
+
     private void addParameters() throws SQLException {
-    	//ensure rows size
-		for (int i = 0; i < super.parameters.length; i++) {
-		    columns[i].add(super.parameters[i]);
-		}
-		maxRows ++;
+        block.appendRow();
 	}
 
     @Override
     public void clearBatch() throws SQLException {
-    	maxRows = 0;
-    	for (int i = 0; i < columns.length; i++) {
-    	    columns[i] = columns[i].sub(0,0);
-        }
     }
 
     @Override
     public int[] executeBatch() throws SQLException {
-        Integer rows = connection.sendInsertRequest(
-            insertQuery, new ValuesWithParametersInputFormat(fullQuery, posOfData, columns, maxRows));
+        Integer rows = connection.sendInsertRequest(block);
         int[] result = new int[rows];
         Arrays.fill(result, -1);
         clearBatch();
diff --git a/src/main/java/com/github/housepower/jdbc/statement/ClickHousePreparedQueryStatement.java b/src/main/java/com/github/housepower/jdbc/statement/ClickHousePreparedQueryStatement.java
index 84007e3..cf34483 100644
--- a/src/main/java/com/github/housepower/jdbc/statement/ClickHousePreparedQueryStatement.java
+++ b/src/main/java/com/github/housepower/jdbc/statement/ClickHousePreparedQueryStatement.java
@@ -16,7 +16,7 @@ public class ClickHousePreparedQueryStatement extends AbstractPreparedStatement
     }
 
     private ClickHousePreparedQueryStatement(ClickHouseConnection conn, String[] parts) {
-        super(conn, parts, parts.length - 1);
+        super(conn, parts);
     }
 
     @Override
@@ -39,19 +39,14 @@ public class ClickHousePreparedQueryStatement extends AbstractPreparedStatement
         return executeQuery(assembleQueryPartsAndParameters());
     }
 
+    @Override
+    public void setObject(int index, Object x) throws SQLException {
+        parameters[index - 1] = x;
+    }
+
     private static String[] splitQueryByQuestionMark(String query) {
         int lastPos = 0;
         List<String> queryParts = new ArrayList<String>();
-        //        while (true) {
-        //            QuotedToken token = lexer.next();
-        //
-        //            if (token.type() == QuotedTokenType.QuestionMark) {
-        //                queryParts.add(query.substring(lastPos, (lastPos = lexer.pos()) - 1));
-        //            } else if (token.type().ordinal() >= QuotedTokenType.EndOfStream.ordinal()) {
-        //                queryParts.add(query.substring(lastPos, query.length()));
-        //                return queryParts.toArray(new String[queryParts.size()]);
-        //            }
-        //        }
         boolean inQuotes = false, inBackQuotes = false;
         for (int i = 0; i < query.length(); i++) {
             char ch = query.charAt(i);
diff --git a/src/main/java/com/github/housepower/jdbc/statement/ClickHouseStatement.java b/src/main/java/com/github/housepower/jdbc/statement/ClickHouseStatement.java
index 1ddd3b3..576d5e6 100644
--- a/src/main/java/com/github/housepower/jdbc/statement/ClickHouseStatement.java
+++ b/src/main/java/com/github/housepower/jdbc/statement/ClickHouseStatement.java
@@ -1,5 +1,12 @@
 package com.github.housepower.jdbc.statement;
 
+import com.github.housepower.jdbc.ClickHouseConnection;
+import com.github.housepower.jdbc.ClickHouseResultSet;
+import com.github.housepower.jdbc.data.Block;
+import com.github.housepower.jdbc.protocol.QueryResponse;
+import com.github.housepower.jdbc.stream.ValuesInputFormat;
+import com.github.housepower.jdbc.wrapper.SQLStatement;
+
 import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.ResultSetMetaData;
@@ -7,16 +14,14 @@ import java.sql.SQLException;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import com.github.housepower.jdbc.ClickHouseResultSet;
-import com.github.housepower.jdbc.wrapper.SQLStatement;
-import com.github.housepower.jdbc.ClickHouseConnection;
-import com.github.housepower.jdbc.protocol.QueryResponse;
-import com.github.housepower.jdbc.stream.ValuesInputFormat;
-
 public class ClickHouseStatement extends SQLStatement {
-    private static final Pattern VALUES_REGEX = Pattern.compile("[V|v][A|a][L|l][U|u][E|e][S|s]\\s*\\(");
+
+    private static final Pattern
+        VALUES_REGEX =
+        Pattern.compile("[V|v][A|a][L|l][U|u][E|e][S|s]\\s*\\(");
 
     private ResultSet lastResultSet;
+    protected Block block;
 
     protected final ClickHouseConnection connection;
 
@@ -41,7 +46,10 @@ public class ClickHouseStatement extends SQLStatement {
         if (matcher.find()) {
             lastResultSet = null;
             String insertQuery = query.substring(0, matcher.end() - 1);
-            return connection.sendInsertRequest(insertQuery, new ValuesInputFormat(matcher.end() - 1, query));
+            block = getSampleBlock(insertQuery);
+            block.initWriteBuffer();
+            new ValuesInputFormat(matcher.end() - 1, query).fillBlock(block);
+            return connection.sendInsertRequest(block);
         }
         QueryResponse response = connection.sendQueryRequest(query);
         lastResultSet = new ClickHouseResultSet(response.header(), response.data().get(), this);
@@ -70,4 +78,8 @@ public class ClickHouseStatement extends SQLStatement {
     public ResultSetMetaData getMetaData() throws SQLException {
         return lastResultSet.getMetaData();
     }
+
+    public Block getSampleBlock(final String insertQuery) throws SQLException {
+        return connection.getSampleBlock(insertQuery);
+    }
 }
diff --git a/src/main/java/com/github/housepower/jdbc/stream/InputFormat.java b/src/main/java/com/github/housepower/jdbc/stream/InputFormat.java
index 6287248..f8bd0b0 100644
--- a/src/main/java/com/github/housepower/jdbc/stream/InputFormat.java
+++ b/src/main/java/com/github/housepower/jdbc/stream/InputFormat.java
@@ -5,5 +5,5 @@ import com.github.housepower.jdbc.data.Block;
 import java.sql.SQLException;
 
 public interface InputFormat {
-    Block next(Block header, int maxRows) throws SQLException;
+    void fillBlock(Block block) throws SQLException;
 }
diff --git a/src/main/java/com/github/housepower/jdbc/stream/ValuesInputFormat.java b/src/main/java/com/github/housepower/jdbc/stream/ValuesInputFormat.java
index 4bd78c9..aceff92 100644
--- a/src/main/java/com/github/housepower/jdbc/stream/ValuesInputFormat.java
+++ b/src/main/java/com/github/housepower/jdbc/stream/ValuesInputFormat.java
@@ -1,52 +1,49 @@
 package com.github.housepower.jdbc.stream;
 
 import com.github.housepower.jdbc.data.Block;
-import com.github.housepower.jdbc.data.Column;
 import com.github.housepower.jdbc.misc.SQLLexer;
-import com.github.housepower.jdbc.misc.Slice;
 import com.github.housepower.jdbc.misc.Validate;
 
 import java.sql.SQLException;
 
 public class ValuesInputFormat implements InputFormat {
+
     private final SQLLexer lexer;
 
     public ValuesInputFormat(int pos, String data) throws SQLException {
         this.lexer = new SQLLexer(pos, data);
     }
 
-    @Override
-    public Block next(Block header, int maxRows) throws SQLException {
-        Object[][] columnData = new Object[header.columns()][maxRows];
 
-        for (int row = 0; row < maxRows; row++) {
+    @Override
+    public void fillBlock(Block block) throws SQLException {
+        int[] constIdx = new int[block.columns()];
+        for (; ; ) {
             char nextChar = lexer.character();
-            if (lexer.eof() || nextChar == ';')
-                return newValuesBlock(header, row, columnData);
+            if (lexer.eof() || nextChar == ';') {
+                break;
+            }
 
-            if (row > 0 && nextChar == ',')
+            if (nextChar == ',') {
                 nextChar = lexer.character();
+            }
             Validate.isTrue(nextChar == '(');
-            for (int column = 0; column < header.columns(); column++) {
-                if (column > 0)
+            for (int column = 0; column < block.columns(); column++) {
+                if (column > 0) {
                     Validate.isTrue(lexer.character() == ',');
-
-                columnData[column][row] = header.getByPosition(column).type().deserializeTextQuoted(lexer);
+                }
+                constIdx[column] = 1;
+                block.setConstObject(column, block.getByPosition(column).type()
+                                                 .deserializeTextQuoted(lexer));
             }
             Validate.isTrue(lexer.character() == ')');
+            block.appendRow();
         }
-        return newValuesBlock(header, maxRows, columnData);
-    }
 
-    private Block newValuesBlock(Block header, int rows, Object[][] columnData) throws SQLException {
-        Validate.isTrue(header.columns() == columnData.length, "");
-
-        Column[] columns = new Column[columnData.length];
-        for (int i = 0; i < columns.length; i++) {
-            Slice slice = new Slice(columnData[i]);
-            columns[i] = new Column(header.getByPosition(i).name(),
-                header.getByPosition(i).type(), slice.sub(0, rows));
+        for (int column = 0; column < block.columns(); column++) {
+            if (constIdx[column] > 0) {
+                block.incrIndex(column);
+            }
         }
-        return new Block(rows, columns);
     }
 }
diff --git a/src/main/java/com/github/housepower/jdbc/stream/ValuesWithParametersInputFormat.java b/src/main/java/com/github/housepower/jdbc/stream/ValuesWithParametersInputFormat.java
index 6b28538..06d62e0 100644
--- a/src/main/java/com/github/housepower/jdbc/stream/ValuesWithParametersInputFormat.java
+++ b/src/main/java/com/github/housepower/jdbc/stream/ValuesWithParametersInputFormat.java
@@ -1,83 +1,45 @@
 package com.github.housepower.jdbc.stream;
 
 import com.github.housepower.jdbc.data.Block;
-import com.github.housepower.jdbc.data.Column;
 import com.github.housepower.jdbc.misc.SQLLexer;
-import com.github.housepower.jdbc.misc.Slice;
 import com.github.housepower.jdbc.misc.Validate;
 
 import java.sql.SQLException;
 
 public class ValuesWithParametersInputFormat implements InputFormat {
 
-    private final Slice[] columns;
-    private final int maxRows;
-
     private SQLLexer lexer;
-    private Object[] parametersInLexer;
-    private boolean parsed;
-    private int currentRow;
-
-    private final int pos;
-    private final String query;
 
-    public ValuesWithParametersInputFormat(String query, int pos, Slice[] columns,
-                                           int maxRows)
+    public ValuesWithParametersInputFormat(String query, int pos)
         throws SQLException {
-        this.pos = pos;
-        this.query = query;
-        this.columns = columns;
-        this.currentRow = 0;
-        this.maxRows = maxRows;
         this.lexer = new SQLLexer(pos, query);
-        this.parsed = false;
     }
 
-    private void parseLexer(Block header) throws SQLException {
-        parametersInLexer = new Object[header.columns()];
 
+    @Override
+    public void fillBlock(Block block) throws SQLException {
+        int[] constIdx = new int[block.columns()];
         char nextChar = lexer.character();
         Validate.isTrue(nextChar == '(');
-        for (int column = 0; column < header.columns(); column++) {
+        for (int column = 0; column < block.columns(); column++) {
             if (column > 0) {
                 Validate.isTrue(lexer.character() == ',');
             }
 
             if (!lexer.isCharacter('?')) {
-                parametersInLexer[column] =
-                    header.getByPosition(column).type().deserializeTextQuoted(lexer);
+                block.setConstObject(column, block.getByPosition(column).type()
+                                                 .deserializeTextQuoted(lexer));
+                constIdx[column] = 1;
             } else {
                 lexer.character();
             }
         }
-        Validate.isTrue(lexer.character() == ')');
-        parsed = true;
-    }
 
-    @Override
-    public Block next(Block header, int blockMaxRows) throws SQLException {
-        if (!parsed) {
-            parseLexer(header);
-        }
-
-        int numRows = Math.min(blockMaxRows, maxRows - currentRow);
-        Column[] cols = new Column[header.columns()];
-
-        int j = 0;
-        for (int idx = 0; idx < cols.length; idx ++) {
-            if (parametersInLexer == null || parametersInLexer[idx] == null) {
-                cols[idx] = new Column(header.getByPosition(idx).name(),
-                                     header.getByPosition(idx).type(),
-                                     columns[j].sub(currentRow, currentRow + numRows));
-                j ++;
-            } else {
-                // set other parameters to const columns
-                cols[idx] = new Column(header.getByPosition(idx).name(),
-                                     header.getByPosition(idx).type(),
-                                     parametersInLexer[idx], maxRows);
+        for (int column = 0; column < block.columns(); column++) {
+            if (constIdx[column] > 0) {
+                block.incrIndex(column);
             }
         }
-        currentRow += numRows;
-        return new Block(numRows, cols);
+        Validate.isTrue(lexer.character() == ')');
     }
 }
diff --git a/src/main/java/examples/BatchQuery.java b/src/main/java/examples/BatchQuery.java
index 3972688..e22bc05 100644
--- a/src/main/java/examples/BatchQuery.java
+++ b/src/main/java/examples/BatchQuery.java
@@ -11,7 +11,6 @@ import java.sql.Statement;
 public class BatchQuery {
 
     public static void main(String[] args) throws Exception {
-        Class.forName("com.github.housepower.jdbc.ClickHouseDriver");
         Connection connection = DriverManager.getConnection("jdbc:clickhouse://127.0.0.1:9000");
 
         Statement stmt = connection.createStatement();
diff --git a/src/main/java/examples/ExecuteQuery.java b/src/main/java/examples/ExecuteQuery.java
index f6aaeb6..9ce1251 100644
--- a/src/main/java/examples/ExecuteQuery.java
+++ b/src/main/java/examples/ExecuteQuery.java
@@ -9,17 +9,12 @@ import java.sql.Statement;
 public class ExecuteQuery {
 
   public static void main(String[] args) throws Exception {
-    Class.forName("com.github.housepower.jdbc.ClickHouseDriver");
     Connection connection = DriverManager.getConnection("jdbc:clickhouse://127.0.0.1:9000");
 
     Statement stmt = connection.createStatement();
-    // drop table
     stmt.executeQuery("drop table if exists test_jdbc_example");
-    // create table
     stmt.executeQuery("create table test_jdbc_example(day default toDate( toDateTime(timestamp) ), timestamp UInt32, name String, impressions UInt32) Engine=MergeTree(day, (timestamp, name), 8192)");
-    // add column `costs`
     stmt.executeQuery("alter table test_jdbc_example add column costs Float32");
-    // drop the table
     stmt.executeQuery("drop table test_jdbc_example");
   }
 }
diff --git a/src/main/java/examples/SimpleQuery.java b/src/main/java/examples/SimpleQuery.java
index 27672c3..0d7c379 100644
--- a/src/main/java/examples/SimpleQuery.java
+++ b/src/main/java/examples/SimpleQuery.java
@@ -10,7 +10,6 @@ import java.sql.Statement;
 public class SimpleQuery {
 
   public static void main(String[] args) throws Exception {
-    Class.forName("com.github.housepower.jdbc.ClickHouseDriver");
     Connection connection = DriverManager.getConnection("jdbc:clickhouse://127.0.0.1:9000");
 
     Statement stmt = connection.createStatement();


Test patch:
diff --git a/src/test/java/com/github/housepower/jdbc/AbstractITest.java b/src/test/java/com/github/housepower/jdbc/AbstractITest.java
index 1ad32cb..4815735 100644
--- a/src/test/java/com/github/housepower/jdbc/AbstractITest.java
+++ b/src/test/java/com/github/housepower/jdbc/AbstractITest.java
@@ -17,7 +17,6 @@ public abstract class AbstractITest {
         }
         DriverManager.registerDriver(new ClickHouseDriver());
 
-        Class.forName("com.github.housepower.jdbc.ClickHouseDriver");
         String connectionStr = "jdbc:clickhouse://127.0.0.1:" + SERVER_PORT;
 
         // first arg is use_client_time_zone
diff --git a/src/test/java/com/github/housepower/jdbc/BatchInsertITest.java b/src/test/java/com/github/housepower/jdbc/BatchInsertITest.java
index d7f385d..eea5a4f 100644
--- a/src/test/java/com/github/housepower/jdbc/BatchInsertITest.java
+++ b/src/test/java/com/github/housepower/jdbc/BatchInsertITest.java
@@ -55,8 +55,8 @@ public class BatchInsertITest extends AbstractITest {
                 Statement statement = connection.createStatement();
 
                 statement.execute("DROP TABLE IF EXISTS test");
-                statement.execute("CREATE TABLE test(name Array(String), value Array(Float64))ENGINE=Log");
-                PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO test VALUES(?, ?)");
+                statement.execute("CREATE TABLE test(name Array(String), value Array(Float64), value2 Array(Array(Int32)))ENGINE=Log");
+                PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO test VALUES(?, ?, [[1,2,3]])");
 
                 List<String> array = Arrays.asList("aa", "bb", "cc");
                 List<Double> array2 = Arrays.asList(1.2, 2.2, 3.2);
diff --git a/src/test/java/com/github/housepower/jdbc/ClickhouseDriverRegisterTest.java b/src/test/java/com/github/housepower/jdbc/ClickhouseDriverRegisterTest.java
index 8c6dd13..1b951c3 100644
--- a/src/test/java/com/github/housepower/jdbc/ClickhouseDriverRegisterTest.java
+++ b/src/test/java/com/github/housepower/jdbc/ClickhouseDriverRegisterTest.java
@@ -18,7 +18,6 @@ public class ClickhouseDriverRegisterTest {
         properties.setProperty("password", "password");
         String mockedUrl = EmbeddedDriver.EMBEDDED_DRIVER_PREFIX + "//127.0.0.1:" + SERVER_PORT;
 
-        Class.forName("com.github.housepower.jdbc.ClickHouseDriver");
         DriverManager.registerDriver(new EmbeddedDriver());
         Assert.assertEquals(EmbeddedDriver.MOCKED_CONNECTION, DriverManager.getConnection(mockedUrl, properties));
     }
diff --git a/src/test/java/com/github/housepower/jdbc/ConnectionParamITest.java b/src/test/java/com/github/housepower/jdbc/ConnectionParamITest.java
index 5474c44..9dbb969 100644
--- a/src/test/java/com/github/housepower/jdbc/ConnectionParamITest.java
+++ b/src/test/java/com/github/housepower/jdbc/ConnectionParamITest.java
@@ -13,7 +13,6 @@ public class ConnectionParamITest {
 
     @Test(expected = SQLException.class)
     public void successfullyMaxRowsToRead() throws Exception {
-        Class.forName("com.github.housepower.jdbc.ClickHouseDriver");
         Connection connection = DriverManager.getConnection("jdbc:clickhouse://127.0.0.1?max_rows_to_read=1&connect_timeout=10");
         Statement statement = connection.createStatement();
         ResultSet rs = statement.executeQuery("SELECT 1 UNION ALL SELECT 2");
@@ -26,7 +25,6 @@ public class ConnectionParamITest {
 
     @Test
     public void successfullyUrlParser() throws Exception {
-        Class.forName("com.github.housepower.jdbc.ClickHouseDriver");
         String url = "jdbc:clickhouse://127.0.0.1/system?min_insert_block_size_rows=1000&connect_timeout=50";
         ClickHouseConfig config = new ClickHouseConfig(url , new Properties());
         Assert.assertEquals(config.database(), "system");
diff --git a/src/test/java/com/github/housepower/jdbc/InsertComplexTypeITest.java b/src/test/java/com/github/housepower/jdbc/InsertComplexTypeITest.java
index 49d92a4..c8e9a1e 100644
--- a/src/test/java/com/github/housepower/jdbc/InsertComplexTypeITest.java
+++ b/src/test/java/com/github/housepower/jdbc/InsertComplexTypeITest.java
@@ -20,11 +20,14 @@ public class InsertComplexTypeITest extends AbstractITest {
                 Statement statement = connection.createStatement();
 
                 statement.executeQuery("DROP TABLE IF EXISTS test");
-                statement.executeQuery("CREATE TABLE test(test_Array Array(UInt8))ENGINE=Log");
-                statement.executeQuery("INSERT INTO test VALUES ([1, 2, 3, 4])");
+                statement.executeQuery("CREATE TABLE test(test_Array Array(UInt8), test_Array2 Array(Array(String)))ENGINE=Log");
+                statement.executeQuery("INSERT INTO test VALUES ([1, 2, 3, 4], [ ['1', '2'] ])");
                 ResultSet rs = statement.executeQuery("SELECT * FROM test");
                 Assert.assertTrue(rs.next());
                 Assert.assertArrayEquals((Object[]) rs.getArray(1).getArray(), new Short[] {1, 2, 3, 4});
+                Object[] objects = (Object[]) rs.getArray(2).getArray();
+                ClickHouseArray array = (ClickHouseArray) objects[0];
+                Assert.assertArrayEquals((Object [])array.getArray(), new Object[]{"1","2"});
                 Assert.assertFalse(rs.next());
                 statement.executeQuery("DROP TABLE IF EXISTS test");
             }
@@ -43,7 +46,7 @@ public class InsertComplexTypeITest extends AbstractITest {
                 statement.executeQuery("INSERT INTO test VALUES('abc')");
 
                 PreparedStatement stmt = connection.prepareStatement("INSERT INTO test VALUES(?)");
-                stmt.setString(1, "abc");
+                stmt.setObject(1, "abc");
                 stmt.executeUpdate();
 
                 ResultSet rs = statement.executeQuery("SELECT str, COUNT(0) FROM test group by str");
diff --git a/src/test/java/com/github/housepower/jdbc/InsertSimpleTypeITest.java b/src/test/java/com/github/housepower/jdbc/InsertSimpleTypeITest.java
index 7fda349..a53ae8d 100644
--- a/src/test/java/com/github/housepower/jdbc/InsertSimpleTypeITest.java
+++ b/src/test/java/com/github/housepower/jdbc/InsertSimpleTypeITest.java
@@ -4,11 +4,10 @@ import org.junit.Assert;
 import org.junit.Test;
 
 import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.sql.*;
+import java.sql.Connection;
 import java.sql.Date;
-import java.text.SimpleDateFormat;
-import java.util.*;
+import java.sql.ResultSet;
+import java.sql.Statement;
 import java.util.concurrent.TimeUnit;
 
 public class InsertSimpleTypeITest extends AbstractITest {
@@ -21,8 +20,10 @@ public class InsertSimpleTypeITest extends AbstractITest {
                 Statement statement = connection.createStatement();
 
                 statement.executeQuery("DROP TABLE IF EXISTS test");
-                statement.executeQuery("CREATE TABLE test(test_uInt8 UInt8, test_Int8 Int8)ENGINE=Log");
+                    statement.executeQuery("CREATE TABLE test(test_uInt8 UInt8, test_Int8 Int8)ENGINE=Log");
+
                 statement.executeQuery("INSERT INTO test VALUES(" + 255 + "," + Byte.MIN_VALUE + ")");
+
                 ResultSet rs = statement.executeQuery("SELECT * FROM test ORDER BY test_uInt8");
                 Assert.assertTrue(rs.next());
                 int aa = rs.getInt(1);
diff --git a/src/test/java/com/github/housepower/jdbc/PreparedStatementITest.java b/src/test/java/com/github/housepower/jdbc/PreparedStatementITest.java
index 7575cc4..c45c3c0 100644
--- a/src/test/java/com/github/housepower/jdbc/PreparedStatementITest.java
+++ b/src/test/java/com/github/housepower/jdbc/PreparedStatementITest.java
@@ -3,10 +3,12 @@ package com.github.housepower.jdbc;
 import org.junit.Assert;
 import org.junit.Test;
 
-import java.sql.*;
+import java.sql.Connection;
 import java.sql.Date;
-import java.util.*;
-import java.util.concurrent.TimeUnit;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.sql.Timestamp;
 
 public class PreparedStatementITest extends AbstractITest {
 
diff --git a/src/test/java/com/github/housepower/jdbc/QueryComplexTypeITest.java b/src/test/java/com/github/housepower/jdbc/QueryComplexTypeITest.java
index f7a0ccc..19ee60e 100644
--- a/src/test/java/com/github/housepower/jdbc/QueryComplexTypeITest.java
+++ b/src/test/java/com/github/housepower/jdbc/QueryComplexTypeITest.java
@@ -1,12 +1,16 @@
 package com.github.housepower.jdbc;
 
-import java.sql.*;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
 import org.junit.Assert;
 import org.junit.Test;
 
+import java.sql.Array;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.sql.Struct;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
 public class QueryComplexTypeITest extends AbstractITest {
 
     @Test
@@ -84,6 +88,37 @@ public class QueryComplexTypeITest extends AbstractITest {
 
     @Test
     public void successfullyArray() throws Exception {
+        withNewConnection(new WithConnection() {
+            @Override
+            public void apply(Connection connection) throws Exception {
+                Statement statement = connection.createStatement();
+
+                // Array(UInt8)
+                ResultSet rs = statement.executeQuery("SELECT [[1], [2], [3], [4,5,6]] from numbers(10)");
+
+                for (int i = 0; i < 10; i ++) {
+                    Assert.assertTrue(rs.next());
+                    Array array1 = rs.getArray(1);
+                    Object[] objects = (Object[]) array1.getArray();
+                    Assert.assertEquals(objects.length, 4);
+
+                    ClickHouseArray a1 = (ClickHouseArray)(objects[0]);
+                    ClickHouseArray a2 = (ClickHouseArray)(objects[1]);
+                    ClickHouseArray a3 = (ClickHouseArray)(objects[2]);
+                    ClickHouseArray a4 = (ClickHouseArray)(objects[3]);
+
+                    Assert.assertArrayEquals((Object[]) a1.getArray(), new Short[]{(short) 1});
+                    Assert.assertArrayEquals((Object[]) a2.getArray(), new Short[]{(short) 2});
+                    Assert.assertArrayEquals((Object[]) a3.getArray(), new Short[]{(short) 3});
+                    Assert.assertArrayEquals((Object[]) a4.getArray(), new Short[]{(short) 4, (short)5, (short)6});
+                }
+                Assert.assertFalse(rs.next());
+            }
+        });
+    }
+
+    @Test
+    public void successfullyArrayJoin() throws Exception {
         withNewConnection(new WithConnection() {
             @Override
             public void apply(Connection connection) throws Exception {
@@ -95,11 +130,14 @@ public class QueryComplexTypeITest extends AbstractITest {
                 Assert.assertTrue(rs.next());
                 Array array1 = rs.getArray(1);
                 Assert.assertNotNull(array1);
-                Assert.assertArrayEquals((Object[]) (array1.getArray()), new Short[]{ (short)1,  (short)2,  (short)3});
+                Assert.assertArrayEquals((Object[]) (array1.getArray()),
+                                         new Short[]{(short) 1, (short) 2, (short) 3});
+
                 Assert.assertTrue(rs.next());
                 Array array2 = rs.getArray(1);
                 Assert.assertNotNull(array2);
-                Assert.assertArrayEquals((Object[]) array2.getArray(), new Number[]{ (short) 4, (short) 5});
+                Assert.assertArrayEquals((Object[]) array2.getArray(),
+                                         new Number[]{(short) 4, (short) 5});
             }
         });
     }
@@ -111,25 +149,30 @@ public class QueryComplexTypeITest extends AbstractITest {
             public void apply(Connection connection) throws Exception {
                 Statement statement = connection.createStatement();
 
-                ResultSet rs = statement.executeQuery("SELECT arrayJoin([[(1,'3'), (2,'4')],[(3,'5')]])");
+                ResultSet
+                    rs =
+                    statement.executeQuery("SELECT arrayJoin([[(1,'3'), (2,'4')],[(3,'5')]])");
 
                 Assert.assertTrue(rs.next());
                 Array array1 = rs.getArray(1);
                 Assert.assertNotNull(array1);
 
-                Object []row1 = (Object [])array1.getArray();
+                Object[] row1 = (Object[]) array1.getArray();
                 Assert.assertTrue(row1.length == 2);
-                Assert.assertEquals(((Short)(((ClickHouseStruct) row1[0]).getAttributes()[0])).intValue(), 1);
+                Assert.assertEquals(
+                    ((Short) (((ClickHouseStruct) row1[0]).getAttributes()[0])).intValue(), 1);
                 Assert.assertEquals(((ClickHouseStruct) row1[0]).getAttributes()[1], "3");
 
-                Assert.assertEquals(((Short)(((ClickHouseStruct) row1[1]).getAttributes()[0])).intValue(), 2);
+                Assert.assertEquals(
+                    ((Short) (((ClickHouseStruct) row1[1]).getAttributes()[0])).intValue(), 2);
                 Assert.assertEquals(((ClickHouseStruct) row1[1]).getAttributes()[1], "4");
 
                 Assert.assertTrue(rs.next());
                 Array array2 = rs.getArray(1);
-                Object []row2 = (Object [])array2.getArray();
+                Object[] row2 = (Object[]) array2.getArray();
                 Assert.assertTrue(row2.length == 1);
-                Assert.assertEquals(((Short)(((ClickHouseStruct) row2[0]).getAttributes()[0])).intValue(), 3);
+                Assert.assertEquals(
+                    ((Short) (((ClickHouseStruct) row2[0]).getAttributes()[0])).intValue(), 3);
                 Assert.assertEquals((((ClickHouseStruct) row2[0]).getAttributes()[1]), "5");
 
                 Assert.assertFalse(rs.next());
@@ -144,20 +187,29 @@ public class QueryComplexTypeITest extends AbstractITest {
             public void apply(Connection connection) throws Exception {
                 Statement statement = connection.createStatement();
 
-                ResultSet rs = statement.executeQuery("SELECT [[1.1, 1.2], [2.1, 2.2], [3.1, 3.2]] AS v, toTypeName(v)");
-
-                Assert.assertTrue(rs.next());
-                Array array1 = rs.getArray(1);
-                Assert.assertNotNull(array1);
-
-                Double [][]res = new Double[][]{ {1.1, 1.2}, {2.1, 2.2}, {3.1, 3.2} };
-
-                Object[] arr = (Object[]) (rs.getArray(1).getArray());
-                Assert.assertArrayEquals((Object[]) ((ClickHouseArray)(arr[0])).getArray(), res[0]);
-                Assert.assertArrayEquals((Object[]) ((ClickHouseArray)(arr[1])).getArray(), res[1]);
-                Assert.assertArrayEquals((Object[]) ((ClickHouseArray)(arr[2])).getArray(), res[2]);
-                Assert.assertEquals(rs.getString(2), "Array(Array(Float64))");
+                ResultSet
+                    rs =
+                    statement.executeQuery(
+                        "SELECT [[1.1, 1.2], [2.1, 2.2], [3.1, 3.2]] AS v, toTypeName(v) from numbers(10)");
+
+                for (int i = 0; i < 10; i++) {
+                    Assert.assertTrue(rs.next());
+                    Array array1 = rs.getArray(1);
+                    Assert.assertNotNull(array1);
+
+                    Double[][] res = new Double[][]{{1.1, 1.2}, {2.1, 2.2}, {3.1, 3.2}};
+
+                    Object[] arr = (Object[]) (rs.getArray(1).getArray());
+                    Assert.assertArrayEquals((Object[]) ((ClickHouseArray) (arr[0])).getArray(),
+                                             res[0]);
+                    Assert.assertArrayEquals((Object[]) ((ClickHouseArray) (arr[1])).getArray(),
+                                             res[1]);
+                    Assert.assertArrayEquals((Object[]) ((ClickHouseArray) (arr[2])).getArray(),
+                                             res[2]);
+                    Assert.assertEquals(rs.getString(2), "Array(Array(Float64))");
+                }
                 Assert.assertFalse(rs.next());
+
             }
         });
     }
@@ -188,12 +240,13 @@ public class QueryComplexTypeITest extends AbstractITest {
 
                 Assert.assertTrue(rs.next());
                 Struct struct = (Struct) rs.getObject(1);
-                Assert.assertEquals(struct.getAttributes(), new Object[]{(long)1, "2"});
+                Assert.assertEquals(struct.getAttributes(), new Object[]{(long) 1, "2"});
 
                 Map<String, Class<?>> attrNameWithClass = new LinkedHashMap<String, Class<?>>();
                 attrNameWithClass.put("_2", String.class);
                 attrNameWithClass.put("_1", Long.class);
-                Assert.assertEquals(struct.getAttributes(attrNameWithClass), new Object[]{"2", (long)1});
+                Assert.assertEquals(struct.getAttributes(attrNameWithClass),
+                                    new Object[]{"2", (long) 1});
             }
         });
     }
diff --git a/src/test/java/com/github/housepower/jdbc/benchmark/AbstractIBenchmark.java b/src/test/java/com/github/housepower/jdbc/benchmark/AbstractIBenchmark.java
index e411cae..b0950a5 100644
--- a/src/test/java/com/github/housepower/jdbc/benchmark/AbstractIBenchmark.java
+++ b/src/test/java/com/github/housepower/jdbc/benchmark/AbstractIBenchmark.java
@@ -1,20 +1,50 @@
 package com.github.housepower.jdbc.benchmark;
 
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
 import java.sql.Connection;
 import java.sql.Driver;
 import java.sql.DriverManager;
 import java.util.Enumeration;
+import java.util.concurrent.TimeUnit;
 
 
-public abstract class AbstractIBenchmark {
-
-    private static final int SERVER_PORT = Integer.valueOf(System.getProperty("CLICK_HOUSE_SERVER_PORT", "9000"));
-    private static final int SERVER_HTTP_PORT = Integer.valueOf(System.getProperty("CLICK_HOUSE_SERVER_HTTP_PORT", "8123"));
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Thread)
+public class AbstractIBenchmark {
+    private static final int
+        SERVER_PORT =
+        Integer.valueOf(System.getProperty("CLICK_HOUSE_SERVER_PORT", "9000"));
+    private static final int
+        SERVER_HTTP_PORT =
+        Integer.valueOf(System.getProperty("CLICK_HOUSE_SERVER_HTTP_PORT", "8123"));
 
     private final Driver httpDriver = new ru.yandex.clickhouse.ClickHouseDriver();
     private final Driver nativeDriver = new com.github.housepower.jdbc.ClickHouseDriver();
 
-    protected void withConnection(WithConnection withConnection, ConnectionType connectionType) throws Exception {
+
+    public static void main(String[] args) throws RunnerException {
+        Options opt = new OptionsBuilder()
+                          .include(".*")
+                          .warmupIterations(0)
+                          .measurementIterations(1)
+                          .forks(2)
+                          .build();
+
+        new Runner(opt).run();
+    }
+
+    protected void withConnection(WithConnection withConnection, ConnectionType connectionType)
+        throws Exception {
         int port = SERVER_PORT;
 
         Enumeration<Driver> drivers = DriverManager.getDrivers();
@@ -42,11 +72,14 @@ public abstract class AbstractIBenchmark {
         }
     }
 
+
     public interface WithConnection {
+
         void apply(Connection connection) throws Exception;
     }
 
     public enum ConnectionType {
         NATIVE, HTTP
     }
-}
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/github/housepower/jdbc/benchmark/AbstractInsertIBenchmark.java b/src/test/java/com/github/housepower/jdbc/benchmark/AbstractInsertIBenchmark.java
new file mode 100644
index 0000000..aed168c
--- /dev/null
+++ b/src/test/java/com/github/housepower/jdbc/benchmark/AbstractInsertIBenchmark.java
@@ -0,0 +1,52 @@
+package com.github.housepower.jdbc.benchmark;
+
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Thread)
+public class AbstractInsertIBenchmark extends AbstractIBenchmark{
+    @Param({"20", "50"})
+    protected int columnNum = 20;
+    @Param({"200000", "500000"})
+    protected int batchSize = 200000;
+
+    AtomicInteger tableMaxId = new AtomicInteger();
+
+    //drop table, create table
+    protected void wideColumnPrepare(Connection connection, String columnType) throws SQLException {
+        int tableId = tableMaxId.incrementAndGet();
+        String testTable = "test_" + tableId;
+        Statement stmt = connection.createStatement();
+        stmt.executeQuery("DROP TABLE IF EXISTS " + testTable);
+        String createSQL = "CREATE TABLE " + testTable + " (";
+        for (int i = 0; i < columnNum; i++) {
+            createSQL += "col_" + i + " "  + columnType ;
+            if (i + 1 != columnNum) {
+                createSQL += ",\n";
+            }
+        }
+        stmt.executeQuery(createSQL + ")Engine = Log");
+    }
+
+    protected void wideColumnAfter(Connection connection) throws SQLException {
+        Statement stmt = connection.createStatement();
+        stmt.executeQuery("DROP TABLE " + getTableName());
+    }
+
+    protected String getTableName() {
+        return "test_" + tableMaxId.get();
+    }
+}
diff --git a/src/test/java/com/github/housepower/jdbc/benchmark/InsertIBenchmark.java b/src/test/java/com/github/housepower/jdbc/benchmark/InsertIBenchmark.java
index a9e0cad..d0ced5f 100644
--- a/src/test/java/com/github/housepower/jdbc/benchmark/InsertIBenchmark.java
+++ b/src/test/java/com/github/housepower/jdbc/benchmark/InsertIBenchmark.java
@@ -1,14 +1,9 @@
 package com.github.housepower.jdbc.benchmark;
 
-import com.carrotsearch.junitbenchmarks.BenchmarkOptions;
-import com.carrotsearch.junitbenchmarks.BenchmarkRule;
-
 import org.junit.Assert;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TestRule;
+import org.openjdk.jmh.annotations.Benchmark;
 
-import java.sql.Connection;
 import java.sql.Date;
 import java.sql.PreparedStatement;
 import java.sql.Statement;
@@ -17,49 +12,42 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  */
-public class InsertIBenchmark extends AbstractIBenchmark{
+public class InsertIBenchmark extends AbstractInsertIBenchmark {
     AtomicInteger tableMaxId = new AtomicInteger();
 
-    @Rule
-    public TestRule benchmarkRun = new BenchmarkRule();
-
-    public WithConnection benchInsert = new WithConnection(){
-        @Override
-        public void apply(Connection connection) throws Exception {
-            int batchSize = 1000000;
-            Timestamp ts = new Timestamp(System.currentTimeMillis());
-            Date date = new Date(ts.getTime());
+    public WithConnection benchInsert = connection -> {
+        Timestamp ts = new Timestamp(System.currentTimeMillis());
+        Date date = new Date(ts.getTime());
 
-            Statement stmt = connection.createStatement();
+        Statement stmt = connection.createStatement();
 
-            int tableId = tableMaxId.getAndIncrement();
-            String testTable = "test_" + tableId;
+        int tableId = tableMaxId.getAndIncrement();
+        String testTable = "test_" + tableId;
 
-            stmt.executeQuery("DROP TABLE IF EXISTS " + testTable);
-            stmt.executeQuery("CREATE TABLE " + testTable +" (number UInt32, name String, birthTime DateTime, birthDay Date) Engine = Log");
-            PreparedStatement pstmt = connection.prepareStatement("INSERT INTO "  + testTable +" values(?, ?, ?, ?)");
+        stmt.executeQuery("DROP TABLE IF EXISTS " + testTable);
+        stmt.executeQuery("CREATE TABLE " + testTable +" (number UInt32, name String, birthTime DateTime, birthDay Date) Engine = Log");
+        PreparedStatement pstmt = connection.prepareStatement("INSERT INTO "  + testTable +" values(?, ?, ?, ?)");
 
 
-            for (int i = 0; i < batchSize; i++) {
-                pstmt.setInt(1, i);
-                pstmt.setString(2, "i_am_a_string");
-                pstmt.setTimestamp(3, ts);
-                pstmt.setDate(4, date);
-                pstmt.addBatch();
-            }
-            int []res = pstmt.executeBatch();
-            Assert.assertEquals(res.length, batchSize);
-            stmt.executeQuery("DROP TABLE " + testTable);
+        for (int i = 0; i < batchSize; i++) {
+            pstmt.setInt(1, i);
+            pstmt.setString(2, "i_am_a_string");
+            pstmt.setTimestamp(3, ts);
+            pstmt.setDate(4, date);
+            pstmt.addBatch();
         }
+        int []res = pstmt.executeBatch();
+        Assert.assertEquals(res.length, batchSize);
+        stmt.executeQuery("DROP TABLE " + testTable);
     };
 
-    @BenchmarkOptions(benchmarkRounds = 2, warmupRounds = 0, concurrency = 1)
+    @Benchmark
     @Test
     public void benchInsertNative() throws Exception {
         withConnection(benchInsert, ConnectionType.NATIVE);
     }
 
-    @BenchmarkOptions(benchmarkRounds = 2, warmupRounds = 0, concurrency = 1)
+    @Benchmark
     @Test
     public void benchInsertHttp() throws Exception {
         withConnection(benchInsert, ConnectionType.HTTP);
diff --git a/src/test/java/com/github/housepower/jdbc/benchmark/RowBinaryDoubleIBenchmark.java b/src/test/java/com/github/housepower/jdbc/benchmark/RowBinaryDoubleIBenchmark.java
new file mode 100644
index 0000000..e4af4a5
--- /dev/null
+++ b/src/test/java/com/github/housepower/jdbc/benchmark/RowBinaryDoubleIBenchmark.java
@@ -0,0 +1,58 @@
+package com.github.housepower.jdbc.benchmark;
+
+import com.google.common.base.Strings;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.Benchmark;
+
+import java.sql.PreparedStatement;
+
+import ru.yandex.clickhouse.ClickHouseStatement;
+import ru.yandex.clickhouse.domain.ClickHouseFormat;
+
+/**
+ */
+public class RowBinaryDoubleIBenchmark extends AbstractInsertIBenchmark {
+    private String columnType = "Float64";
+
+    @Benchmark
+    @Test
+    public void benchInsertNative() throws Exception {
+        withConnection(connection -> {
+            wideColumnPrepare(connection, columnType);
+            String params = Strings.repeat("?, ", columnNum);
+            PreparedStatement
+                pstmt = connection.prepareStatement("INSERT INTO " + getTableName() + " values(" + params.substring(0, params.length() - 2) + ")");
+            for (int i = 0; i < batchSize; i++) {
+                for (int j = 0; j < columnNum; j++ ) {
+                    pstmt.setDouble(j + 1, j + 1.0);
+                }
+                pstmt.addBatch();
+            }
+            int []res = pstmt.executeBatch();
+            Assert.assertEquals(res.length, batchSize);
+            wideColumnAfter(connection);
+
+        }, ConnectionType.NATIVE);
+    }
+
+    @Benchmark
+    @Test
+    public void benchInsertHttpRowBinary() throws Exception {
+        withConnection(connection -> {
+            wideColumnPrepare(connection, columnType);
+            ClickHouseStatement sth = (ClickHouseStatement) connection.createStatement();
+            sth.write().send("INSERT INTO " + getTableName(), stream -> {
+                for (int i = 0; i < batchSize; i++) {
+                    for (int j = 0; j < columnNum; j++ ) {
+                        stream.writeFloat64(j + 1.0);
+                    }
+                }
+            }, ClickHouseFormat.RowBinary);
+
+            wideColumnAfter(connection);
+        }, ConnectionType.HTTP);
+    }
+
+}
diff --git a/src/test/java/com/github/housepower/jdbc/benchmark/RowBinaryIntIBenchmark.java b/src/test/java/com/github/housepower/jdbc/benchmark/RowBinaryIntIBenchmark.java
new file mode 100644
index 0000000..46d25c1
--- /dev/null
+++ b/src/test/java/com/github/housepower/jdbc/benchmark/RowBinaryIntIBenchmark.java
@@ -0,0 +1,58 @@
+package com.github.housepower.jdbc.benchmark;
+
+import com.google.common.base.Strings;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.Benchmark;
+
+import java.sql.PreparedStatement;
+
+import ru.yandex.clickhouse.ClickHouseStatement;
+import ru.yandex.clickhouse.domain.ClickHouseFormat;
+
+/**
+ */
+public class RowBinaryIntIBenchmark extends AbstractInsertIBenchmark {
+    private String columnType = "Int32";
+
+    @Benchmark
+    @Test
+    public void benchInsertNative() throws Exception {
+        withConnection(connection -> {
+            wideColumnPrepare(connection, columnType);
+            String params = Strings.repeat("?, ", columnNum);
+            PreparedStatement
+                pstmt = connection.prepareStatement("INSERT INTO " + getTableName() + " values(" + params.substring(0, params.length() - 2) + ")");
+            for (int i = 0; i < batchSize; i++) {
+                for (int j = 0; j < columnNum; j++ ) {
+                    pstmt.setObject(j + 1, j + 1);
+                }
+                pstmt.addBatch();
+            }
+            int []res = pstmt.executeBatch();
+            Assert.assertEquals(res.length, batchSize);
+
+            wideColumnAfter(connection);
+        }, ConnectionType.NATIVE);
+    }
+
+    @Benchmark
+    @Test
+    public void benchInsertHttpRowBinary() throws Exception {
+        withConnection(connection -> {
+            wideColumnPrepare(connection, columnType);
+            ClickHouseStatement sth = (ClickHouseStatement) connection.createStatement();
+            sth.write().send("INSERT INTO " + getTableName(), stream -> {
+                for (int i = 0; i < batchSize; i++) {
+                    for (int j = 0; j < columnNum; j++ ) {
+                        stream.writeInt32(j + 1);
+                    }
+                }
+            }, ClickHouseFormat.RowBinary);
+
+            wideColumnAfter(connection);
+        }, ConnectionType.HTTP);
+    }
+
+}
diff --git a/src/test/java/com/github/housepower/jdbc/benchmark/RowBinaryStringIBenchmark.java b/src/test/java/com/github/housepower/jdbc/benchmark/RowBinaryStringIBenchmark.java
new file mode 100644
index 0000000..e141556
--- /dev/null
+++ b/src/test/java/com/github/housepower/jdbc/benchmark/RowBinaryStringIBenchmark.java
@@ -0,0 +1,57 @@
+package com.github.housepower.jdbc.benchmark;
+
+import com.google.common.base.Strings;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.Benchmark;
+
+import java.sql.PreparedStatement;
+
+import ru.yandex.clickhouse.ClickHouseStatement;
+import ru.yandex.clickhouse.domain.ClickHouseFormat;
+
+/**
+ */
+public class RowBinaryStringIBenchmark extends AbstractInsertIBenchmark{
+    private String columnType = "String";
+
+    @Benchmark
+    @Test
+    public void benchInsertNative() throws Exception {
+        withConnection(connection -> {
+            wideColumnPrepare(connection, columnType);
+            String params = Strings.repeat("?, ", columnNum);
+            PreparedStatement
+                pstmt = connection.prepareStatement("INSERT INTO " + getTableName() + " values(" + params.substring(0, params.length() - 2) + ")");
+            for (int i = 0; i < batchSize; i++) {
+                for (int j = 0; j < columnNum; j++ ) {
+                    pstmt.setObject(j + 1, j + 1 + "");
+                }
+                pstmt.addBatch();
+            }
+            int []res = pstmt.executeBatch();
+            Assert.assertEquals(res.length, batchSize);
+            wideColumnAfter(connection);
+        }, ConnectionType.NATIVE);
+    }
+
+    @Benchmark
+    @Test
+    public void benchInsertHttpRowBinary() throws Exception {
+        withConnection(connection -> {
+            wideColumnPrepare(connection, columnType);
+            ClickHouseStatement sth = (ClickHouseStatement) connection.createStatement();
+            sth.write().send("INSERT INTO " + getTableName(), stream -> {
+                for (int i = 0; i < batchSize; i++) {
+                    for (int j = 0; j < columnNum; j++ ) {
+                        stream.writeString(j + 1 + "");
+                    }
+                }
+            }, ClickHouseFormat.RowBinary);
+
+            wideColumnAfter(connection);
+        }, ConnectionType.HTTP);
+    }
+
+}
diff --git a/src/test/java/com/github/housepower/jdbc/benchmark/SelectIBenchmark.java b/src/test/java/com/github/housepower/jdbc/benchmark/SelectIBenchmark.java
index 0a1ac6d..4833686 100644
--- a/src/test/java/com/github/housepower/jdbc/benchmark/SelectIBenchmark.java
+++ b/src/test/java/com/github/housepower/jdbc/benchmark/SelectIBenchmark.java
@@ -1,12 +1,9 @@
 package com.github.housepower.jdbc.benchmark;
 
-import com.carrotsearch.junitbenchmarks.BenchmarkOptions;
-import com.carrotsearch.junitbenchmarks.BenchmarkRule;
-
 import org.junit.Assert;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TestRule;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Param;
 
 import java.sql.ResultSet;
 import java.sql.Statement;
@@ -14,32 +11,29 @@ import java.sql.Statement;
 /**
  */
 public class SelectIBenchmark extends AbstractIBenchmark {
-
-    @Rule
-    public TestRule benchmarkRun = new BenchmarkRule();
-
+    @Param({"500000", "10000000"})
+    protected long selectNumber = 100000;
     public WithConnection benchSelect = connection -> {
         long sum = 0;
         Statement statement = connection.createStatement();
-        long number = 1000000;
-        ResultSet rs = statement.executeQuery(String.format("SELECT number as n1, 'i_am_a_string' , now(), today() from numbers(%d)", number));
+        ResultSet rs = statement.executeQuery(String.format("SELECT number as n1, 'i_am_a_string' , now(), today() from numbers(%d)", selectNumber));
         while (rs.next()) {
-            sum += rs.getInt(1);
+            sum += rs.getLong(1);
 
             rs.getString(2);
             rs.getTimestamp(3);
             rs.getDate(4);
         }
-        Assert.assertEquals((number-1) * number / 2, sum);
+        Assert.assertEquals((selectNumber-1) * selectNumber / 2, sum);
     };
 
-    @BenchmarkOptions(benchmarkRounds = 2, warmupRounds = 0)
+    @Benchmark
     @Test
     public void benchSelectNative() throws Exception {
         withConnection(benchSelect, ConnectionType.NATIVE);
     }
 
-    @BenchmarkOptions(benchmarkRounds = 2, warmupRounds = 0)
+    @Benchmark
     @Test
     public void benchSelectHTTP() throws Exception {
         withConnection(benchSelect, ConnectionType.HTTP);
diff --git a/src/test/java/com/github/housepower/jdbc/benchmark/WideColumnDoubleInsertIBenchmark.java b/src/test/java/com/github/housepower/jdbc/benchmark/WideColumnDoubleInsertIBenchmark.java
new file mode 100644
index 0000000..7ff0a9f
--- /dev/null
+++ b/src/test/java/com/github/housepower/jdbc/benchmark/WideColumnDoubleInsertIBenchmark.java
@@ -0,0 +1,49 @@
+package com.github.housepower.jdbc.benchmark;
+
+import com.google.common.base.Strings;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.Benchmark;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+
+/**
+ */
+public class WideColumnDoubleInsertIBenchmark extends AbstractInsertIBenchmark {
+    @Benchmark
+    @Test
+    public void benchInsertNative() throws Exception {
+        withConnection(benchInsert, ConnectionType.NATIVE);
+    }
+
+    @Benchmark
+    @Test
+    public void benchInsertHttp() throws Exception {
+        withConnection(benchInsert, ConnectionType.HTTP);
+    }
+
+    public WithConnection benchInsert = new WithConnection(){
+        @Override
+        public void apply(Connection connection) throws Exception {
+            wideColumnPrepare(connection, "Float64");
+
+            String params = Strings.repeat("?, ", columnNum);
+            PreparedStatement pstmt = connection.prepareStatement("INSERT INTO "  + getTableName() +" values("   + params.substring(0, params.length()-2) + ")");
+
+
+            for (int i = 0; i < batchSize; i++) {
+                for (int j = 0; j < columnNum; j++ ) {
+                    pstmt.setDouble(j + 1, j + 1.2);
+                }
+                pstmt.addBatch();
+            }
+            int []res = pstmt.executeBatch();
+            Assert.assertEquals(res.length, batchSize);
+
+            wideColumnAfter(connection);
+        }
+    };
+
+}
diff --git a/src/test/java/com/github/housepower/jdbc/benchmark/WideColumnInsertIBenchmark.java b/src/test/java/com/github/housepower/jdbc/benchmark/WideColumnInsertIBenchmark.java
deleted file mode 100644
index 1c1ef45..0000000
--- a/src/test/java/com/github/housepower/jdbc/benchmark/WideColumnInsertIBenchmark.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package com.github.housepower.jdbc.benchmark;
-
-import com.google.common.base.Strings;
-
-import com.carrotsearch.junitbenchmarks.BenchmarkOptions;
-import com.carrotsearch.junitbenchmarks.BenchmarkRule;
-
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.Statement;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- */
-public class WideColumnInsertIBenchmark extends AbstractIBenchmark{
-    private static final int COLUMN_NUM = 50;
-    private static final int BATCH_SZIE = 200000;
-
-    AtomicInteger tableMaxId = new AtomicInteger();
-
-    @Rule
-    public TestRule benchmarkRun = new BenchmarkRule();
-
-    public WithConnection benchInsert = new WithConnection(){
-        @Override
-        public void apply(Connection connection) throws Exception {
-            Statement stmt = connection.createStatement();
-
-            int tableId = tableMaxId.getAndIncrement();
-            String testTable = "test2_" + tableId;
-
-
-            stmt.executeQuery("DROP TABLE IF EXISTS " + testTable);
-            String createSQL = "CREATE TABLE " + testTable + " (";
-            for (int i = 0; i < COLUMN_NUM; i++) {
-                createSQL += "col_" + i + " String";
-                if (i + 1 != COLUMN_NUM) {
-                    createSQL += ",\n";
-                }
-            }
-            createSQL += ") Engine = Log";
-
-            stmt.executeQuery(createSQL);
-            String params = Strings.repeat("?, ", COLUMN_NUM);
-            PreparedStatement pstmt = connection.prepareStatement("INSERT INTO "  + testTable +" values("   + params.substring(0, params.length()-2) + ")");
-
-
-            for (int i = 0; i < BATCH_SZIE; i++) {
-                for (int j = 0; j < COLUMN_NUM; j++ ) {
-                    pstmt.setString(j + 1, j + 1 + "");
-                }
-                pstmt.addBatch();
-            }
-            int []res = pstmt.executeBatch();
-            Assert.assertEquals(res.length, BATCH_SZIE);
-            stmt.executeQuery("DROP TABLE " + testTable);
-        }
-    };
-
-    @BenchmarkOptions(benchmarkRounds = 1, warmupRounds = 0, concurrency = 1)
-    @Test
-    public void benchInsertNative() throws Exception {
-        withConnection(benchInsert, ConnectionType.NATIVE);
-    }
-
-    @BenchmarkOptions(benchmarkRounds = 1, warmupRounds = 0, concurrency = 1)
-    @Test
-    public void benchInsertHttp() throws Exception {
-        withConnection(benchInsert, ConnectionType.HTTP);
-    }
-}
diff --git a/src/test/java/com/github/housepower/jdbc/benchmark/WideColumnIntInsertIBenchmark.java b/src/test/java/com/github/housepower/jdbc/benchmark/WideColumnIntInsertIBenchmark.java
new file mode 100644
index 0000000..f243b79
--- /dev/null
+++ b/src/test/java/com/github/housepower/jdbc/benchmark/WideColumnIntInsertIBenchmark.java
@@ -0,0 +1,49 @@
+package com.github.housepower.jdbc.benchmark;
+
+import com.google.common.base.Strings;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.Benchmark;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+
+/**
+ */
+public class WideColumnIntInsertIBenchmark extends AbstractInsertIBenchmark {
+    @Benchmark
+    @Test
+    public void benchInsertNative() throws Exception {
+        withConnection(benchInsert, ConnectionType.NATIVE);
+    }
+
+    @Benchmark
+    @Test
+    public void benchInsertHttp() throws Exception {
+        withConnection(benchInsert, ConnectionType.HTTP);
+    }
+
+    public WithConnection benchInsert = new WithConnection(){
+        @Override
+        public void apply(Connection connection) throws Exception {
+            wideColumnPrepare(connection, "Int32");
+
+            String params = Strings.repeat("?, ", columnNum);
+            PreparedStatement pstmt = connection.prepareStatement("INSERT INTO "  + getTableName() +" values("   + params.substring(0, params.length()-2) + ")");
+
+
+            for (int i = 0; i < batchSize; i++) {
+                for (int j = 0; j < columnNum; j++ ) {
+                    pstmt.setInt(j + 1, j + 1);
+                }
+                pstmt.addBatch();
+            }
+            int []res = pstmt.executeBatch();
+            Assert.assertEquals(res.length, batchSize);
+
+            wideColumnAfter(connection);
+        }
+    };
+
+}
diff --git a/src/test/java/com/github/housepower/jdbc/benchmark/WideColumnStringInsertIBenchmark.java b/src/test/java/com/github/housepower/jdbc/benchmark/WideColumnStringInsertIBenchmark.java
new file mode 100644
index 0000000..fb81ace
--- /dev/null
+++ b/src/test/java/com/github/housepower/jdbc/benchmark/WideColumnStringInsertIBenchmark.java
@@ -0,0 +1,56 @@
+package com.github.housepower.jdbc.benchmark;
+
+import com.google.common.base.Strings;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.util.concurrent.TimeUnit;
+
+/**
+ */
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Thread)
+public class WideColumnStringInsertIBenchmark extends AbstractInsertIBenchmark {
+
+    @Benchmark
+    @Test
+    public void benchInsertNative() throws Exception {
+        withConnection(benchInsert, ConnectionType.NATIVE);
+    }
+
+    @Benchmark
+    @Test
+    public void benchInsertHttp() throws Exception {
+        withConnection(benchInsert, ConnectionType.HTTP);
+    }
+
+    public WithConnection benchInsert = new WithConnection(){
+        @Override
+        public void apply(Connection connection) throws Exception {
+            wideColumnPrepare(connection, "String");
+            String params = Strings.repeat("?, ", columnNum);
+            PreparedStatement pstmt = connection.prepareStatement("INSERT INTO "  + getTableName() +" values("   + params.substring(0, params.length()-2) + ")");
+
+            for (int i = 0; i < batchSize; i++) {
+                for (int j = 0; j < columnNum; j++ ) {
+                    pstmt.setString(j + 1, j + 1 + "");
+                }
+                pstmt.addBatch();
+            }
+            int []res = pstmt.executeBatch();
+            Assert.assertEquals(res.length, batchSize);
+            wideColumnAfter(connection);
+        }
+    };
+
+}