You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sb...@apache.org on 2017/05/31 07:35:41 UTC

[10/19] ignite git commit: ignite-5203 Simple BLOB support added

ignite-5203 Simple BLOB support added


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/64ab5cdf
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/64ab5cdf
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/64ab5cdf

Branch: refs/heads/ignite-5075
Commit: 64ab5cdfe9297ed819d269166a3d1a3b489d03ec
Parents: 618a448
Author: agura <ag...@apache.org>
Authored: Thu May 18 19:40:09 2017 +0300
Committer: agura <ag...@apache.org>
Committed: Tue May 30 00:33:46 2017 +0300

----------------------------------------------------------------------
 .../jdbc2/JdbcAbstractDmlStatementSelfTest.java |  50 +-
 .../JdbcAbstractUpdateStatementSelfTest.java    |  11 +-
 .../ignite/internal/jdbc2/JdbcBlobTest.java     | 485 +++++++++++++++++++
 .../jdbc2/JdbcInsertStatementSelfTest.java      |  16 +-
 .../jdbc2/JdbcMergeStatementSelfTest.java       |  16 +-
 .../jdbc2/JdbcPreparedStatementSelfTest.java    |  47 ++
 .../jdbc/suite/IgniteJdbcDriverTestSuite.java   |   7 +-
 .../apache/ignite/internal/jdbc2/JdbcBlob.java  | 191 ++++++++
 .../ignite/internal/jdbc2/JdbcConnection.java   |   2 +-
 .../internal/jdbc2/JdbcPreparedStatement.java   |   4 +-
 .../ignite/internal/jdbc2/JdbcResultSet.java    |   8 +-
 11 files changed, 808 insertions(+), 29 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/64ab5cdf/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractDmlStatementSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractDmlStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractDmlStatementSelfTest.java
index 6ef86d3..81c913d 100644
--- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractDmlStatementSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractDmlStatementSelfTest.java
@@ -18,8 +18,11 @@
 package org.apache.ignite.internal.jdbc2;
 
 import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.sql.Blob;
 import java.sql.Connection;
 import java.sql.DriverManager;
+import java.sql.SQLException;
 import java.util.Collections;
 import org.apache.ignite.cache.QueryEntity;
 import org.apache.ignite.cache.query.annotations.QuerySqlField;
@@ -35,6 +38,9 @@ import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC;
  * Statement test.
  */
 public abstract class JdbcAbstractDmlStatementSelfTest extends GridCommonAbstractTest {
+    /** UTF 16 character set name. */
+    private static final String UTF_16 = "UTF-16"; // RAWTOHEX function use UTF-16 for conversion strings to byte arrays.
+
     /** JDBC URL. */
     private static final String BASE_URL = CFG_URL_PREFIX + "cache=" + DEFAULT_CACHE_NAME + "@modules/clients/src/test/config/jdbc-config.xml";
 
@@ -42,7 +48,7 @@ public abstract class JdbcAbstractDmlStatementSelfTest extends GridCommonAbstrac
     static final String BASE_URL_BIN = CFG_URL_PREFIX + "cache=" + DEFAULT_CACHE_NAME + "@modules/clients/src/test/config/jdbc-bin-config.xml";
 
     /** SQL SELECT query for verification. */
-    static final String SQL_SELECT = "select _key, id, firstName, lastName, age from Person";
+    static final String SQL_SELECT = "select _key, id, firstName, lastName, age, data from Person";
 
     /** Alias for _key */
     private static final String KEY_ALIAS = "key";
@@ -107,6 +113,7 @@ public abstract class JdbcAbstractDmlStatementSelfTest extends GridCommonAbstrac
         e.addQueryField("age", Integer.class.getName(), null);
         e.addQueryField("firstName", String.class.getName(), null);
         e.addQueryField("lastName", String.class.getName(), null);
+        e.addQueryField("data", byte[].class.getName(), null);
 
         cache.setQueryEntities(Collections.singletonList(e));
 
@@ -136,6 +143,42 @@ public abstract class JdbcAbstractDmlStatementSelfTest extends GridCommonAbstrac
     }
 
     /**
+     * @param str String.
+     */
+    static byte[] getBytes(String str) {
+        try {
+            return str.getBytes(UTF_16);
+        }
+        catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * @param blob Blob.
+     */
+    static byte[] getBytes(Blob blob) {
+        try {
+            return blob.getBytes(1, (int)blob.length());
+        }
+        catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * @param arr Array.
+     */
+    static String str(byte[] arr) {
+        try {
+            return new String(arr, UTF_16);
+        }
+        catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
      * Person.
      */
     @SuppressWarnings("UnusedDeclaration")
@@ -156,6 +199,10 @@ public abstract class JdbcAbstractDmlStatementSelfTest extends GridCommonAbstrac
         @QuerySqlField
         private final int age;
 
+        /** Binary data. */
+        @QuerySqlField
+        private final byte[] data;
+
         /**
          * @param id ID.
          * @param firstName First name.
@@ -171,6 +218,7 @@ public abstract class JdbcAbstractDmlStatementSelfTest extends GridCommonAbstrac
             this.firstName = firstName;
             this.lastName = lastName;
             this.age = age;
+            this.data = getBytes(lastName);
         }
 
         /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/64ab5cdf/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractUpdateStatementSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractUpdateStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractUpdateStatementSelfTest.java
index a20b815..ace1be6 100644
--- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractUpdateStatementSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcAbstractUpdateStatementSelfTest.java
@@ -19,12 +19,15 @@ package org.apache.ignite.internal.jdbc2;
 
 import java.sql.Statement;
 
+/**
+ *
+ */
 public abstract class JdbcAbstractUpdateStatementSelfTest extends JdbcAbstractDmlStatementSelfTest {
     /** SQL query to populate cache. */
-    private static final String ITEMS_SQL = "insert into Person(_key, id, firstName, lastName, age) values " +
-        "('p1', 1, 'John', 'White', 25), " +
-        "('p2', 2, 'Joe', 'Black', 35), " +
-        "('p3', 3, 'Mike', 'Green', 40)";
+    private static final String ITEMS_SQL = "insert into Person(_key, id, firstName, lastName, age, data) values " +
+        "('p1', 1, 'John', 'White', 25, RAWTOHEX('White')), " +
+        "('p2', 2, 'Joe', 'Black', 35, RAWTOHEX('Black')), " +
+        "('p3', 3, 'Mike', 'Green', 40, RAWTOHEX('Green'))";
 
     /** {@inheritDoc} */
     @Override protected void beforeTest() throws Exception {

http://git-wip-us.apache.org/repos/asf/ignite/blob/64ab5cdf/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcBlobTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcBlobTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcBlobTest.java
new file mode 100644
index 0000000..9e0e0d2
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcBlobTest.java
@@ -0,0 +1,485 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.jdbc2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.sql.SQLException;
+import java.util.Arrays;
+import junit.framework.TestCase;
+
+/**
+ *
+ */
+public class JdbcBlobTest extends TestCase {
+    /**
+     * @throws Exception If failed.
+     */
+    public void testLength() throws Exception {
+        JdbcBlob blob = new JdbcBlob(new byte[16]);
+
+        assertEquals(16, (int)blob.length());
+
+        blob.free();
+
+        try {
+            blob.length();
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testGetBytes() throws Exception {
+        byte[] arr = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+
+        JdbcBlob blob = new JdbcBlob(arr);
+
+        try {
+            blob.getBytes(0, 16);
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+
+        try {
+            blob.getBytes(17, 16);
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+
+        try {
+            blob.getBytes(1, -1);
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+
+        byte[] res = blob.getBytes(1, 0);
+        assertEquals(0, res.length);
+
+        assertTrue(Arrays.equals(arr, blob.getBytes(1, 16)));
+
+        res = blob.getBytes(1, 20);
+        assertEquals(16, res.length);
+        assertTrue(Arrays.equals(arr, res));
+
+        res = blob.getBytes(1, 10);
+        assertEquals(10, res.length);
+        assertEquals(0, res[0]);
+        assertEquals(9, res[9]);
+
+        res = blob.getBytes(7, 10);
+        assertEquals(10, res.length);
+        assertEquals(6, res[0]);
+        assertEquals(15, res[9]);
+
+        res = blob.getBytes(7, 20);
+        assertEquals(10, res.length);
+        assertEquals(6, res[0]);
+        assertEquals(15, res[9]);
+
+        res = blob.getBytes(1, 0);
+        assertEquals(0, res.length);
+
+        blob.free();
+
+        try {
+            blob.getBytes(1, 16);
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testGetBinaryStream() throws Exception {
+        byte[] arr = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+
+        JdbcBlob blob = new JdbcBlob(arr);
+
+        InputStream is = blob.getBinaryStream();
+
+        byte[] res = readBytes(is);
+
+        assertTrue(Arrays.equals(arr, res));
+
+        blob.free();
+
+        try {
+            blob.getBinaryStream();
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testGetBinaryStreamWithParams() throws Exception {
+        byte[] arr = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+
+        JdbcBlob blob = new JdbcBlob(arr);
+
+        try {
+            blob.getBinaryStream(0, arr.length);
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+
+        try {
+            blob.getBinaryStream(1, 0);
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+
+        try {
+            blob.getBinaryStream(17, arr.length);
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+
+        try {
+            blob.getBinaryStream(1, arr.length + 1);
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+
+        InputStream is = blob.getBinaryStream(1, arr.length);
+        byte[] res = readBytes(is);
+        assertTrue(Arrays.equals(arr, res));
+
+        is = blob.getBinaryStream(1, 10);
+        res = readBytes(is);
+        assertEquals(10, res.length);
+        assertEquals(0, res[0]);
+        assertEquals(9, res[9]);
+
+        is = blob.getBinaryStream(6, 10);
+        res = readBytes(is);
+        assertEquals(10, res.length);
+        assertEquals(5, res[0]);
+        assertEquals(14, res[9]);
+
+        blob.free();
+
+        try {
+            blob.getBinaryStream(1, arr.length);
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testPositionBytePattern() throws Exception {
+        byte[] arr = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+
+        JdbcBlob blob = new JdbcBlob(arr);
+
+        assertEquals(-1, blob.position(new byte[] {1, 2, 3}, 0));
+        assertEquals(-1, blob.position(new byte[] {1, 2, 3}, arr.length + 1));
+        assertEquals(-1, blob.position(new byte[0], 1));
+        assertEquals(-1, blob.position(new byte[17], 1));
+        assertEquals(-1, blob.position(new byte[] {3, 2, 1}, 1));
+        assertEquals(1, blob.position(new byte[] {0, 1, 2}, 1));
+        assertEquals(2, blob.position(new byte[] {1, 2, 3}, 1));
+        assertEquals(2, blob.position(new byte[] {1, 2, 3}, 2));
+        assertEquals(-1, blob.position(new byte[] {1, 2, 3}, 3));
+        assertEquals(14, blob.position(new byte[] {13, 14, 15}, 3));
+        assertEquals(-1, blob.position(new byte[] {0, 1, 3}, 1));
+        assertEquals(-1, blob.position(new byte[] {0, 2, 3}, 1));
+        assertEquals(-1, blob.position(new byte[] {1, 2, 4}, 1));
+
+        blob.free();
+
+        try {
+            blob.position(new byte[] {0, 1, 2}, 1);
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testPositionBlobPattern() throws Exception {
+        byte[] arr = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+
+        JdbcBlob blob = new JdbcBlob(arr);
+
+        assertEquals(-1, blob.position(new JdbcBlob(new byte[] {1, 2, 3}), 0));
+        assertEquals(-1, blob.position(new JdbcBlob(new byte[] {1, 2, 3}), arr.length + 1));
+        assertEquals(-1, blob.position(new JdbcBlob(new byte[0]), 1));
+        assertEquals(-1, blob.position(new JdbcBlob(new byte[17]), 1));
+        assertEquals(-1, blob.position(new JdbcBlob(new byte[] {3, 2, 1}), 1));
+        assertEquals(1, blob.position(new JdbcBlob(new byte[] {0, 1, 2}), 1));
+        assertEquals(2, blob.position(new JdbcBlob(new byte[] {1, 2, 3}), 1));
+        assertEquals(2, blob.position(new JdbcBlob(new byte[] {1, 2, 3}), 2));
+        assertEquals(-1, blob.position(new JdbcBlob(new byte[] {1, 2, 3}), 3));
+        assertEquals(14, blob.position(new JdbcBlob(new byte[] {13, 14, 15}), 3));
+        assertEquals(-1, blob.position(new JdbcBlob(new byte[] {0, 1, 3}), 1));
+        assertEquals(-1, blob.position(new JdbcBlob(new byte[] {0, 2, 3}), 1));
+        assertEquals(-1, blob.position(new JdbcBlob(new byte[] {1, 2, 4}), 1));
+
+        blob.free();
+
+        try {
+            blob.position(new JdbcBlob(new byte[] {0, 1, 2}), 1);
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testSetBytes() throws Exception {
+        byte[] arr = new byte[] {0, 1, 2, 3, 4, 5, 6, 7};
+
+        JdbcBlob blob = new JdbcBlob(arr);
+
+        try {
+            blob.setBytes(0, new byte[4]);
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+
+        try {
+            blob.setBytes(17, new byte[4]);
+
+            fail();
+        }
+        catch (ArrayIndexOutOfBoundsException e) {
+            // No-op.
+        }
+
+        assertEquals(4, blob.setBytes(1, new byte[] {3, 2, 1, 0}));
+        assertTrue(Arrays.equals(new byte[] {3, 2, 1, 0, 4, 5, 6, 7}, blob.getBytes(1, arr.length)));
+
+        assertEquals(4, blob.setBytes(5, new byte[] {7, 6, 5, 4}));
+        assertTrue(Arrays.equals(new byte[] {3, 2, 1, 0, 7, 6, 5, 4}, blob.getBytes(1, arr.length)));
+
+        assertEquals(4, blob.setBytes(7, new byte[] {8, 9, 10, 11}));
+        assertTrue(Arrays.equals(new byte[] {3, 2, 1, 0, 7, 6, 8, 9, 10, 11}, blob.getBytes(1, (int)blob.length())));
+
+        blob = new JdbcBlob(new byte[] {15, 16});
+        assertEquals(8, blob.setBytes(1, new byte[] {0, 1, 2, 3, 4, 5, 6, 7}));
+        assertTrue(Arrays.equals(new byte[] {0, 1, 2, 3, 4, 5, 6, 7}, blob.getBytes(1, (int)blob.length())));
+
+        blob.free();
+
+        try {
+            blob.setBytes(1, new byte[] {0, 1, 2});
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testSetBytesWithOffsetAndLength() throws Exception {
+        byte[] arr = new byte[] {0, 1, 2, 3, 4, 5, 6, 7};
+
+        JdbcBlob blob = new JdbcBlob(arr);
+
+        try {
+            blob.setBytes(0, new byte[4], 0, 2);
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+
+        try {
+            blob.setBytes(17, new byte[4], 0, 2);
+
+            fail();
+        }
+        catch (ArrayIndexOutOfBoundsException e) {
+            // No-op.
+        }
+
+        try {
+            blob.setBytes(1, new byte[4], -1, 2);
+
+            fail();
+        }
+        catch (ArrayIndexOutOfBoundsException e) {
+            // No-op.
+        }
+
+        try {
+            blob.setBytes(1, new byte[4], 0, 5);
+
+            fail();
+        }
+        catch (ArrayIndexOutOfBoundsException e) {
+            // No-op.
+        }
+
+        assertEquals(4, blob.setBytes(1, new byte[] {3, 2, 1, 0}, 0, 4));
+        assertTrue(Arrays.equals(new byte[] {3, 2, 1, 0, 4, 5, 6, 7}, blob.getBytes(1, arr.length)));
+
+        assertEquals(4, blob.setBytes(5, new byte[] {7, 6, 5, 4}, 0, 4));
+        assertTrue(Arrays.equals(new byte[] {3, 2, 1, 0, 7, 6, 5, 4}, blob.getBytes(1, arr.length)));
+
+        assertEquals(4, blob.setBytes(7, new byte[] {8, 9, 10, 11}, 0, 4));
+        assertTrue(Arrays.equals(new byte[] {3, 2, 1, 0, 7, 6, 8, 9, 10, 11}, blob.getBytes(1, (int)blob.length())));
+
+        assertEquals(2, blob.setBytes(1, new byte[] {3, 2, 1, 0}, 2, 2));
+        assertTrue(Arrays.equals(new byte[] {1, 0, 1, 0, 7, 6, 8, 9, 10, 11}, blob.getBytes(1, (int)blob.length())));
+
+        assertEquals(2, blob.setBytes(9, new byte[] {3, 2, 1, 0}, 1, 2));
+        assertTrue(Arrays.equals(new byte[] {1, 0, 1, 0, 7, 6, 8, 9, 2, 1}, blob.getBytes(1, (int)blob.length())));
+
+        assertEquals(3, blob.setBytes(9, new byte[] {3, 2, 1, 0}, 0, 3));
+        assertTrue(Arrays.equals(new byte[] {1, 0, 1, 0, 7, 6, 8, 9, 3, 2, 1}, blob.getBytes(1, (int)blob.length())));
+
+        blob = new JdbcBlob(new byte[] {15, 16});
+        assertEquals(8, blob.setBytes(1, new byte[] {0, 1, 2, 3, 4, 5, 6, 7}, 0, 8));
+        assertTrue(Arrays.equals(new byte[] {0, 1, 2, 3, 4, 5, 6, 7}, blob.getBytes(1, (int)blob.length())));
+
+        blob.free();
+
+        try {
+            blob.setBytes(1, new byte[] {0, 1, 2}, 0, 2);
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testTruncate() throws Exception {
+        byte[] arr = new byte[] {0, 1, 2, 3, 4, 5, 6, 7};
+
+        JdbcBlob blob = new JdbcBlob(arr);
+
+        try {
+            blob.truncate(-1);
+
+            fail();
+        }
+        catch(SQLException e) {
+            // No-op.
+        }
+
+        try {
+            blob.truncate(arr.length + 1);
+
+            fail();
+        }
+        catch(SQLException e) {
+            // No-op.
+        }
+
+        blob.truncate(4);
+        assertTrue(Arrays.equals(new byte[] {0, 1, 2, 3}, blob.getBytes(1, (int)blob.length())));
+
+        blob.truncate(0);
+        assertEquals(0, (int)blob.length());
+
+        blob.free();
+
+        try {
+            blob.truncate(0);
+
+            fail();
+        }
+        catch (SQLException e) {
+            // No-op.
+            System.out.println();
+        }
+    }
+
+    /**
+     * @param is Input stream.
+     */
+    private static byte[] readBytes(InputStream is) throws IOException {
+        byte[] tmp = new byte[16];
+
+        int i = 0;
+        int read;
+        int cnt = 0;
+
+        while ((read = is.read()) != -1) {
+            tmp[i++] = (byte)read;
+            cnt++;
+        }
+
+        byte[] res = new byte[cnt];
+
+        System.arraycopy(tmp, 0, res, 0, cnt);
+
+        return res;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/64ab5cdf/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcInsertStatementSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcInsertStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcInsertStatementSelfTest.java
index b23f947..0e7539f 100644
--- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcInsertStatementSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcInsertStatementSelfTest.java
@@ -33,14 +33,14 @@ import org.apache.ignite.testframework.GridTestUtils;
  */
 public class JdbcInsertStatementSelfTest extends JdbcAbstractDmlStatementSelfTest {
     /** SQL query. */
-    private static final String SQL = "insert into Person(_key, id, firstName, lastName, age) values " +
-        "('p1', 1, 'John', 'White', 25), " +
-        "('p2', 2, 'Joe', 'Black', 35), " +
-        "('p3', 3, 'Mike', 'Green', 40)";
+    private static final String SQL = "insert into Person(_key, id, firstName, lastName, age, data) values " +
+        "('p1', 1, 'John', 'White', 25, RAWTOHEX('White')), " +
+        "('p2', 2, 'Joe', 'Black', 35, RAWTOHEX('Black')), " +
+        "('p3', 3, 'Mike', 'Green', 40, RAWTOHEX('Green'))";
 
     /** SQL query. */
-    private static final String SQL_PREPARED = "insert into Person(_key, id, firstName, lastName, age) values " +
-        "(?, ?, ?, ?, ?), (?, ?, ?, ?, ?)";
+    private static final String SQL_PREPARED = "insert into Person(_key, id, firstName, lastName, age, data) values " +
+        "(?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?)";
 
     /** Statement. */
     private Statement stmt;
@@ -79,6 +79,7 @@ public class JdbcInsertStatementSelfTest extends JdbcAbstractDmlStatementSelfTes
                         assertEquals("John", rs.getString("firstName"));
                         assertEquals("White", rs.getString("lastName"));
                         assertEquals(25, rs.getInt("age"));
+                        assertEquals("White", str(getBytes(rs.getBlob("data"))));
                         break;
 
                     case 2:
@@ -86,6 +87,7 @@ public class JdbcInsertStatementSelfTest extends JdbcAbstractDmlStatementSelfTes
                         assertEquals("Joe", rs.getString("firstName"));
                         assertEquals("Black", rs.getString("lastName"));
                         assertEquals(35, rs.getInt("age"));
+                        assertEquals("Black", str(getBytes(rs.getBlob("data"))));
                         break;
 
                     case 3:
@@ -93,6 +95,7 @@ public class JdbcInsertStatementSelfTest extends JdbcAbstractDmlStatementSelfTes
                         assertEquals("Mike", rs.getString("firstName"));
                         assertEquals("Green", rs.getString("lastName"));
                         assertEquals(40, rs.getInt("age"));
+                        assertEquals("Green", str(getBytes(rs.getBlob("data"))));
                         break;
 
                     case 4:
@@ -100,6 +103,7 @@ public class JdbcInsertStatementSelfTest extends JdbcAbstractDmlStatementSelfTes
                         assertEquals("Leah", rs.getString("firstName"));
                         assertEquals("Grey", rs.getString("lastName"));
                         assertEquals(22, rs.getInt("age"));
+                        assertEquals("Grey", str(getBytes(rs.getBlob("data"))));
                         break;
 
                     default:

http://git-wip-us.apache.org/repos/asf/ignite/blob/64ab5cdf/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMergeStatementSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMergeStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMergeStatementSelfTest.java
index f4577f5..1432a78 100644
--- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMergeStatementSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMergeStatementSelfTest.java
@@ -28,14 +28,14 @@ import org.apache.ignite.cache.CachePeekMode;
  */
 public class JdbcMergeStatementSelfTest extends JdbcAbstractDmlStatementSelfTest {
     /** SQL query. */
-    private static final String SQL = "merge into Person(_key, id, firstName, lastName, age) values " +
-        "('p1', 1, 'John', 'White', 25), " +
-        "('p2', 2, 'Joe', 'Black', 35), " +
-        "('p3', 3, 'Mike', 'Green', 40)";
+    private static final String SQL = "merge into Person(_key, id, firstName, lastName, age, data) values " +
+        "('p1', 1, 'John', 'White', 25, RAWTOHEX('White')), " +
+        "('p2', 2, 'Joe', 'Black', 35, RAWTOHEX('Black')), " +
+        "('p3', 3, 'Mike', 'Green', 40, RAWTOHEX('Green'))";
 
     /** SQL query. */
-    protected static final String SQL_PREPARED = "merge into Person(_key, id, firstName, lastName, age) values " +
-        "(?, ?, ?, ?, ?), (?, ?, ?, ?, ?)";
+    protected static final String SQL_PREPARED = "merge into Person(_key, id, firstName, lastName, age, data) values " +
+        "(?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?)";
 
     /** Statement. */
     protected Statement stmt;
@@ -74,6 +74,7 @@ public class JdbcMergeStatementSelfTest extends JdbcAbstractDmlStatementSelfTest
                         assertEquals("John", rs.getString("firstName"));
                         assertEquals("White", rs.getString("lastName"));
                         assertEquals(25, rs.getInt("age"));
+                        assertEquals("White", str(getBytes(rs.getBlob("data"))));
                         break;
 
                     case 2:
@@ -81,6 +82,7 @@ public class JdbcMergeStatementSelfTest extends JdbcAbstractDmlStatementSelfTest
                         assertEquals("Joe", rs.getString("firstName"));
                         assertEquals("Black", rs.getString("lastName"));
                         assertEquals(35, rs.getInt("age"));
+                        assertEquals("Black", str(getBytes(rs.getBlob("data"))));
                         break;
 
                     case 3:
@@ -88,6 +90,7 @@ public class JdbcMergeStatementSelfTest extends JdbcAbstractDmlStatementSelfTest
                         assertEquals("Mike", rs.getString("firstName"));
                         assertEquals("Green", rs.getString("lastName"));
                         assertEquals(40, rs.getInt("age"));
+                        assertEquals("Green", str(getBytes(rs.getBlob("data"))));
                         break;
 
                     case 4:
@@ -95,6 +98,7 @@ public class JdbcMergeStatementSelfTest extends JdbcAbstractDmlStatementSelfTest
                         assertEquals("Leah", rs.getString("firstName"));
                         assertEquals("Grey", rs.getString("lastName"));
                         assertEquals(22, rs.getInt("age"));
+                        assertEquals("Grey", str(getBytes(rs.getBlob("data"))));
                         break;
 
                     default:

http://git-wip-us.apache.org/repos/asf/ignite/blob/64ab5cdf/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatementSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatementSelfTest.java
index 5e402ff..30bd018 100644
--- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatementSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatementSelfTest.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.jdbc2;
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.net.URL;
+import java.sql.Blob;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.PreparedStatement;
@@ -117,6 +118,7 @@ public class JdbcPreparedStatementSelfTest extends GridCommonAbstractTest {
         o.bigVal = new BigDecimal(1);
         o.strVal = "str";
         o.arrVal = new byte[] {1};
+        o.blobVal = new byte[] {1};
         o.dateVal = new Date(1);
         o.timeVal = new Time(1);
         o.tsVal = new Timestamp(1);
@@ -529,6 +531,47 @@ public class JdbcPreparedStatementSelfTest extends GridCommonAbstractTest {
     /**
      * @throws Exception If failed.
      */
+    public void testBlob() throws Exception {
+        stmt = conn.prepareStatement("select * from TestObject where blobVal is not distinct from ?");
+
+        Blob blob = conn.createBlob();
+
+        blob.setBytes(1, new byte[] {1});
+
+        stmt.setBlob(1, blob);
+
+        ResultSet rs = stmt.executeQuery();
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 1;
+
+            cnt++;
+        }
+
+        assertEquals(1, cnt);
+
+        stmt.setNull(1, BINARY);
+
+        rs = stmt.executeQuery();
+
+        cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 2;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
     public void testDate() throws Exception {
         stmt = conn.prepareStatement("select * from TestObject where dateVal is not distinct from ?");
 
@@ -725,6 +768,10 @@ public class JdbcPreparedStatementSelfTest extends GridCommonAbstractTest {
 
         /** */
         @QuerySqlField
+        private byte[] blobVal;
+
+        /** */
+        @QuerySqlField
         private Date dateVal;
 
         /** */

http://git-wip-us.apache.org/repos/asf/ignite/blob/64ab5cdf/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java
index e2f09ba..abfdf90 100644
--- a/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java
@@ -18,6 +18,8 @@
 package org.apache.ignite.jdbc.suite;
 
 import junit.framework.TestSuite;
+import org.apache.ignite.internal.jdbc2.JdbcBlobTest;
+import org.apache.ignite.internal.jdbc2.JdbcDistributedJoinsQueryTest;
 import org.apache.ignite.jdbc.JdbcComplexQuerySelfTest;
 import org.apache.ignite.jdbc.JdbcConnectionSelfTest;
 import org.apache.ignite.jdbc.JdbcEmptyCacheSelfTest;
@@ -41,7 +43,7 @@ public class IgniteJdbcDriverTestSuite extends TestSuite {
     public static TestSuite suite() throws Exception {
         TestSuite suite = new TestSuite("Ignite JDBC Driver Test Suite");
 
-        // Thin client based driver tests
+        // Thin client based driver tests.
         suite.addTest(new TestSuite(JdbcConnectionSelfTest.class));
         suite.addTest(new TestSuite(JdbcStatementSelfTest.class));
         suite.addTest(new TestSuite(JdbcPreparedStatementSelfTest.class));
@@ -61,7 +63,7 @@ public class IgniteJdbcDriverTestSuite extends TestSuite {
         suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcPreparedStatementSelfTest.class));
         suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcResultSetSelfTest.class));
         suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcComplexQuerySelfTest.class));
-        suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcDistributedJoinsQueryTest.class));
+        suite.addTest(new TestSuite(JdbcDistributedJoinsQueryTest.class));
         suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcMetadataSelfTest.class));
         suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcEmptyCacheSelfTest.class));
         suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcLocalCachesSelfTest.class));
@@ -71,6 +73,7 @@ public class IgniteJdbcDriverTestSuite extends TestSuite {
         suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcInsertStatementSelfTest.class));
         suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcBinaryMarshallerInsertStatementSelfTest.class));
         suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcDeleteStatementSelfTest.class));
+        suite.addTest(new TestSuite(JdbcBlobTest.class));
         suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcStreamingSelfTest.class));
 
         // DDL tests.

http://git-wip-us.apache.org/repos/asf/ignite/blob/64ab5cdf/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcBlob.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcBlob.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcBlob.java
new file mode 100644
index 0000000..d1610d1
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcBlob.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.jdbc2;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.sql.Blob;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.Arrays;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ * Simple BLOB implementation. Actually there is no such entity as BLOB in Ignite. So using arrays is preferable way
+ * to work with binary objects.
+ *
+ * This implementation can be useful for reading binary fields of objects through JDBC.
+ */
+public class JdbcBlob implements Blob {
+    /** Byte array. */
+    private byte[] arr;
+
+    /**
+     * @param arr Byte array.
+     */
+    public JdbcBlob(byte[] arr) {
+        this.arr = arr;
+    }
+
+    /** {@inheritDoc} */
+    @Override public long length() throws SQLException {
+        ensureNotClosed();
+
+        return arr.length;
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte[] getBytes(long pos, int len) throws SQLException {
+        ensureNotClosed();
+
+        if (pos < 1 || arr.length - pos < 0 || len < 0)
+            throw new SQLException("Invalid argument. Position can't be less than 1 or " +
+                "greater than size of underlying byte array. Requested length also can't be negative " + "" +
+                "[pos=" + pos + ", len=" + len +']');
+
+        int idx = (int)(pos - 1);
+
+        int size = len > arr.length - idx ? arr.length - idx : len;
+
+        byte[] res = new byte[size];
+
+        U.arrayCopy(arr, idx, res, 0, size);
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public InputStream getBinaryStream() throws SQLException {
+        ensureNotClosed();
+
+        return new ByteArrayInputStream(arr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public InputStream getBinaryStream(long pos, long len) throws SQLException {
+        ensureNotClosed();
+
+        if (pos < 1 || len < 1 || pos > arr.length || len > arr.length - pos + 1)
+            throw new SQLException("Invalid argument. Position can't be less than 1 or " +
+                "greater than size of underlying byte array. Requested length can't be negative and can't be " +
+                "greater than available bytes from given position [pos=" + pos + ", len=" + len +']');
+
+
+        return new ByteArrayInputStream(arr, (int)(pos - 1), (int)len);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long position(byte[] ptrn, long start) throws SQLException {
+        ensureNotClosed();
+
+        if (start < 1 || start > arr.length || ptrn.length == 0 || ptrn.length > arr.length)
+            return -1;
+
+        for(int i = 0, pos = (int)(start - 1); pos < arr.length;) {
+            if (arr[pos] == ptrn[i]) {
+                pos++;
+
+                i++;
+
+                if (i == ptrn.length)
+                    return pos - ptrn.length + 1;
+            }
+            else {
+                pos = pos - i + 1;
+
+                i = 0;
+            }
+        }
+
+        return -1;
+    }
+
+    /** {@inheritDoc} */
+    @Override public long position(Blob ptrn, long start) throws SQLException {
+        ensureNotClosed();
+
+        if (start < 1 || start > arr.length || ptrn.length() == 0 || ptrn.length() > arr.length)
+            return -1;
+
+        return position(ptrn.getBytes(1, (int)ptrn.length()), start);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int setBytes(long pos, byte[] bytes) throws SQLException {
+        return setBytes(pos, bytes, 0, bytes.length);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int setBytes(long pos, byte[] bytes, int off, int len) throws SQLException {
+        ensureNotClosed();
+
+        if (pos < 1)
+            throw new SQLException("Invalid argument. Position can't be less than 1 [pos=" + pos + ']');
+
+        int idx = (int)(pos - 1);
+
+        if (pos - 1 > arr.length || off < 0 || off >= bytes.length || off + len > bytes.length)
+            throw new ArrayIndexOutOfBoundsException();
+
+        byte[] dst = arr;
+
+        if (idx + len > arr.length) {
+            dst = new byte[arr.length + (len - (arr.length - idx))];
+
+            U.arrayCopy(arr, 0, dst, 0, idx);
+
+            arr = dst;
+        }
+
+        U.arrayCopy(bytes, off, dst, idx, len);
+
+        return len;
+    }
+
+    /** {@inheritDoc} */
+    @Override public OutputStream setBinaryStream(long pos) throws SQLException {
+        throw new SQLFeatureNotSupportedException();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void truncate(long len) throws SQLException {
+        ensureNotClosed();
+
+        if (len < 0 || len > arr.length)
+            throw new SQLException("Invalid argument. Length can't be " +
+                "less than zero or greater than Blob length [len=" + len + ']');
+
+        arr = Arrays.copyOf(arr, (int)len);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override public void free() throws SQLException {
+        if (arr != null)
+            arr = null;
+    }
+
+    /**
+     *
+     */
+    private void ensureNotClosed() throws SQLException {
+        if (arr == null)
+            throw new SQLException("Blob instance can't be used after free() has been called.");
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/64ab5cdf/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java
index f6f79fb..ee8b605 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java
@@ -597,7 +597,7 @@ public class JdbcConnection implements Connection {
     @Override public Blob createBlob() throws SQLException {
         ensureNotClosed();
 
-        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+        return new JdbcBlob(new byte[0]);
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/64ab5cdf/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatement.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatement.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatement.java
index 54e58e9..1a66ced 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatement.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatement.java
@@ -239,9 +239,7 @@ public class JdbcPreparedStatement extends JdbcStatement implements PreparedStat
 
     /** {@inheritDoc} */
     @Override public void setBlob(int paramIdx, Blob x) throws SQLException {
-        ensureNotClosed();
-
-        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+        setBytes(paramIdx, x.getBytes(1, (int)x.length()));
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/64ab5cdf/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSet.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSet.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSet.java
index 0f91bd8..10cf17a 100755
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSet.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSet.java
@@ -952,9 +952,7 @@ public class JdbcResultSet implements ResultSet {
 
     /** {@inheritDoc} */
     @Override public Blob getBlob(int colIdx) throws SQLException {
-        ensureNotClosed();
-
-        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+        return new JdbcBlob(getBytes(colIdx));
     }
 
     /** {@inheritDoc} */
@@ -985,9 +983,7 @@ public class JdbcResultSet implements ResultSet {
 
     /** {@inheritDoc} */
     @Override public Blob getBlob(String colLb) throws SQLException {
-        ensureNotClosed();
-
-        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+        return new JdbcBlob(getBytes(colLb));
     }
 
     /** {@inheritDoc} */