You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vk...@apache.org on 2015/09/12 03:36:02 UTC

[1/4] ignite git commit: ignite-1250 JDBC driver: migration to embedded Ignite client node

Repository: ignite
Updated Branches:
  refs/heads/ignite-1.4 1ff4a52af -> ebb9e2e9d


http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/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
new file mode 100644
index 0000000..5092b42
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSet.java
@@ -0,0 +1,1520 @@
+/*
+ * 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.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Date;
+import java.sql.NClob;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.RowId;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Statement;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import org.apache.ignite.Ignite;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * JDBC result set implementation.
+ */
+public class JdbcResultSet implements ResultSet {
+    /** Uuid. */
+    private final UUID uuid;
+
+    /** Statement. */
+    private final JdbcStatement stmt;
+
+    /** Table names. */
+    private final List<String> tbls;
+
+    /** Column names. */
+    private final List<String> cols;
+
+    /** Class names. */
+    private final List<String> types;
+
+    /** Rows cursor iterator. */
+    private Iterator<List<?>> it;
+
+    /** Finished flag. */
+    private boolean finished;
+
+    /** Current position. */
+    private int pos;
+
+    /** Current. */
+    private List<Object> curr;
+
+    /** Closed flag. */
+    private boolean closed;
+
+    /** Was {@code NULL} flag. */
+    private boolean wasNull;
+
+    /** Fetch size. */
+    private int fetchSize;
+
+    /**
+     * Creates new result set with predefined fields.
+     * Result set created with this constructor will
+     * never execute remote tasks.
+     *
+     * @param uuid Query UUID.
+     * @param stmt Statement.
+     * @param tbls Table names.
+     * @param cols Column names.
+     * @param types Types.
+     * @param fields Fields.
+     */
+    JdbcResultSet(@Nullable UUID uuid, JdbcStatement stmt, List<String> tbls, List<String> cols,
+        List<String> types, Collection<List<?>> fields, boolean finished) {
+        assert stmt != null;
+        assert tbls != null;
+        assert cols != null;
+        assert types != null;
+        assert fields != null;
+
+        this.uuid = uuid;
+        this.stmt = stmt;
+        this.tbls = tbls;
+        this.cols = cols;
+        this.types = types;
+        this.finished = finished;
+
+        this.it = fields.iterator();
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override public boolean next() throws SQLException {
+        ensureNotClosed();
+
+        if (it == null || (stmt.getMaxRows() > 0 && pos >= stmt.getMaxRows())) {
+            curr = null;
+
+            return false;
+        }
+        else if (it.hasNext()) {
+            curr = new ArrayList<>(it.next());
+
+            pos++;
+
+            if (finished && !it.hasNext())
+                it = null;
+
+            return true;
+        }
+        else if (!finished) {
+            JdbcConnection conn = (JdbcConnection)stmt.getConnection();
+
+            Ignite ignite = conn.ignite();
+
+            UUID nodeId = conn.nodeId();
+
+            boolean loc = nodeId == null;
+
+            JdbcQueryTask qryTask = new JdbcQueryTask(loc ? ignite : null, conn.cacheName(),
+                null, loc, null, fetchSize, uuid, conn.isLocalQuery(), conn.isCollocatedQuery());
+
+            try {
+                JdbcQueryTask.QueryResult res =
+                    loc ? qryTask.call() : ignite.compute(ignite.cluster().forNodeId(nodeId)).call(qryTask);
+
+                finished = res.isFinished();
+
+                it = res.getRows().iterator();
+
+                return next();
+            }
+            catch (Exception e) {
+                throw new SQLException("Failed to query Ignite.", e);
+            }
+        }
+
+        it = null;
+
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void close() throws SQLException {
+        if (uuid != null)
+            stmt.resSets.remove(this);
+
+        closeInternal();
+    }
+
+    /**
+     * Marks result set as closed.
+     * If this result set is associated with locally executed query then query cursor will also closed.
+     */
+    void closeInternal() throws SQLException  {
+        if (((JdbcConnection)stmt.getConnection()).nodeId() == null)
+            JdbcQueryTask.remove(uuid);
+
+        closed = true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean wasNull() throws SQLException {
+        return wasNull;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getString(int colIdx) throws SQLException {
+        return getTypedValue(colIdx, String.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean getBoolean(int colIdx) throws SQLException {
+        Boolean val = getTypedValue(colIdx, Boolean.class);
+
+        return val != null ? val : false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte getByte(int colIdx) throws SQLException {
+        Byte val = getTypedValue(colIdx, Byte.class);
+
+        return val != null ? val : 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public short getShort(int colIdx) throws SQLException {
+        Short val = getTypedValue(colIdx, Short.class);
+
+        return val != null ? val : 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getInt(int colIdx) throws SQLException {
+        Integer val = getTypedValue(colIdx, Integer.class);
+
+        return val != null ? val : 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public long getLong(int colIdx) throws SQLException {
+        Long val = getTypedValue(colIdx, Long.class);
+
+        return val != null ? val : 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public float getFloat(int colIdx) throws SQLException {
+        Float val = getTypedValue(colIdx, Float.class);
+
+        return val != null ? val : 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public double getDouble(int colIdx) throws SQLException {
+        Double val = getTypedValue(colIdx, Double.class);
+
+        return val != null ? val : 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public BigDecimal getBigDecimal(int colIdx, int scale) throws SQLException {
+        return getTypedValue(colIdx, BigDecimal.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte[] getBytes(int colIdx) throws SQLException {
+        return getTypedValue(colIdx, byte[].class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Date getDate(int colIdx) throws SQLException {
+        return getTypedValue(colIdx, Date.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Time getTime(int colIdx) throws SQLException {
+        return getTypedValue(colIdx, Time.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Timestamp getTimestamp(int colIdx) throws SQLException {
+        return getTypedValue(colIdx, Timestamp.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public InputStream getAsciiStream(int colIdx) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public InputStream getUnicodeStream(int colIdx) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public InputStream getBinaryStream(int colIdx) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Stream are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getString(String colLb) throws SQLException {
+        return getTypedValue(colLb, String.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean getBoolean(String colLb) throws SQLException {
+        Boolean val = getTypedValue(colLb, Boolean.class);
+
+        return val != null ? val : false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte getByte(String colLb) throws SQLException {
+        Byte val = getTypedValue(colLb, Byte.class);
+
+        return val != null ? val : 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public short getShort(String colLb) throws SQLException {
+        Short val = getTypedValue(colLb, Short.class);
+
+        return val != null ? val : 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getInt(String colLb) throws SQLException {
+        Integer val = getTypedValue(colLb, Integer.class);
+
+        return val != null ? val : 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public long getLong(String colLb) throws SQLException {
+        Long val = getTypedValue(colLb, Long.class);
+
+        return val != null ? val : 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public float getFloat(String colLb) throws SQLException {
+        Float val = getTypedValue(colLb, Float.class);
+
+        return val != null ? val : 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public double getDouble(String colLb) throws SQLException {
+        Double val = getTypedValue(colLb, Double.class);
+
+        return val != null ? val : 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public BigDecimal getBigDecimal(String colLb, int scale) throws SQLException {
+        return getTypedValue(colLb, BigDecimal.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte[] getBytes(String colLb) throws SQLException {
+        return getTypedValue(colLb, byte[].class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Date getDate(String colLb) throws SQLException {
+        return getTypedValue(colLb, Date.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Time getTime(String colLb) throws SQLException {
+        return getTypedValue(colLb, Time.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Timestamp getTimestamp(String colLb) throws SQLException {
+        return getTypedValue(colLb, Timestamp.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public InputStream getAsciiStream(String colLb) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public InputStream getUnicodeStream(String colLb) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public InputStream getBinaryStream(String colLb) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public SQLWarning getWarnings() throws SQLException {
+        ensureNotClosed();
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void clearWarnings() throws SQLException {
+        ensureNotClosed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getCursorName() throws SQLException {
+        ensureNotClosed();
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSetMetaData getMetaData() throws SQLException {
+        ensureNotClosed();
+
+        return new JdbcResultSetMetadata(tbls, cols, types);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object getObject(int colIdx) throws SQLException {
+        return getTypedValue(colIdx, Object.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object getObject(String colLb) throws SQLException {
+        return getTypedValue(colLb, Object.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int findColumn(String colLb) throws SQLException {
+        ensureNotClosed();
+
+        int idx = cols.indexOf(colLb.toUpperCase());
+
+        if (idx == -1)
+            throw new SQLException("Column not found: " + colLb);
+
+        return idx + 1;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Reader getCharacterStream(int colIdx) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Reader getCharacterStream(String colLb) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public BigDecimal getBigDecimal(int colIdx) throws SQLException {
+        return getTypedValue(colIdx, BigDecimal.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public BigDecimal getBigDecimal(String colLb) throws SQLException {
+        return getTypedValue(colLb, BigDecimal.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isBeforeFirst() throws SQLException {
+        ensureNotClosed();
+
+        return pos < 1;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isAfterLast() throws SQLException {
+        ensureNotClosed();
+
+        return finished && it == null && curr == null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isFirst() throws SQLException {
+        ensureNotClosed();
+
+        return pos == 1;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isLast() throws SQLException {
+        ensureNotClosed();
+
+        return finished && it == null && curr != null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void beforeFirst() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLException("Result set is forward-only.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void afterLast() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLException("Result set is forward-only.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean first() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLException("Result set is forward-only.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean last() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLException("Result set is forward-only.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getRow() throws SQLException {
+        ensureNotClosed();
+
+        return isAfterLast() ? 0 : pos;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean absolute(int row) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLException("Result set is forward-only.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean relative(int rows) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLException("Result set is forward-only.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean previous() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLException("Result set is forward-only.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setFetchDirection(int direction) throws SQLException {
+        ensureNotClosed();
+
+        if (direction != FETCH_FORWARD)
+            throw new SQLFeatureNotSupportedException("Only forward direction is supported");
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getFetchDirection() throws SQLException {
+        ensureNotClosed();
+
+        return FETCH_FORWARD;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setFetchSize(int fetchSize) throws SQLException {
+        ensureNotClosed();
+
+        if (fetchSize <= 0)
+            throw new SQLException("Fetch size must be greater than zero.");
+
+        this.fetchSize = fetchSize;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getFetchSize() throws SQLException {
+        ensureNotClosed();
+
+        return fetchSize;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getType() throws SQLException {
+        ensureNotClosed();
+
+        return stmt.getResultSetType();
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getConcurrency() throws SQLException {
+        ensureNotClosed();
+
+        return CONCUR_READ_ONLY;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean rowUpdated() throws SQLException {
+        ensureNotClosed();
+
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean rowInserted() throws SQLException {
+        ensureNotClosed();
+
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean rowDeleted() throws SQLException {
+        ensureNotClosed();
+
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateNull(int colIdx) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBoolean(int colIdx, boolean x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateByte(int colIdx, byte x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateShort(int colIdx, short x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateInt(int colIdx, int x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateLong(int colIdx, long x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateFloat(int colIdx, float x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateDouble(int colIdx, double x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBigDecimal(int colIdx, BigDecimal x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateString(int colIdx, String x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBytes(int colIdx, byte[] x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateDate(int colIdx, Date x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateTime(int colIdx, Time x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateTimestamp(int colIdx, Timestamp x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateAsciiStream(int colIdx, InputStream x, int len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBinaryStream(int colIdx, InputStream x, int len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateCharacterStream(int colIdx, Reader x, int len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateObject(int colIdx, Object x, int scaleOrLen) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateObject(int colIdx, Object x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateNull(String colLb) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBoolean(String colLb, boolean x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateByte(String colLb, byte x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateShort(String colLb, short x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateInt(String colLb, int x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateLong(String colLb, long x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateFloat(String colLb, float x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateDouble(String colLb, double x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBigDecimal(String colLb, BigDecimal x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateString(String colLb, String x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBytes(String colLb, byte[] x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateDate(String colLb, Date x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateTime(String colLb, Time x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateTimestamp(String colLb, Timestamp x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateAsciiStream(String colLb, InputStream x, int len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBinaryStream(String colLb, InputStream x, int len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateCharacterStream(String colLb, Reader reader, int len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateObject(String colLb, Object x, int scaleOrLen) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateObject(String colLb, Object x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void insertRow() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateRow() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void deleteRow() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void refreshRow() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Row refreshing is not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void cancelRowUpdates() throws SQLException {
+        ensureNotClosed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void moveToInsertRow() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void moveToCurrentRow() throws SQLException {
+        ensureNotClosed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Statement getStatement() throws SQLException {
+        ensureNotClosed();
+
+        return stmt;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object getObject(int colIdx, Map<String, Class<?>> map) throws SQLException {
+        return getTypedValue(colIdx, Object.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Ref getRef(int colIdx) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Blob getBlob(int colIdx) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Clob getClob(int colIdx) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Array getArray(int colIdx) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object getObject(String colLb, Map<String, Class<?>> map) throws SQLException {
+        return getTypedValue(colLb, Object.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Ref getRef(String colLb) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Blob getBlob(String colLb) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Clob getClob(String colLb) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Array getArray(String colLb) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Date getDate(int colIdx, Calendar cal) throws SQLException {
+        return getTypedValue(colIdx, Date.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Date getDate(String colLb, Calendar cal) throws SQLException {
+        return getTypedValue(colLb, Date.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Time getTime(int colIdx, Calendar cal) throws SQLException {
+        return getTypedValue(colIdx, Time.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Time getTime(String colLb, Calendar cal) throws SQLException {
+        return getTypedValue(colLb, Time.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Timestamp getTimestamp(int colIdx, Calendar cal) throws SQLException {
+        return getTypedValue(colIdx, Timestamp.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Timestamp getTimestamp(String colLb, Calendar cal) throws SQLException {
+        return getTypedValue(colLb, Timestamp.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public URL getURL(int colIdx) throws SQLException {
+        return getTypedValue(colIdx, URL.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public URL getURL(String colLb) throws SQLException {
+        return getTypedValue(colLb, URL.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateRef(int colIdx, Ref x) throws SQLException {
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateRef(String colLb, Ref x) throws SQLException {
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBlob(int colIdx, Blob x) throws SQLException {
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBlob(String colLb, Blob x) throws SQLException {
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateClob(int colIdx, Clob x) throws SQLException {
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateClob(String colLb, Clob x) throws SQLException {
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateArray(int colIdx, Array x) throws SQLException {
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateArray(String colLb, Array x) throws SQLException {
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public RowId getRowId(int colIdx) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public RowId getRowId(String colLb) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateRowId(int colIdx, RowId x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateRowId(String colLb, RowId x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getHoldability() throws SQLException {
+        ensureNotClosed();
+
+        return HOLD_CURSORS_OVER_COMMIT;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isClosed() throws SQLException {
+        return closed;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateNString(int colIdx, String nStr) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateNString(String colLb, String nStr) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateNClob(int colIdx, NClob nClob) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateNClob(String colLb, NClob nClob) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public NClob getNClob(int colIdx) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public NClob getNClob(String colLb) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public SQLXML getSQLXML(int colIdx) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public SQLXML getSQLXML(String colLb) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateSQLXML(int colIdx, SQLXML xmlObj) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateSQLXML(String colLb, SQLXML xmlObj) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getNString(int colIdx) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getNString(String colLb) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Reader getNCharacterStream(int colIdx) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Reader getNCharacterStream(String colLb) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateNCharacterStream(int colIdx, Reader x, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateNCharacterStream(String colLb, Reader reader, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateAsciiStream(int colIdx, InputStream x, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBinaryStream(int colIdx, InputStream x, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateCharacterStream(int colIdx, Reader x, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateAsciiStream(String colLb, InputStream x, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBinaryStream(String colLb, InputStream x, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateCharacterStream(String colLb, Reader reader, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBlob(int colIdx, InputStream inputStream, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBlob(String colLb, InputStream inputStream, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateClob(int colIdx, Reader reader, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateClob(String colLb, Reader reader, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateNClob(int colIdx, Reader reader, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateNClob(String colLb, Reader reader, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateNCharacterStream(int colIdx, Reader x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateNCharacterStream(String colLb, Reader reader) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateAsciiStream(int colIdx, InputStream x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBinaryStream(int colIdx, InputStream x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateCharacterStream(int colIdx, Reader x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateAsciiStream(String colLb, InputStream x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBinaryStream(String colLb, InputStream x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateCharacterStream(String colLb, Reader reader) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBlob(int colIdx, InputStream inputStream) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateBlob(String colLb, InputStream inputStream) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateClob(int colIdx, Reader reader) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateClob(String colLb, Reader reader) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateNClob(int colIdx, Reader reader) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateNClob(String colLb, Reader reader) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override public <T> T unwrap(Class<T> iface) throws SQLException {
+        if (!isWrapperFor(iface))
+            throw new SQLException("Result set is not a wrapper for " + iface.getName());
+
+        return (T)this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isWrapperFor(Class<?> iface) throws SQLException {
+        return iface != null && iface == ResultSet.class;
+    }
+
+    /** {@inheritDoc} */
+    @Override public <T> T getObject(int colIdx, Class<T> type) throws SQLException {
+        return getTypedValue(colIdx, type);
+    }
+
+    /** {@inheritDoc} */
+    @Override public <T> T getObject(String colLb, Class<T> type) throws SQLException {
+        return getTypedValue(colLb, type);
+    }
+
+    /**
+     * Gets casted field value by label.
+     *
+     * @param colLb Column label.
+     * @param cls Value class.
+     * @return Casted field value.
+     * @throws SQLException In case of error.
+     */
+    private <T> T getTypedValue(String colLb, Class<T> cls) throws SQLException {
+        ensureNotClosed();
+        ensureHasCurrentRow();
+
+        String name = colLb.toUpperCase();
+
+        Integer idx = stmt.fieldsIdxs.get(name);
+
+        int colIdx;
+
+        if (idx != null)
+            colIdx = idx;
+        else {
+            colIdx = cols.indexOf(name) + 1;
+
+            if (colIdx <= 0)
+                throw new SQLException("Invalid column label: " + colLb);
+
+            stmt.fieldsIdxs.put(name, colIdx);
+        }
+
+        return getTypedValue(colIdx, cls);
+    }
+
+    /**
+     * Gets casted field value by index.
+     *
+     * @param colIdx Column index.
+     * @param cls Value class.
+     * @return Casted field value.
+     * @throws SQLException In case of error.
+     */
+    @SuppressWarnings("unchecked")
+    private <T> T getTypedValue(int colIdx, Class<T> cls) throws SQLException {
+        ensureNotClosed();
+        ensureHasCurrentRow();
+
+        try {
+            T val = cls == String.class ? (T)String.valueOf(curr.get(colIdx - 1)) : (T)curr.get(colIdx - 1);
+
+            wasNull = val == null;
+
+            return val;
+        }
+        catch (IndexOutOfBoundsException ignored) {
+            throw new SQLException("Invalid column index: " + colIdx);
+        }
+        catch (ClassCastException ignored) {
+            throw new SQLException("Value is an not instance of " + cls.getName());
+        }
+    }
+
+    /**
+     * Ensures that result set is not closed.
+     *
+     * @throws SQLException If result set is closed.
+     */
+    private void ensureNotClosed() throws SQLException {
+        if (closed)
+            throw new SQLException("Result set is closed.");
+    }
+
+    /**
+     * Ensures that result set is positioned on a row.
+     *
+     * @throws SQLException If result set is not positioned on a row.
+     */
+    private void ensureHasCurrentRow() throws SQLException {
+        if (curr == null)
+            throw new SQLException("Result set is not positioned on a row.");
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSetMetadata.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSetMetadata.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSetMetadata.java
new file mode 100644
index 0000000..ce8c269
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSetMetadata.java
@@ -0,0 +1,171 @@
+/*
+ * 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.sql.*;
+import java.util.*;
+
+/**
+ * JDBC result set metadata implementation.
+ */
+public class JdbcResultSetMetadata implements ResultSetMetaData {
+    /** Column width. */
+    private static final int COL_WIDTH = 30;
+
+    /** Table names. */
+    private final List<String> tbls;
+
+    /** Column names. */
+    private final List<String> cols;
+
+    /** Class names. */
+    private final List<String> types;
+
+    /**
+     * @param tbls Table names.
+     * @param cols Column names.
+     * @param types Types.
+     */
+    JdbcResultSetMetadata(List<String> tbls, List<String> cols, List<String> types) {
+        assert cols != null;
+        assert types != null;
+
+        this.tbls = tbls;
+        this.cols = cols;
+        this.types = types;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getColumnCount() throws SQLException {
+        return cols.size();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isAutoIncrement(int col) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isCaseSensitive(int col) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isSearchable(int col) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isCurrency(int col) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int isNullable(int col) throws SQLException {
+        return columnNullable;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isSigned(int col) throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getColumnDisplaySize(int col) throws SQLException {
+        return COL_WIDTH;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getColumnLabel(int col) throws SQLException {
+        return cols.get(col - 1);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getColumnName(int col) throws SQLException {
+        return cols.get(col - 1);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getSchemaName(int col) throws SQLException {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getPrecision(int col) throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getScale(int col) throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getTableName(int col) throws SQLException {
+        return tbls != null ? tbls.get(col - 1) : "";
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getCatalogName(int col) throws SQLException {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getColumnType(int col) throws SQLException {
+        return JdbcUtils.type(types.get(col - 1));
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getColumnTypeName(int col) throws SQLException {
+        return JdbcUtils.typeName(types.get(col - 1));
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isReadOnly(int col) throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isWritable(int col) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isDefinitelyWritable(int col) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getColumnClassName(int col) throws SQLException {
+        return types.get(col - 1);
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override public <T> T unwrap(Class<T> iface) throws SQLException {
+        if (!isWrapperFor(iface))
+            throw new SQLException("Result set meta data is not a wrapper for " + iface.getName());
+
+        return (T)this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isWrapperFor(Class<?> iface) throws SQLException {
+        return iface == ResultSetMetaData.class;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcStatement.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcStatement.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcStatement.java
new file mode 100644
index 0000000..f4a3e1d
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcStatement.java
@@ -0,0 +1,456 @@
+/*
+ * 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.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.SQLWarning;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import org.apache.ignite.Ignite;
+
+import static java.sql.ResultSet.CONCUR_READ_ONLY;
+import static java.sql.ResultSet.FETCH_FORWARD;
+import static java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT;
+import static java.sql.ResultSet.TYPE_FORWARD_ONLY;
+
+/**
+ * JDBC statement implementation.
+ */
+public class JdbcStatement implements Statement {
+    /** Default fetch size. */
+    private static final int DFLT_FETCH_SIZE = 1024;
+
+    /** Connection. */
+    private final JdbcConnection conn;
+
+    /** Closed flag. */
+    private boolean closed;
+
+    /** Rows limit. */
+    private int maxRows;
+
+    /** Current result set. */
+    private ResultSet rs;
+
+    /** Query arguments. */
+    protected Object[] args;
+
+    /** Fetch size. */
+    private int fetchSize = DFLT_FETCH_SIZE;
+
+    /** Result sets. */
+    final Set<JdbcResultSet> resSets = new HashSet<>();
+
+    /** Fields indexes. */
+    Map<String, Integer> fieldsIdxs = new HashMap<>();
+
+    /**
+     * Creates new statement.
+     *
+     * @param conn Connection.
+     */
+    JdbcStatement(JdbcConnection conn) {
+        assert conn != null;
+
+        this.conn = conn;
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet executeQuery(String sql) throws SQLException {
+        ensureNotClosed();
+
+        rs = null;
+
+        if (sql == null || sql.isEmpty())
+            throw new SQLException("SQL query is empty");
+
+        Ignite ignite = conn.ignite();
+
+        UUID nodeId = conn.nodeId();
+
+        UUID uuid = UUID.randomUUID();
+
+        boolean loc = nodeId == null;
+
+        JdbcQueryTask qryTask = new JdbcQueryTask(loc ? ignite : null, conn.cacheName(),
+            sql, loc, args, fetchSize, uuid, conn.isLocalQuery(), conn.isCollocatedQuery());
+
+        try {
+            JdbcQueryTask.QueryResult res =
+                loc ? qryTask.call() : ignite.compute(ignite.cluster().forNodeId(nodeId)).call(qryTask);
+
+            JdbcResultSet rs = new JdbcResultSet(uuid, this, res.getTbls(), res.getCols(), res.getTypes(),
+                res.getRows(), res.isFinished());
+
+            rs.setFetchSize(fetchSize);
+
+            resSets.add(rs);
+
+            return rs;
+        }
+        catch (Exception e) {
+            throw new SQLException("Failed to query Ignite.", e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public int executeUpdate(String sql) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void close() throws SQLException {
+        conn.statements.remove(this);
+
+        closeInternal();
+    }
+
+    /**
+     * Marks statement as closed and closes all result sets.
+     */
+    void closeInternal() throws SQLException {
+        for (Iterator<JdbcResultSet> it = resSets.iterator(); it.hasNext(); ) {
+            JdbcResultSet rs = it.next();
+
+            rs.closeInternal();
+
+            it.remove();
+        }
+
+        closed = true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxFieldSize() throws SQLException {
+        ensureNotClosed();
+
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setMaxFieldSize(int max) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Field size limitation is not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxRows() throws SQLException {
+        ensureNotClosed();
+
+        return maxRows;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setMaxRows(int maxRows) throws SQLException {
+        ensureNotClosed();
+
+        this.maxRows = maxRows;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setEscapeProcessing(boolean enable) throws SQLException {
+        ensureNotClosed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getQueryTimeout() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Query timeout is not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setQueryTimeout(int timeout) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Query timeout is not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void cancel() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Cancellation is not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public SQLWarning getWarnings() throws SQLException {
+        ensureNotClosed();
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void clearWarnings() throws SQLException {
+        ensureNotClosed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setCursorName(String name) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean execute(String sql) throws SQLException {
+        ensureNotClosed();
+
+        rs = executeQuery(sql);
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getResultSet() throws SQLException {
+        ensureNotClosed();
+
+        ResultSet rs0 = rs;
+
+        rs = null;
+
+        return rs0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getUpdateCount() throws SQLException {
+        ensureNotClosed();
+
+        return -1;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean getMoreResults() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Multiple open results are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setFetchDirection(int direction) throws SQLException {
+        ensureNotClosed();
+
+        if (direction != FETCH_FORWARD)
+            throw new SQLFeatureNotSupportedException("Only forward direction is supported");
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getFetchDirection() throws SQLException {
+        ensureNotClosed();
+
+        return FETCH_FORWARD;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setFetchSize(int fetchSize) throws SQLException {
+        ensureNotClosed();
+
+        if (fetchSize < 0)
+            throw new SQLException("Fetch size must be greater or equal zero.");
+
+        this.fetchSize = fetchSize;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getFetchSize() throws SQLException {
+        ensureNotClosed();
+
+        return fetchSize;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getResultSetConcurrency() throws SQLException {
+        ensureNotClosed();
+
+        return CONCUR_READ_ONLY;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getResultSetType() throws SQLException {
+        ensureNotClosed();
+
+        return TYPE_FORWARD_ONLY;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void addBatch(String sql) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void clearBatch() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public int[] executeBatch() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Connection getConnection() throws SQLException {
+        ensureNotClosed();
+
+        return conn;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean getMoreResults(int curr) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Multiple open results are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getGeneratedKeys() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public int executeUpdate(String sql, int[] colIndexes) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public int executeUpdate(String sql, String[] colNames) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
+        ensureNotClosed();
+
+        if (autoGeneratedKeys == RETURN_GENERATED_KEYS)
+            throw new SQLFeatureNotSupportedException("Updates are not supported.");
+
+        return execute(sql);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean execute(String sql, int[] colIndexes) throws SQLException {
+        ensureNotClosed();
+
+        if (colIndexes != null && colIndexes.length > 0)
+            throw new SQLFeatureNotSupportedException("Updates are not supported.");
+
+        return execute(sql);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean execute(String sql, String[] colNames) throws SQLException {
+        ensureNotClosed();
+
+        if (colNames != null && colNames.length > 0)
+            throw new SQLFeatureNotSupportedException("Updates are not supported.");
+
+        return execute(sql);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getResultSetHoldability() throws SQLException {
+        ensureNotClosed();
+
+        return HOLD_CURSORS_OVER_COMMIT;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isClosed() throws SQLException {
+        return closed;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setPoolable(boolean poolable) throws SQLException {
+        ensureNotClosed();
+
+        if (poolable)
+            throw new SQLFeatureNotSupportedException("Pooling is not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isPoolable() throws SQLException {
+        ensureNotClosed();
+
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override public <T> T unwrap(Class<T> iface) throws SQLException {
+        if (!isWrapperFor(iface))
+            throw new SQLException("Statement is not a wrapper for " + iface.getName());
+
+        return (T)this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isWrapperFor(Class<?> iface) throws SQLException {
+        return iface != null && iface == Statement.class;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void closeOnCompletion() throws SQLException {
+        throw new SQLFeatureNotSupportedException("closeOnCompletion is not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isCloseOnCompletion() throws SQLException {
+        ensureNotClosed();
+
+        return false;
+    }
+
+    /**
+     * Ensures that statement is not closed.
+     *
+     * @throws SQLException If statement is closed.
+     */
+    protected void ensureNotClosed() throws SQLException {
+        if (closed)
+            throw new SQLException("Statement is closed.");
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcUtils.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcUtils.java
new file mode 100644
index 0000000..b519340
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcUtils.java
@@ -0,0 +1,155 @@
+/*
+ * 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.math.BigDecimal;
+import java.net.URL;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.util.Date;
+
+import static java.sql.Types.BIGINT;
+import static java.sql.Types.BINARY;
+import static java.sql.Types.BOOLEAN;
+import static java.sql.Types.DATE;
+import static java.sql.Types.DOUBLE;
+import static java.sql.Types.FLOAT;
+import static java.sql.Types.INTEGER;
+import static java.sql.Types.OTHER;
+import static java.sql.Types.SMALLINT;
+import static java.sql.Types.TIME;
+import static java.sql.Types.TIMESTAMP;
+import static java.sql.Types.TINYINT;
+import static java.sql.Types.VARCHAR;
+
+/**
+ * Utility methods for JDBC driver.
+ */
+public class JdbcUtils {
+    /**
+     * Converts Java class name to type from {@link Types}.
+     *
+     * @param cls Java class name.
+     * @return Type from {@link Types}.
+     */
+    public static int type(String cls) {
+        if (Boolean.class.getName().equals(cls) || boolean.class.getName().equals(cls))
+            return BOOLEAN;
+        else if (Byte.class.getName().equals(cls) || byte.class.getName().equals(cls))
+            return TINYINT;
+        else if (Short.class.getName().equals(cls) || short.class.getName().equals(cls))
+            return SMALLINT;
+        else if (Integer.class.getName().equals(cls) || int.class.getName().equals(cls))
+            return INTEGER;
+        else if (Long.class.getName().equals(cls) || long.class.getName().equals(cls))
+            return BIGINT;
+        else if (Float.class.getName().equals(cls) || float.class.getName().equals(cls))
+            return FLOAT;
+        else if (Double.class.getName().equals(cls) || double.class.getName().equals(cls))
+            return DOUBLE;
+        else if (String.class.getName().equals(cls))
+            return VARCHAR;
+        else if (byte[].class.getName().equals(cls))
+            return BINARY;
+        else if (Time.class.getName().equals(cls))
+            return TIME;
+        else if (Timestamp.class.getName().equals(cls))
+            return TIMESTAMP;
+        else if (Date.class.getName().equals(cls) || java.sql.Date.class.getName().equals(cls))
+            return DATE;
+        else
+            return OTHER;
+    }
+
+    /**
+     * Converts Java class name to SQL type name.
+     *
+     * @param cls Java class name.
+     * @return SQL type name.
+     */
+    public static String typeName(String cls) {
+        if (Boolean.class.getName().equals(cls) || boolean.class.getName().equals(cls))
+            return "BOOLEAN";
+        else if (Byte.class.getName().equals(cls) || byte.class.getName().equals(cls))
+            return "TINYINT";
+        else if (Short.class.getName().equals(cls) || short.class.getName().equals(cls))
+            return "SMALLINT";
+        else if (Integer.class.getName().equals(cls) || int.class.getName().equals(cls))
+            return "INTEGER";
+        else if (Long.class.getName().equals(cls) || long.class.getName().equals(cls))
+            return "BIGINT";
+        else if (Float.class.getName().equals(cls) || float.class.getName().equals(cls))
+            return "FLOAT";
+        else if (Double.class.getName().equals(cls) || double.class.getName().equals(cls))
+            return "DOUBLE";
+        else if (String.class.getName().equals(cls))
+            return "VARCHAR";
+        else if (byte[].class.getName().equals(cls))
+            return "BINARY";
+        else if (Time.class.getName().equals(cls))
+            return "TIME";
+        else if (Timestamp.class.getName().equals(cls))
+            return "TIMESTAMP";
+        else if (Date.class.getName().equals(cls) || java.sql.Date.class.getName().equals(cls))
+            return "DATE";
+        else
+            return "OTHER";
+    }
+
+    /**
+     * Determines whether type is nullable.
+     *
+     * @param name Column name.
+     * @param cls Java class name.
+     * @return {@code True} if nullable.
+     */
+    public static boolean nullable(String name, String cls) {
+        return !"_KEY".equalsIgnoreCase(name) &&
+            !"_VAL".equalsIgnoreCase(name) &&
+            !(boolean.class.getName().equals(cls) ||
+            byte.class.getName().equals(cls) ||
+            short.class.getName().equals(cls) ||
+            int.class.getName().equals(cls) ||
+            long.class.getName().equals(cls) ||
+            float.class.getName().equals(cls) ||
+            double.class.getName().equals(cls));
+    }
+
+    /**
+     * Checks whether type of the object is SQL-complaint.
+     *
+     * @param obj Object.
+     * @return Whether type of the object is SQL-complaint.
+     */
+    public static boolean sqlType(Object obj) {
+        return obj == null ||
+            obj instanceof BigDecimal ||
+            obj instanceof Boolean ||
+            obj instanceof Byte ||
+            obj instanceof byte[] ||
+            obj instanceof java.util.Date ||
+            obj instanceof Double ||
+            obj instanceof Float ||
+            obj instanceof Integer ||
+            obj instanceof Long ||
+            obj instanceof Short ||
+            obj instanceof String ||
+            obj instanceof URL;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/core/src/main/resources/META-INF/classnames.properties
----------------------------------------------------------------------
diff --git a/modules/core/src/main/resources/META-INF/classnames.properties b/modules/core/src/main/resources/META-INF/classnames.properties
index dd8c3f3..70c32e5 100644
--- a/modules/core/src/main/resources/META-INF/classnames.properties
+++ b/modules/core/src/main/resources/META-INF/classnames.properties
@@ -254,6 +254,15 @@ org.apache.ignite.internal.executor.GridExecutorService
 org.apache.ignite.internal.executor.GridExecutorService$1
 org.apache.ignite.internal.executor.GridExecutorService$TaskTerminateListener
 org.apache.ignite.internal.igfs.common.IgfsIpcCommand
+org.apache.ignite.internal.interop.InteropAwareEventFilter
+org.apache.ignite.internal.interop.InteropBootstrapFactory
+org.apache.ignite.internal.interop.InteropException
+org.apache.ignite.internal.interop.InteropNoCallbackException
+org.apache.ignite.internal.jdbc2.JdbcConnection$JdbcConnectionValidationTask
+org.apache.ignite.internal.jdbc2.JdbcDatabaseMetadata$UpdateMetadataTask
+org.apache.ignite.internal.jdbc2.JdbcQueryTask
+org.apache.ignite.internal.jdbc2.JdbcQueryTask$1
+org.apache.ignite.internal.jdbc2.JdbcQueryTask$QueryResult
 org.apache.ignite.internal.managers.GridManagerAdapter$1$1
 org.apache.ignite.internal.managers.checkpoint.GridCheckpointManager$CheckpointSet
 org.apache.ignite.internal.managers.checkpoint.GridCheckpointRequest
@@ -426,7 +435,8 @@ org.apache.ignite.internal.processors.cache.GridCachePartitionExchangeManager$Ex
 org.apache.ignite.internal.processors.cache.GridCachePartitionExchangeManager$MessageHandler
 org.apache.ignite.internal.processors.cache.GridCacheProcessor$2
 org.apache.ignite.internal.processors.cache.GridCacheProcessor$3
-org.apache.ignite.internal.processors.cache.GridCacheProcessor$5
+org.apache.ignite.internal.processors.cache.GridCacheProcessor$4
+org.apache.ignite.internal.processors.cache.GridCacheProcessor$6
 org.apache.ignite.internal.processors.cache.GridCacheProcessor$LocalAffinityFunction
 org.apache.ignite.internal.processors.cache.GridCacheProxyImpl
 org.apache.ignite.internal.processors.cache.GridCacheReturn

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/yardstick/config/benchmark-query.properties
----------------------------------------------------------------------
diff --git a/modules/yardstick/config/benchmark-query.properties b/modules/yardstick/config/benchmark-query.properties
index 1a75926..486d00e 100644
--- a/modules/yardstick/config/benchmark-query.properties
+++ b/modules/yardstick/config/benchmark-query.properties
@@ -21,7 +21,7 @@
 # JVM_OPTS=${JVM_OPTS}" -DIGNITE_QUIET=false"
 
 # Uncomment to enable concurrent garbage collection (GC) if you encounter long GC pauses.
-JVM_OPTS=${JVM_OPTS}" -DIGNITE_QUIET=false" \
+JVM_OPTS=${JVM_OPTS}" -DIGNITE_QUIET=false \
   -XX:+UseParNewGC \
   -XX:+UseConcMarkSweepGC \
   -XX:+UseTLAB \
@@ -64,5 +64,6 @@ CONFIGS="\
 -cfg ${SCRIPT_DIR}/../config/ignite-localhost-config.xml -nn ${nodesNum} -b 1 -w 60 -d 300 -t 64 -sm PRIMARY_SYNC -dn IgniteSqlQueryJoinOffHeapBenchmark -sn IgniteNode -ds sql-query-join-offheap-1-backup,\
 -cfg ${SCRIPT_DIR}/../config/ignite-localhost-config.xml -nn ${nodesNum} -b 1 -w 60 -d 300 -t 64 -sm PRIMARY_SYNC -dn IgniteSqlQueryPutBenchmark -sn IgniteNode -ds sql-query-put-1-backup,\
 -cfg ${SCRIPT_DIR}/../config/ignite-localhost-config.xml -nn ${nodesNum} -b 1 -w 60 -d 300 -t 64 -sm PRIMARY_SYNC -dn IgniteSqlQueryPutOffHeapBenchmark -sn IgniteNode -ds sql-query-put-offheap-1-backup,\
--cfg ${SCRIPT_DIR}/../config/ignite-localhost-config.xml -nn ${nodesNum} -b 1 -w 60 -d 300 -jdbc jdbc:ignite://127.0.0.1/query -t 64 -sm PRIMARY_SYNC -dn IgniteJdbcSqlQueryBenchmark -sn IgniteNode -ds sql-query-jdbc-1-backup\
+-cfg ${SCRIPT_DIR}/../config/ignite-localhost-config.xml -nn ${nodesNum} -b 1 -w 60 -d 300 -jdbc jdbc:ignite://127.0.0.1/query -t 64 -sm PRIMARY_SYNC -dn IgniteJdbcSqlQueryBenchmark -sn IgniteNode -ds sql-query-jdbc-1-backup,\
+-cfg ${SCRIPT_DIR}/../config/ignite-localhost-config.xml -nn ${nodesNum} -b 1 -w 60 -d 300 -jdbc jdbc:ignite:cfg://cache=query@config/ignite-jdbc-config.xml -t 64 -sm PRIMARY_SYNC -dn IgniteJdbcSqlQueryBenchmark -sn IgniteNode -ds sql-query-jdbc-1-backup\
 "

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/yardstick/config/ignite-base-config.xml
----------------------------------------------------------------------
diff --git a/modules/yardstick/config/ignite-base-config.xml b/modules/yardstick/config/ignite-base-config.xml
index c77cc9a..6e94b3c 100644
--- a/modules/yardstick/config/ignite-base-config.xml
+++ b/modules/yardstick/config/ignite-base-config.xml
@@ -25,7 +25,7 @@
        xsi:schemaLocation="
         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
     <bean id="base-ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration" abstract="true">
-        <property name="peerClassLoadingEnabled" value="false"/>
+        <property name="peerClassLoadingEnabled" value="true"/>
 
         <property name="metricsLogFrequency" value="5000"/>
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/yardstick/config/ignite-jdbc-config.xml
----------------------------------------------------------------------
diff --git a/modules/yardstick/config/ignite-jdbc-config.xml b/modules/yardstick/config/ignite-jdbc-config.xml
new file mode 100644
index 0000000..9428858
--- /dev/null
+++ b/modules/yardstick/config/ignite-jdbc-config.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  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.
+-->
+
+<!--
+    Ignite Spring configuration file.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+        http://www.springframework.org/schema/beans
+        http://www.springframework.org/schema/beans/spring-beans.xsd">
+    <bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
+        <property name="clientMode" value="true"/>
+
+        <property name="peerClassLoadingEnabled" value="true"/>
+
+        <property name="localHost" value="127.0.0.1"/>
+
+        <property name="marshaller">
+            <bean class="org.apache.ignite.marshaller.optimized.OptimizedMarshaller">
+                <property name="requireSerializable" value="false"/>
+            </bean>
+        </property>
+
+        <property name="discoverySpi">
+            <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
+                <property name="ipFinder">
+                    <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">
+                        <property name="addresses">
+                            <list>
+                                <value>127.0.0.1:47500..47549</value>
+                            </list>
+                        </property>
+                    </bean>
+                </property>
+            </bean>
+        </property>
+    </bean>
+</beans>


[2/4] ignite git commit: ignite-1250 JDBC driver: migration to embedded Ignite client node

Posted by vk...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcDatabaseMetadata.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcDatabaseMetadata.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcDatabaseMetadata.java
new file mode 100644
index 0000000..98a2563
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcDatabaseMetadata.java
@@ -0,0 +1,1401 @@
+/*
+ * 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.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.RowIdLifetime;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.internal.processors.cache.IgniteCacheProxy;
+import org.apache.ignite.internal.processors.cache.query.GridCacheSqlIndexMetadata;
+import org.apache.ignite.internal.processors.cache.query.GridCacheSqlMetadata;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteCallable;
+import org.apache.ignite.resources.IgniteInstanceResource;
+
+import static java.sql.Connection.TRANSACTION_NONE;
+import static java.sql.ResultSet.CONCUR_READ_ONLY;
+import static java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT;
+import static java.sql.RowIdLifetime.ROWID_UNSUPPORTED;
+
+/**
+ * JDBC database metadata implementation.
+ */
+public class JdbcDatabaseMetadata implements DatabaseMetaData {
+    /** Connection. */
+    private final JdbcConnection conn;
+
+    /** Metadata. */
+    private Map<String, Map<String, Map<String, String>>> meta;
+
+    /** Index info. */
+    private Collection<List<Object>> indexes;
+
+    /**
+     * @param conn Connection.
+     */
+    JdbcDatabaseMetadata(JdbcConnection conn) {
+        this.conn = conn;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean allProceduresAreCallable() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean allTablesAreSelectable() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getURL() throws SQLException {
+        return conn.url();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getUserName() throws SQLException {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isReadOnly() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean nullsAreSortedHigh() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean nullsAreSortedLow() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean nullsAreSortedAtStart() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean nullsAreSortedAtEnd() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getDatabaseProductName() throws SQLException {
+        return "Ignite Cache";
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getDatabaseProductVersion() throws SQLException {
+        return "4.1.0";
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getDriverName() throws SQLException {
+        return "Ignite JDBC Driver";
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getDriverVersion() throws SQLException {
+        return "1.0";
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getDriverMajorVersion() {
+        return 1;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getDriverMinorVersion() {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean usesLocalFiles() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean usesLocalFilePerTable() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsMixedCaseIdentifiers() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean storesUpperCaseIdentifiers() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean storesLowerCaseIdentifiers() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean storesMixedCaseIdentifiers() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean storesUpperCaseQuotedIdentifiers() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean storesLowerCaseQuotedIdentifiers() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean storesMixedCaseQuotedIdentifiers() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getIdentifierQuoteString() throws SQLException {
+        return " ";
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getSQLKeywords() throws SQLException {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getNumericFunctions() throws SQLException {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getStringFunctions() throws SQLException {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getSystemFunctions() throws SQLException {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getTimeDateFunctions() throws SQLException {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getSearchStringEscape() throws SQLException {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getExtraNameCharacters() throws SQLException {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsAlterTableWithAddColumn() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsAlterTableWithDropColumn() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsColumnAliasing() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean nullPlusNonNullIsNull() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsConvert() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsConvert(int fromType, int toType) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsTableCorrelationNames() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsDifferentTableCorrelationNames() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsExpressionsInOrderBy() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsOrderByUnrelated() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsGroupBy() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsGroupByUnrelated() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsGroupByBeyondSelect() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsLikeEscapeClause() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsMultipleResultSets() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsMultipleTransactions() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsNonNullableColumns() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsMinimumSQLGrammar() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsCoreSQLGrammar() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsExtendedSQLGrammar() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsANSI92EntryLevelSQL() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsANSI92IntermediateSQL() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsANSI92FullSQL() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsIntegrityEnhancementFacility() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsOuterJoins() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsFullOuterJoins() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsLimitedOuterJoins() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getSchemaTerm() throws SQLException {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getProcedureTerm() throws SQLException {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getCatalogTerm() throws SQLException {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isCatalogAtStart() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getCatalogSeparator() throws SQLException {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsSchemasInDataManipulation() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsSchemasInProcedureCalls() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsSchemasInTableDefinitions() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsSchemasInIndexDefinitions() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsCatalogsInDataManipulation() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsCatalogsInProcedureCalls() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsCatalogsInTableDefinitions() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsCatalogsInIndexDefinitions() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsPositionedDelete() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsPositionedUpdate() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsSelectForUpdate() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsStoredProcedures() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsSubqueriesInComparisons() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsSubqueriesInExists() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsSubqueriesInIns() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsSubqueriesInQuantifieds() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsCorrelatedSubqueries() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsUnion() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsUnionAll() throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsOpenCursorsAcrossCommit() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsOpenCursorsAcrossRollback() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsOpenStatementsAcrossCommit() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsOpenStatementsAcrossRollback() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxBinaryLiteralLength() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxCharLiteralLength() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxColumnNameLength() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxColumnsInGroupBy() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxColumnsInIndex() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxColumnsInOrderBy() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxColumnsInSelect() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxColumnsInTable() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxConnections() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxCursorNameLength() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxIndexLength() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxSchemaNameLength() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxProcedureNameLength() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxCatalogNameLength() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxRowSize() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean doesMaxRowSizeIncludeBlobs() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxStatementLength() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxStatements() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxTableNameLength() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxTablesInSelect() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getMaxUserNameLength() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getDefaultTransactionIsolation() throws SQLException {
+        return TRANSACTION_NONE;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsTransactions() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsTransactionIsolationLevel(int level) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsDataManipulationTransactionsOnly() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean dataDefinitionCausesTransactionCommit() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean dataDefinitionIgnoredInTransactions() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getProcedures(String catalog, String schemaPtrn,
+        String procedureNamePtrn) throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Arrays.asList("PROCEDURE_CAT", "PROCEDURE_SCHEM", "PROCEDURE_NAME",
+                "REMARKS", "PROCEDURE_TYPE", "SPECIFIC_NAME"),
+            Arrays.asList(String.class.getName(), String.class.getName(), String.class.getName(),
+                String.class.getName(), Short.class.getName(), String.class.getName()),
+            Collections.<List<?>>emptyList(), true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getProcedureColumns(String catalog, String schemaPtrn, String procedureNamePtrn,
+        String colNamePtrn) throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Arrays.asList("PROCEDURE_CAT", "PROCEDURE_SCHEM", "PROCEDURE_NAME",
+                "COLUMN_NAME", "COLUMN_TYPE", "DATA_TYPE", "TYPE_NAME", "PRECISION",
+                "LENGTH", "SCALE", "RADIX", "NULLABLE", "REMARKS", "COLUMN_DEF",
+                "SQL_DATA_TYPE", "SQL_DATETIME_SUB", "CHAR_OCTET_LENGTH",
+                "ORDINAL_POSITION", "IS_NULLABLE", "SPECIFIC_NAME"),
+            Arrays.asList(String.class.getName(), String.class.getName(), String.class.getName(),
+                String.class.getName(), Short.class.getName(), Integer.class.getName(), String.class.getName(),
+                Integer.class.getName(), Integer.class.getName(), Short.class.getName(), Short.class.getName(),
+                Short.class.getName(), String.class.getName(), String.class.getName(), Integer.class.getName(),
+                Integer.class.getName(), Integer.class.getName(), Integer.class.getName(), String.class.getName(),
+                String.class.getName()),
+            Collections.<List<?>>emptyList(), true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getTables(String catalog, String schemaPtrn, String tblNamePtrn,
+        String[] tblTypes) throws SQLException {
+        updateMetaData();
+
+        List<List<?>> rows = new LinkedList<>();
+
+        if (tblTypes == null || Arrays.asList(tblTypes).contains("TABLE"))
+            for (Map.Entry<String, Map<String, Map<String, String>>> schema : meta.entrySet())
+                if (matches(schema.getKey(), schemaPtrn))
+                    for (String tbl : schema.getValue().keySet())
+                        if (matches(tbl, tblNamePtrn))
+                            rows.add(tableRow(schema.getKey(), tbl));
+
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Arrays.asList("TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "TABLE_TYPE", "REMARKS", "TYPE_CAT",
+                "TYPE_SCHEM", "TYPE_NAME", "SELF_REFERENCING_COL_NAME", "REF_GENERATION"),
+            Arrays.asList(String.class.getName(), String.class.getName(), String.class.getName(),
+                String.class.getName(), String.class.getName(), String.class.getName(), String.class.getName(),
+                String.class.getName(), String.class.getName(), String.class.getName()),
+            rows, true
+        );
+    }
+
+    /**
+     * @param schema Schema name.
+     * @param tbl Table name.
+     * @return Table metadata row.
+     */
+    private List<Object> tableRow(String schema, String tbl) {
+        List<Object> row = new ArrayList<>(10);
+
+        row.add(null);
+        row.add(schema);
+        row.add(tbl.toUpperCase());
+        row.add("TABLE");
+        row.add(null);
+        row.add(null);
+        row.add(null);
+        row.add(null);
+        row.add(null);
+        row.add(null);
+
+        return row;
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getSchemas() throws SQLException {
+        return getSchemas(null, "%");
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getCatalogs() throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Collections.singletonList("TABLE_CAT"),
+            Collections.singletonList(String.class.getName()),
+            Collections.<List<?>>emptyList(),
+            true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getTableTypes() throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Collections.singletonList("TABLE_TYPE"),
+            Collections.singletonList(String.class.getName()),
+            Collections.<List<?>>singletonList(Collections.singletonList("TABLE")),
+            true);
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getColumns(String catalog, String schemaPtrn, String tblNamePtrn,
+        String colNamePtrn) throws SQLException {
+        updateMetaData();
+
+        List<List<?>> rows = new LinkedList<>();
+
+        int cnt = 0;
+
+        for (Map.Entry<String, Map<String, Map<String, String>>> schema : meta.entrySet())
+            if (matches(schema.getKey(), schemaPtrn))
+                for (Map.Entry<String, Map<String, String>> tbl : schema.getValue().entrySet())
+                    if (matches(tbl.getKey(), tblNamePtrn))
+                        for (Map.Entry<String, String> col : tbl.getValue().entrySet())
+                            rows.add(columnRow(schema.getKey(), tbl.getKey(), col.getKey(),
+                                JdbcUtils.type(col.getValue()), JdbcUtils.typeName(col.getValue()),
+                                JdbcUtils.nullable(col.getKey(), col.getValue()), ++cnt));
+
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Arrays.asList("TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "COLUMN_NAME", "DATA_TYPE",
+                "TYPE_NAME", "COLUMN_SIZE", "DECIMAL_DIGITS", "NUM_PREC_RADIX", "NULLABLE",
+                "REMARKS", "COLUMN_DEF", "CHAR_OCTET_LENGTH", "ORDINAL_POSITION", "IS_NULLABLE",
+                "SCOPE_CATLOG", "SCOPE_SCHEMA", "SCOPE_TABLE", "SOURCE_DATA_TYPE", "IS_AUTOINCREMENT"),
+            Arrays.asList(String.class.getName(), String.class.getName(), String.class.getName(),
+                String.class.getName(), Integer.class.getName(), String.class.getName(), Integer.class.getName(),
+                Integer.class.getName(), Integer.class.getName(), Integer.class.getName(), String.class.getName(),
+                String.class.getName(), Integer.class.getName(), Integer.class.getName(), String.class.getName(),
+                String.class.getName(), String.class.getName(), String.class.getName(), Short.class.getName(),
+                String.class.getName()),
+            rows, true
+        );
+    }
+
+    /**
+     * @param schema Schema name.
+     * @param tbl Table name.
+     * @param col Column name.
+     * @param type Type.
+     * @param typeName Type name.
+     * @param nullable Nullable flag.
+     * @param pos Ordinal position.
+     * @return Column metadata row.
+     */
+    private List<Object> columnRow(String schema, String tbl, String col, int type, String typeName,
+        boolean nullable, int pos) {
+        List<Object> row = new ArrayList<>(20);
+
+        row.add(null);
+        row.add(schema);
+        row.add(tbl);
+        row.add(col);
+        row.add(type);
+        row.add(typeName);
+        row.add(null);
+        row.add(null);
+        row.add(10);
+        row.add(nullable ? columnNullable : columnNoNulls);
+        row.add(null);
+        row.add(null);
+        row.add(Integer.MAX_VALUE);
+        row.add(pos);
+        row.add("YES");
+        row.add(null);
+        row.add(null);
+        row.add(null);
+        row.add(null);
+        row.add("NO");
+
+        return row;
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getColumnPrivileges(String catalog, String schema, String tbl,
+        String colNamePtrn) throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<List<?>>emptyList(),
+            true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getTablePrivileges(String catalog, String schemaPtrn,
+        String tblNamePtrn) throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<List<?>>emptyList(),
+            true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getBestRowIdentifier(String catalog, String schema, String tbl, int scope,
+        boolean nullable) throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<List<?>>emptyList(),
+            true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getVersionColumns(String catalog, String schema, String tbl) throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<List<?>>emptyList(),
+            true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getPrimaryKeys(String catalog, String schema, String tbl) throws SQLException {
+        updateMetaData();
+
+        List<List<?>> rows = new LinkedList<>();
+
+        for (Map.Entry<String, Map<String, Map<String, String>>> s : meta.entrySet())
+            if (schema == null || schema.toUpperCase().equals(s.getKey()))
+                for (Map.Entry<String, Map<String, String>> t : s.getValue().entrySet())
+                    if (tbl == null || tbl.toUpperCase().equals(t.getKey()))
+                        rows.add(Arrays.<Object>asList(null, s.getKey().toUpperCase(),
+                            t.getKey().toUpperCase(), "_KEY", 1, "_KEY"));
+
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Arrays.asList("TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "COLUMN_NAME", "KEY_SEQ", "PK_NAME"),
+            Arrays.asList(String.class.getName(), String.class.getName(), String.class.getName(),
+                String.class.getName(), Short.class.getName(), String.class.getName()),
+            rows, true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getImportedKeys(String catalog, String schema, String tbl) throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<List<?>>emptyList(),
+            true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getExportedKeys(String catalog, String schema, String tbl) throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<List<?>>emptyList(),
+            true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTbl,
+        String foreignCatalog, String foreignSchema, String foreignTbl) throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<List<?>>emptyList(),
+            true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getTypeInfo() throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<List<?>>emptyList(),
+            true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getIndexInfo(String catalog, String schema, String tbl, boolean unique,
+        boolean approximate) throws SQLException {
+        Collection<List<?>> rows = new ArrayList<>(indexes.size());
+
+        for (List<Object> idx : indexes) {
+            String idxSchema = (String)idx.get(0);
+            String idxTbl = (String)idx.get(1);
+
+            if ((schema == null || schema.equals(idxSchema)) && (tbl == null || tbl.equals(idxTbl))) {
+                List<Object> row = new ArrayList<>(13);
+
+                row.add(null);
+                row.add(idxSchema);
+                row.add(idxTbl);
+                row.add(idx.get(2));
+                row.add(null);
+                row.add(idx.get(3));
+                row.add((int)tableIndexOther);
+                row.add(idx.get(4));
+                row.add(idx.get(5));
+                row.add((Boolean)idx.get(6) ? "D" : "A");
+                row.add(0);
+                row.add(0);
+                row.add(null);
+
+                rows.add(row);
+            }
+        }
+
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Arrays.asList("TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "NON_UNIQUE", "INDEX_QUALIFIER",
+                "INDEX_NAME", "TYPE", "ORDINAL_POSITION", "COLUMN_NAME", "ASC_OR_DESC", "CARDINALITY",
+                "PAGES", "FILTER_CONDITION"),
+            Arrays.asList(String.class.getName(), String.class.getName(), String.class.getName(),
+                Boolean.class.getName(), String.class.getName(), String.class.getName(), Short.class.getName(),
+                Short.class.getName(), String.class.getName(), String.class.getName(), Integer.class.getName(),
+                Integer.class.getName(), String.class.getName()),
+            rows, true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsResultSetType(int type) throws SQLException {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException {
+        return concurrency == CONCUR_READ_ONLY;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean ownUpdatesAreVisible(int type) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean ownDeletesAreVisible(int type) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean ownInsertsAreVisible(int type) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean othersUpdatesAreVisible(int type) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean othersDeletesAreVisible(int type) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean othersInsertsAreVisible(int type) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean updatesAreDetected(int type) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean deletesAreDetected(int type) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean insertsAreDetected(int type) throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsBatchUpdates() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getUDTs(String catalog, String schemaPtrn, String typeNamePtrn,
+        int[] types) throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<List<?>>emptyList(),
+            true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public Connection getConnection() throws SQLException {
+        return conn;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsSavepoints() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsNamedParameters() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsMultipleOpenResults() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsGetGeneratedKeys() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getSuperTypes(String catalog, String schemaPtrn,
+        String typeNamePtrn) throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<List<?>>emptyList(),
+            true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getSuperTables(String catalog, String schemaPtrn,
+        String tblNamePtrn) throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<List<?>>emptyList(),
+            true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getAttributes(String catalog, String schemaPtrn, String typeNamePtrn,
+        String attributeNamePtrn) throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<List<?>>emptyList(),
+            true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsResultSetHoldability(int holdability) throws SQLException {
+        return holdability == HOLD_CURSORS_OVER_COMMIT;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getResultSetHoldability() throws SQLException {
+        return HOLD_CURSORS_OVER_COMMIT;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getDatabaseMajorVersion() throws SQLException {
+        return 1;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getDatabaseMinorVersion() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getJDBCMajorVersion() throws SQLException {
+        return 1;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getJDBCMinorVersion() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getSQLStateType() throws SQLException {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean locatorsUpdateCopy() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsStatementPooling() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RowIdLifetime getRowIdLifetime() throws SQLException {
+        return ROWID_UNSUPPORTED;
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getSchemas(String catalog, String schemaPtrn) throws SQLException {
+        updateMetaData();
+
+        List<List<?>> rows = new ArrayList<>(meta.size());
+
+        for (String schema : meta.keySet())
+            if (matches(schema, schemaPtrn))
+                rows.add(Arrays.<Object>asList(schema, null));
+
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Arrays.asList("TABLE_SCHEM", "TABLE_CATALOG"),
+            Arrays.asList(String.class.getName(), String.class.getName()),
+            rows, true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean autoCommitFailureClosesAllResultSets() throws SQLException {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getClientInfoProperties() throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<List<?>>emptyList(),
+            true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getFunctions(String catalog, String schemaPtrn,
+        String functionNamePtrn) throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Arrays.asList("FUNCTION_CAT", "FUNCTION_SCHEM", "FUNCTION_NAME",
+                "REMARKS", "FUNCTION_TYPE", "SPECIFIC_NAME"),
+            Arrays.asList(String.class.getName(), String.class.getName(), String.class.getName(),
+                String.class.getName(), Short.class.getName(), String.class.getName()),
+            Collections.<List<?>>emptyList(), true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getFunctionColumns(String catalog, String schemaPtrn, String functionNamePtrn,
+        String colNamePtrn) throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Arrays.asList("FUNCTION_CAT", "FUNCTION_SCHEM", "FUNCTION_NAME",
+                "COLUMN_NAME", "COLUMN_TYPE", "DATA_TYPE", "TYPE_NAME", "PRECISION",
+                "LENGTH", "SCALE", "RADIX", "NULLABLE", "REMARKS", "CHAR_OCTET_LENGTH",
+                "ORDINAL_POSITION", "IS_NULLABLE", "SPECIFIC_NAME"),
+            Arrays.asList(String.class.getName(), String.class.getName(), String.class.getName(),
+                String.class.getName(), Short.class.getName(), Integer.class.getName(), String.class.getName(),
+                Integer.class.getName(), Integer.class.getName(), Short.class.getName(), Short.class.getName(),
+                Short.class.getName(), String.class.getName(), Integer.class.getName(), Integer.class.getName(),
+                String.class.getName(), String.class.getName()),
+            Collections.<List<?>>emptyList(), true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override public <T> T unwrap(Class<T> iface) throws SQLException {
+        if (!isWrapperFor(iface))
+            throw new SQLException("Database meta data is not a wrapper for " + iface.getName());
+
+        return (T)this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isWrapperFor(Class<?> iface) throws SQLException {
+        return iface != null && iface == DatabaseMetaData.class;
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet getPseudoColumns(String catalog, String schemaPtrn, String tblNamePtrn,
+        String colNamePtrn) throws SQLException {
+        return new JdbcResultSet(null,
+            conn.createStatement0(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<String>emptyList(),
+            Collections.<List<?>>emptyList(),
+            true
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean generatedKeyAlwaysReturned() throws SQLException {
+        return false;
+    }
+
+    /**
+     * Updates meta data.
+     *
+     * @throws SQLException In case of error.
+     */
+    @SuppressWarnings("unchecked")
+    private void updateMetaData() throws SQLException {
+        if (conn.isClosed())
+            throw new SQLException("Connection is closed.");
+
+        try {
+            Ignite ignite = conn.ignite();
+
+            UUID nodeId = conn.nodeId();
+
+            Collection<GridCacheSqlMetadata> metas;
+
+            UpdateMetadataTask task = new UpdateMetadataTask(conn.cacheName(), nodeId == null ? ignite : null);
+
+            metas = nodeId == null ? task.call() : ignite.compute(ignite.cluster().forNodeId(nodeId)).call(task);
+
+            meta = U.newHashMap(metas.size());
+
+            indexes = new ArrayList<>();
+
+            for (GridCacheSqlMetadata m : metas) {
+                String name = m.cacheName();
+
+                if (name == null)
+                    name = "PUBLIC";
+
+                Collection<String> types = m.types();
+
+                Map<String, Map<String, String>> typesMap = U.newHashMap(types.size());
+
+                for (String type : types) {
+                    typesMap.put(type.toUpperCase(), m.fields(type));
+
+                    for (GridCacheSqlIndexMetadata idx : m.indexes(type)) {
+                        int cnt = 0;
+
+                        for (String field : idx.fields()) {
+                            indexes.add(F.<Object>asList(name, type.toUpperCase(), !idx.unique(),
+                                idx.name().toUpperCase(), ++cnt, field, idx.descending(field)));
+                        }
+                    }
+                }
+
+                meta.put(name, typesMap);
+            }
+        }
+        catch (Exception e) {
+            throw new SQLException("Failed to get meta data from Ignite.", e);
+        }
+    }
+
+    /**
+     * Checks whether string matches SQL pattern.
+     *
+     * @param str String.
+     * @param ptrn Pattern.
+     * @return Whether string matches pattern.
+     */
+    private boolean matches(String str, String ptrn) {
+        return str != null && (ptrn == null ||
+            str.toUpperCase().matches(ptrn.toUpperCase().replace("%", ".*").replace("_", ".")));
+    }
+
+    /**
+     *
+     */
+    private static class UpdateMetadataTask implements IgniteCallable<Collection<GridCacheSqlMetadata>> {
+        /** Serial version uid. */
+        private static final long serialVersionUID = 0L;
+
+        /** Ignite. */
+        @IgniteInstanceResource
+        private Ignite ignite;
+
+        /** Cache name. */
+        private final String cacheName;
+
+        /**
+         * @param cacheName Cache name.
+         * @param ignite Ignite.
+         */
+        public UpdateMetadataTask(String cacheName, Ignite ignite) {
+            this.cacheName = cacheName;
+            this.ignite = ignite;
+        }
+
+        /** {@inheritDoc} */
+        @SuppressWarnings("unchecked")
+        @Override public Collection<GridCacheSqlMetadata> call() throws Exception {
+            IgniteCache cache = ignite.cache(cacheName);
+
+            return ((IgniteCacheProxy)cache).context().queries().sqlMetadata();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/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
new file mode 100644
index 0000000..a99f24c
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatement.java
@@ -0,0 +1,411 @@
+/*
+ * 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.*;
+import java.math.*;
+import java.net.*;
+import java.sql.*;
+import java.sql.Date;
+import java.util.*;
+
+/**
+ * JDBC prepared statement implementation.
+ */
+public class JdbcPreparedStatement extends JdbcStatement implements PreparedStatement {
+    /** SQL query. */
+    private final String sql;
+
+    /** Arguments count. */
+    private final int argsCnt;
+
+    /**
+     * Creates new prepared statement.
+     *
+     * @param conn Connection.
+     * @param sql SQL query.
+     */
+    JdbcPreparedStatement(JdbcConnection conn, String sql) {
+        super(conn);
+
+        this.sql = sql;
+
+        argsCnt = sql.replaceAll("[^?]", "").length();
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSet executeQuery() throws SQLException {
+        ResultSet rs = executeQuery(sql);
+
+        args = null;
+
+        return rs;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int executeUpdate() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setNull(int paramIdx, int sqlType) throws SQLException {
+        setArgument(paramIdx, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setBoolean(int paramIdx, boolean x) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setByte(int paramIdx, byte x) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setShort(int paramIdx, short x) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setInt(int paramIdx, int x) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setLong(int paramIdx, long x) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setFloat(int paramIdx, float x) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setDouble(int paramIdx, double x) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setBigDecimal(int paramIdx, BigDecimal x) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setString(int paramIdx, String x) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setBytes(int paramIdx, byte[] x) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setDate(int paramIdx, Date x) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setTime(int paramIdx, Time x) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setTimestamp(int paramIdx, Timestamp x) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setAsciiStream(int paramIdx, InputStream x, int len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setUnicodeStream(int paramIdx, InputStream x, int len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setBinaryStream(int paramIdx, InputStream x, int len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void clearParameters() throws SQLException {
+        ensureNotClosed();
+
+        args = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setObject(int paramIdx, Object x, int targetSqlType) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setObject(int paramIdx, Object x) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean execute() throws SQLException {
+        return execute(sql);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void addBatch() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setCharacterStream(int paramIdx, Reader x, int len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setRef(int paramIdx, Ref x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setBlob(int paramIdx, Blob x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setClob(int paramIdx, Clob x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setArray(int paramIdx, Array x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public ResultSetMetaData getMetaData() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Meta data for prepared statement is not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setDate(int paramIdx, Date x, Calendar cal) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setTime(int paramIdx, Time x, Calendar cal) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setTimestamp(int paramIdx, Timestamp x, Calendar cal) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setNull(int paramIdx, int sqlType, String typeName) throws SQLException {
+        setNull(paramIdx, sqlType);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setURL(int paramIdx, URL x) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public ParameterMetaData getParameterMetaData() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Meta data for prepared statement is not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setRowId(int paramIdx, RowId x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setNString(int paramIdx, String val) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setNCharacterStream(int paramIdx, Reader val, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setNClob(int paramIdx, NClob val) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setClob(int paramIdx, Reader reader, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setBlob(int paramIdx, InputStream inputStream, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setNClob(int paramIdx, Reader reader, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setSQLXML(int paramIdx, SQLXML xmlObj) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setObject(int paramIdx, Object x, int targetSqlType,
+        int scaleOrLen) throws SQLException {
+        setArgument(paramIdx, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setAsciiStream(int paramIdx, InputStream x, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setBinaryStream(int paramIdx, InputStream x, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setCharacterStream(int paramIdx, Reader x, long len) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setAsciiStream(int paramIdx, InputStream x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setBinaryStream(int paramIdx, InputStream x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setCharacterStream(int paramIdx, Reader x) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Streams are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setNCharacterStream(int paramIdx, Reader val) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setClob(int paramIdx, Reader reader) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setBlob(int paramIdx, InputStream inputStream) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setNClob(int paramIdx, Reader reader) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /**
+     * Sets query argument value.
+     *
+     * @param paramIdx Index.
+     * @param val Value.
+     * @throws SQLException If index is invalid.
+     */
+    private void setArgument(int paramIdx, Object val) throws SQLException {
+        ensureNotClosed();
+
+        if (paramIdx < 1 || paramIdx > argsCnt)
+            throw new SQLException("Parameter index is invalid: " + paramIdx);
+
+        if (args == null)
+            args = new Object[argsCnt];
+
+        args[paramIdx - 1] = val;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcQueryTask.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcQueryTask.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcQueryTask.java
new file mode 100644
index 0000000..ac711b8
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcQueryTask.java
@@ -0,0 +1,361 @@
+/*
+ * 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.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteJdbcDriver;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.cache.query.QueryCursor;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.internal.processors.cache.QueryCursorImpl;
+import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata;
+import org.apache.ignite.internal.util.typedef.CAX;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteCallable;
+import org.apache.ignite.resources.IgniteInstanceResource;
+
+/**
+ * Task for SQL queries execution through {@link IgniteJdbcDriver}.
+ * <p>
+ * Not closed cursors will be removed after {@link #RMV_DELAY} milliseconds.
+ * This parameter can be configured via {@link IgniteSystemProperties#IGNITE_JDBC_DRIVER_CURSOR_REMOVE_DELAY}
+ * system property.
+ */
+class JdbcQueryTask implements IgniteCallable<JdbcQueryTask.QueryResult> {
+    /** Serial version uid. */
+    private static final long serialVersionUID = 0L;
+
+    /** How long to store open cursor. */
+    private static final long RMV_DELAY = IgniteSystemProperties.getLong(
+        IgniteSystemProperties.IGNITE_JDBC_DRIVER_CURSOR_REMOVE_DELAY, 600000);
+
+    /** Scheduler. */
+    private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1);
+
+    /** Open cursors. */
+    private static final ConcurrentMap<UUID, Cursor> CURSORS = new ConcurrentHashMap<>();
+
+    /** Ignite. */
+    @IgniteInstanceResource
+    private Ignite ignite;
+
+    /** Uuid. */
+    private final UUID uuid;
+
+    /** Cache name. */
+    private final String cacheName;
+
+    /** Sql. */
+    private final String sql;
+
+    /** Args. */
+    private final Object[] args;
+
+    /** Fetch size. */
+    private final int fetchSize;
+
+    /** Local execution flag. */
+    private final boolean loc;
+
+    /** Local query flag. */
+    private final boolean locQry;
+
+    /** Collocated query flag. */
+    private final boolean collocatedQry;
+
+    /**
+     * @param ignite Ignite.
+     * @param cacheName Cache name.
+     * @param sql Sql query.
+     * @param loc Local execution flag.
+     * @param args Args.
+     * @param fetchSize Fetch size.
+     * @param uuid UUID.
+     * @param locQry Local query flag.
+     * @param collocatedQry Collocated query flag.
+     */
+    public JdbcQueryTask(Ignite ignite, String cacheName, String sql,
+        boolean loc, Object[] args, int fetchSize, UUID uuid, boolean locQry, boolean collocatedQry) {
+        this.ignite = ignite;
+        this.args = args;
+        this.uuid = uuid;
+        this.cacheName = cacheName;
+        this.sql = sql;
+        this.fetchSize = fetchSize;
+        this.loc = loc;
+        this.locQry = locQry;
+        this.collocatedQry = collocatedQry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public JdbcQueryTask.QueryResult call() throws Exception {
+        Cursor cursor = CURSORS.get(uuid);
+
+        List<String> tbls = null;
+        List<String> cols = null;
+        List<String> types = null;
+
+        boolean first;
+
+        if (first = (cursor == null)) {
+            IgniteCache<?, ?> cache = ignite.cache(cacheName);
+
+            SqlFieldsQuery qry = new SqlFieldsQuery(sql).setArgs(args);
+
+            qry.setPageSize(fetchSize);
+            qry.setLocal(locQry);
+            qry.setCollocated(collocatedQry);
+
+            QueryCursor<List<?>> qryCursor = cache.query(qry);
+
+            Collection<GridQueryFieldMetadata> meta = ((QueryCursorImpl<List<?>>)qryCursor).fieldsMeta();
+
+            tbls = new ArrayList<>(meta.size());
+            cols = new ArrayList<>(meta.size());
+            types = new ArrayList<>(meta.size());
+
+            for (GridQueryFieldMetadata desc : meta) {
+                tbls.add(desc.typeName());
+                cols.add(desc.fieldName().toUpperCase());
+                types.add(desc.fieldTypeName());
+            }
+
+            CURSORS.put(uuid, cursor = new Cursor(qryCursor, qryCursor.iterator()));
+        }
+
+        List<List<?>> rows = new ArrayList<>();
+
+        for (List<?> row : cursor) {
+            List<Object> row0 = new ArrayList<>(row.size());
+
+            for (Object val : row)
+                row0.add(JdbcUtils.sqlType(val) ? val : val.toString());
+
+            rows.add(row0);
+
+            if (rows.size() == fetchSize) // If fetchSize is 0 then unlimited
+                break;
+        }
+
+        boolean finished = !cursor.hasNext();
+
+        if (finished)
+            remove(uuid, cursor);
+        else if (first) {
+            if (!loc)
+                scheduleRemoval(uuid, RMV_DELAY);
+        }
+        else if (!loc && !CURSORS.replace(uuid, cursor, new Cursor(cursor.cursor, cursor.iter)))
+            assert !CURSORS.containsKey(uuid) : "Concurrent cursor modification.";
+
+        return new QueryResult(uuid, finished, rows, cols, tbls, types);
+    }
+
+    /**
+     * Schedules removal of stored cursor in case of remote query execution.
+     *
+     * @param uuid Cursor UUID.
+     * @param delay Delay in milliseconds.
+     */
+    private void scheduleRemoval(final UUID uuid, long delay) {
+        assert !loc;
+
+        SCHEDULER.schedule(new CAX() {
+            @Override public void applyx() {
+                while (true) {
+                    Cursor c = CURSORS.get(uuid);
+
+                    if (c == null)
+                        break;
+
+                    // If the cursor was accessed since last scheduling then reschedule.
+                    long untouchedTime = U.currentTimeMillis() - c.lastAccessTime;
+
+                    if (untouchedTime < RMV_DELAY) {
+                        scheduleRemoval(uuid, RMV_DELAY - untouchedTime);
+
+                        break;
+                    }
+                    else if (remove(uuid, c))
+                        break;
+                }
+            }
+        }, delay, TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * @param uuid Cursor UUID.
+     * @param c Cursor.
+     * @return {@code true} If succeeded.
+     */
+    private static boolean remove(UUID uuid, Cursor c) {
+        boolean rmv = CURSORS.remove(uuid, c);
+
+        if (rmv)
+            c.cursor.close();
+
+        return rmv;
+    }
+
+    /**
+     * Closes and removes cursor.
+     *
+     * @param uuid Cursor UUID.
+     */
+    static void remove(UUID uuid) {
+        Cursor c = CURSORS.remove(uuid);
+
+        if (c != null)
+            c.cursor.close();
+    }
+
+
+    /**
+     * Result of query execution.
+     */
+    static class QueryResult implements Serializable {
+        /** Serial version uid. */
+        private static final long serialVersionUID = 0L;
+
+        /** Uuid. */
+        private final UUID uuid;
+
+        /** Finished. */
+        private final boolean finished;
+
+        /** Rows. */
+        private final List<List<?>> rows;
+
+        /** Tables. */
+        private final List<String> tbls;
+
+        /** Columns. */
+        private final List<String> cols;
+
+        /** Types. */
+        private final List<String> types;
+
+        /**
+         * @param uuid UUID..
+         * @param finished Finished.
+         * @param rows Rows.
+         * @param cols Columns.
+         * @param tbls Tables.
+         * @param types Types.
+         */
+        public QueryResult(UUID uuid, boolean finished, List<List<?>> rows, List<String> cols,
+            List<String> tbls, List<String> types) {
+            this.cols = cols;
+            this.uuid = uuid;
+            this.finished = finished;
+            this.rows = rows;
+            this.tbls = tbls;
+            this.types = types;
+        }
+
+        /**
+         * @return Query result rows.
+         */
+        public List<List<?>> getRows() {
+            return rows;
+        }
+
+        /**
+         * @return Tables metadata.
+         */
+        public List<String> getTbls() {
+            return tbls;
+        }
+
+        /**
+         * @return Columns metadata.
+         */
+        public List<String> getCols() {
+            return cols;
+        }
+
+        /**
+         * @return Types metadata.
+         */
+        public List<String> getTypes() {
+            return types;
+        }
+
+        /**
+         * @return Query UUID.
+         */
+        public UUID getUuid() {
+            return uuid;
+        }
+
+        /**
+         * @return {@code True} if it is finished query.
+         */
+        public boolean isFinished() {
+            return finished;
+        }
+    }
+
+    /**
+     * Cursor.
+     */
+    private static final class Cursor implements Iterable<List<?>> {
+        /** Cursor. */
+        final QueryCursor<List<?>> cursor;
+
+        /** Iterator. */
+        final Iterator<List<?>> iter;
+
+        /** Last access time. */
+        final long lastAccessTime;
+
+        /**
+         * @param cursor Cursor.
+         * @param iter Iterator.
+         */
+        private Cursor(QueryCursor<List<?>> cursor, Iterator<List<?>> iter) {
+            this.cursor = cursor;
+            this.iter = iter;
+            this.lastAccessTime = U.currentTimeMillis();
+        }
+
+        /** {@inheritDoc} */
+        @Override public Iterator<List<?>> iterator() {
+            return iter;
+        }
+
+        /**
+         * @return {@code True} if cursor has next element.
+         */
+        public boolean hasNext() {
+            return iter.hasNext();
+        }
+    }
+}


[4/4] ignite git commit: ignite-1250 JDBC driver: migration to embedded Ignite client node

Posted by vk...@apache.org.
ignite-1250 JDBC driver: migration to embedded Ignite client node

Signed-off-by: Valentin Kulichenko <va...@gmail.com>


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

Branch: refs/heads/ignite-1.4
Commit: ebb9e2e9d3e05ba65c06ec301bee040b3a74fd3b
Parents: 1ff4a52
Author: Andrey Gura <ag...@gridgain.com>
Authored: Fri Sep 11 18:32:54 2015 -0700
Committer: Valentin Kulichenko <va...@gmail.com>
Committed: Fri Sep 11 18:32:54 2015 -0700

----------------------------------------------------------------------
 modules/clients/pom.xml                         |    7 +
 modules/clients/src/test/config/jdbc-config.xml |   55 +
 .../jdbc2/JdbcComplexQuerySelfTest.java         |  316 ++++
 .../internal/jdbc2/JdbcConnectionSelfTest.java  |  268 +++
 .../internal/jdbc2/JdbcEmptyCacheSelfTest.java  |  140 ++
 .../internal/jdbc2/JdbcLocalCachesSelfTest.java |  156 ++
 .../internal/jdbc2/JdbcMetadataSelfTest.java    |  334 ++++
 .../jdbc2/JdbcPreparedStatementSelfTest.java    |  730 +++++++++
 .../internal/jdbc2/JdbcResultSetSelfTest.java   |  751 +++++++++
 .../internal/jdbc2/JdbcStatementSelfTest.java   |  292 ++++
 .../jdbc/suite/IgniteJdbcDriverTestSuite.java   |   11 +
 .../org/apache/ignite/IgniteJdbcDriver.java     |  281 +++-
 .../apache/ignite/IgniteSystemProperties.java   |    5 +-
 .../ignite/internal/jdbc/JdbcConnection.java    |    4 +
 .../internal/jdbc/JdbcConnectionInfo.java       |   91 --
 .../internal/jdbc/JdbcDatabaseMetadata.java     |    4 +
 .../internal/jdbc/JdbcPreparedStatement.java    |    4 +
 .../ignite/internal/jdbc/JdbcResultSet.java     |    4 +
 .../internal/jdbc/JdbcResultSetMetadata.java    |    4 +
 .../ignite/internal/jdbc/JdbcStatement.java     |    4 +
 .../apache/ignite/internal/jdbc/JdbcUtils.java  |    4 +
 .../ignite/internal/jdbc2/JdbcConnection.java   |  777 +++++++++
 .../internal/jdbc2/JdbcDatabaseMetadata.java    | 1401 ++++++++++++++++
 .../internal/jdbc2/JdbcPreparedStatement.java   |  411 +++++
 .../ignite/internal/jdbc2/JdbcQueryTask.java    |  361 +++++
 .../ignite/internal/jdbc2/JdbcResultSet.java    | 1520 ++++++++++++++++++
 .../internal/jdbc2/JdbcResultSetMetadata.java   |  171 ++
 .../ignite/internal/jdbc2/JdbcStatement.java    |  456 ++++++
 .../apache/ignite/internal/jdbc2/JdbcUtils.java |  155 ++
 .../resources/META-INF/classnames.properties    |   12 +-
 .../yardstick/config/benchmark-query.properties |    5 +-
 modules/yardstick/config/ignite-base-config.xml |    2 +-
 modules/yardstick/config/ignite-jdbc-config.xml |   55 +
 33 files changed, 8606 insertions(+), 185 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/clients/pom.xml
----------------------------------------------------------------------
diff --git a/modules/clients/pom.xml b/modules/clients/pom.xml
index 61f6694..6e690dc 100644
--- a/modules/clients/pom.xml
+++ b/modules/clients/pom.xml
@@ -56,6 +56,13 @@
 
         <dependency>
             <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-spring</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
             <artifactId>ignite-indexing</artifactId>
             <version>${project.version}</version>
             <scope>test</scope>

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/clients/src/test/config/jdbc-config.xml
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/config/jdbc-config.xml b/modules/clients/src/test/config/jdbc-config.xml
new file mode 100644
index 0000000..980eaf1
--- /dev/null
+++ b/modules/clients/src/test/config/jdbc-config.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  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.
+-->
+
+<!--
+    Ignite Spring configuration file.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+        http://www.springframework.org/schema/beans
+        http://www.springframework.org/schema/beans/spring-beans.xsd">
+    <bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
+        <property name="clientMode" value="true"/>
+
+        <property name="localHost" value="127.0.0.1"/>
+
+        <property name="marshaller">
+            <bean class="org.apache.ignite.marshaller.optimized.OptimizedMarshaller">
+                <property name="requireSerializable" value="false"/>
+            </bean>
+        </property>
+
+        <property name="discoverySpi">
+            <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
+                <property name="ipFinder">
+                    <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">
+                        <property name="addresses">
+                            <list>
+                                <value>127.0.0.1:47500..47549</value>
+                            </list>
+                        </property>
+                    </bean>
+                </property>
+            </bean>
+        </property>
+
+        <property name="peerClassLoadingEnabled" value="true"/>
+    </bean>
+</beans>

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcComplexQuerySelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcComplexQuerySelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcComplexQuerySelfTest.java
new file mode 100644
index 0000000..d126d34
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcComplexQuerySelfTest.java
@@ -0,0 +1,316 @@
+/*
+ * 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 org.apache.ignite.*;
+import org.apache.ignite.cache.affinity.*;
+import org.apache.ignite.cache.query.annotations.*;
+import org.apache.ignite.configuration.*;
+import org.apache.ignite.internal.util.typedef.*;
+import org.apache.ignite.spi.discovery.tcp.*;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.*;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.*;
+import org.apache.ignite.testframework.junits.common.*;
+
+import java.io.*;
+import java.sql.*;
+
+import static org.apache.ignite.IgniteJdbcDriver.*;
+import static org.apache.ignite.cache.CacheAtomicityMode.*;
+import static org.apache.ignite.cache.CacheMode.*;
+import static org.apache.ignite.cache.CacheWriteSynchronizationMode.*;
+
+/**
+ * Tests for complex queries (joins, etc.).
+ */
+public class JdbcComplexQuerySelfTest extends GridCommonAbstractTest {
+    /** IP finder. */
+    private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
+
+    /** JDBC URL. */
+    private static final String BASE_URL = CFG_URL_PREFIX + "modules/clients/src/test/config/jdbc-config.xml";
+
+    /** Statement. */
+    private Statement stmt;
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String gridName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(gridName);
+
+        CacheConfiguration<?,?> cache = defaultCacheConfiguration();
+
+        cache.setCacheMode(PARTITIONED);
+        cache.setBackups(1);
+        cache.setWriteSynchronizationMode(FULL_SYNC);
+        cache.setAtomicityMode(TRANSACTIONAL);
+        cache.setIndexedTypes(String.class, Organization.class, AffinityKey.class, Person.class);
+
+        cfg.setCacheConfiguration(cache);
+
+        TcpDiscoverySpi disco = new TcpDiscoverySpi();
+
+        disco.setIpFinder(IP_FINDER);
+
+        cfg.setDiscoverySpi(disco);
+
+        cfg.setConnectorConfiguration(new ConnectorConfiguration());
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        startGrids(3);
+
+        IgniteCache<String, Organization> orgCache = grid(0).cache(null);
+
+        assert orgCache != null;
+
+        orgCache.put("o1", new Organization(1, "A"));
+        orgCache.put("o2", new Organization(2, "B"));
+
+        IgniteCache<AffinityKey<String>, Person> personCache = grid(0).cache(null);
+
+        assert personCache != null;
+
+        personCache.put(new AffinityKey<>("p1", "o1"), new Person(1, "John White", 25, 1));
+        personCache.put(new AffinityKey<>("p2", "o1"), new Person(2, "Joe Black", 35, 1));
+        personCache.put(new AffinityKey<>("p3", "o2"), new Person(3, "Mike Green", 40, 2));
+
+        Class.forName("org.apache.ignite.IgniteJdbcDriver");
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        stopAllGrids();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        stmt = DriverManager.getConnection(BASE_URL).createStatement();
+
+        assert stmt != null;
+        assert !stmt.isClosed();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        if (stmt != null) {
+            stmt.getConnection().close();
+            stmt.close();
+
+            assert stmt.isClosed();
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testJoin() throws Exception {
+        ResultSet rs = stmt.executeQuery(
+            "select p.id, p.name, o.name as orgName from Person p, Organization o where p.orgId = o.id");
+
+        assert rs != null;
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            int id = rs.getInt("id");
+
+            if (id == 1) {
+                assert "John White".equals(rs.getString("name"));
+                assert "A".equals(rs.getString("orgName"));
+            }
+            else if (id == 2) {
+                assert "Joe Black".equals(rs.getString("name"));
+                assert "A".equals(rs.getString("orgName"));
+            }
+            else if (id == 3) {
+                assert "Mike Green".equals(rs.getString("name"));
+                assert "B".equals(rs.getString("orgName"));
+            }
+            else
+                assert false : "Wrong ID: " + id;
+
+            cnt++;
+        }
+
+        assert cnt == 3;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testJoinWithoutAlias() throws Exception {
+        ResultSet rs = stmt.executeQuery(
+            "select p.id, p.name, o.name from Person p, Organization o where p.orgId = o.id");
+
+        assert rs != null;
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            int id = rs.getInt(1);
+
+            if (id == 1) {
+                assert "John White".equals(rs.getString("name"));
+                assert "John White".equals(rs.getString(2));
+                assert "A".equals(rs.getString(3));
+            }
+            else if (id == 2) {
+                assert "Joe Black".equals(rs.getString("name"));
+                assert "Joe Black".equals(rs.getString(2));
+                assert "A".equals(rs.getString(3));
+            }
+            else if (id == 3) {
+                assert "Mike Green".equals(rs.getString("name"));
+                assert "Mike Green".equals(rs.getString(2));
+                assert "B".equals(rs.getString(3));
+            }
+            else
+                assert false : "Wrong ID: " + id;
+
+            cnt++;
+        }
+
+        assert cnt == 3;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testIn() throws Exception {
+        ResultSet rs = stmt.executeQuery("select name from Person where age in (25, 35)");
+
+        assert rs != null;
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            assert "John White".equals(rs.getString("name")) ||
+                "Joe Black".equals(rs.getString("name"));
+
+            cnt++;
+        }
+
+        assert cnt == 2;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testBetween() throws Exception {
+        ResultSet rs = stmt.executeQuery("select name from Person where age between 24 and 36");
+
+        assert rs != null;
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            assert "John White".equals(rs.getString("name")) ||
+                "Joe Black".equals(rs.getString("name"));
+
+            cnt++;
+        }
+
+        assert cnt == 2;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testCalculatedValue() throws Exception {
+        ResultSet rs = stmt.executeQuery("select age * 2 from Person");
+
+        assert rs != null;
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            assert rs.getInt(1) == 50 ||
+                rs.getInt(1) == 70 ||
+                rs.getInt(1) == 80;
+
+            cnt++;
+        }
+
+        assert cnt == 3;
+    }
+
+    /**
+     * Person.
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    private static class Person implements Serializable {
+        /** ID. */
+        @QuerySqlField
+        private final int id;
+
+        /** Name. */
+        @QuerySqlField(index = false)
+        private final String name;
+
+        /** Age. */
+        @QuerySqlField
+        private final int age;
+
+        /** Organization ID. */
+        @QuerySqlField
+        private final int orgId;
+
+        /**
+         * @param id ID.
+         * @param name Name.
+         * @param age Age.
+         * @param orgId Organization ID.
+         */
+        private Person(int id, String name, int age, int orgId) {
+            assert !F.isEmpty(name);
+            assert age > 0;
+            assert orgId > 0;
+
+            this.id = id;
+            this.name = name;
+            this.age = age;
+            this.orgId = orgId;
+        }
+    }
+
+    /**
+     * Organization.
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    private static class Organization implements Serializable {
+        /** ID. */
+        @QuerySqlField
+        private final int id;
+
+        /** Name. */
+        @QuerySqlField(index = false)
+        private final String name;
+
+        /**
+         * @param id ID.
+         * @param name Name.
+         */
+        private Organization(int id, String name) {
+            this.id = id;
+            this.name = name;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java
new file mode 100644
index 0000000..951890e
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java
@@ -0,0 +1,268 @@
+/*
+ * 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.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.jetbrains.annotations.Nullable;
+
+import static org.apache.ignite.IgniteJdbcDriver.CFG_URL_PREFIX;
+
+/**
+ * Connection test.
+ */
+public class JdbcConnectionSelfTest extends GridCommonAbstractTest {
+    /** IP finder. */
+    private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
+
+    /** Custom cache name. */
+    private static final String CUSTOM_CACHE_NAME = "custom-cache";
+
+    /** Ignite configuration URL. */
+    private static final String CFG_URL = "modules/clients/src/test/config/jdbc-config.xml";
+
+    /** Grid count. */
+    private static final int GRID_CNT = 2;
+
+    /** Daemon node flag. */
+    private boolean daemon;
+
+    /** Client node flag. */
+    private boolean client;
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String gridName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(gridName);
+
+        cfg.setCacheConfiguration(cacheConfiguration(null), cacheConfiguration(CUSTOM_CACHE_NAME));
+
+        TcpDiscoverySpi disco = new TcpDiscoverySpi();
+
+        disco.setIpFinder(IP_FINDER);
+
+        cfg.setDiscoverySpi(disco);
+
+        cfg.setDaemon(daemon);
+
+        cfg.setClientMode(client);
+
+        return cfg;
+    }
+
+    /**
+     * @param name Cache name.
+     * @return Cache configuration.
+     * @throws Exception In case of error.
+     */
+    private CacheConfiguration cacheConfiguration(@Nullable String name) throws Exception {
+        CacheConfiguration cfg = defaultCacheConfiguration();
+
+        cfg.setName(name);
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        startGridsMultiThreaded(GRID_CNT);
+
+        Class.forName("org.apache.ignite.IgniteJdbcDriver");
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        stopAllGrids();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testDefaults() throws Exception {
+        String url = CFG_URL_PREFIX + CFG_URL;
+
+        try (Connection conn = DriverManager.getConnection(url)) {
+            assertNotNull(conn);
+        }
+
+        try (Connection conn = DriverManager.getConnection(url + '/')) {
+            assertNotNull(conn);
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testNodeId() throws Exception {
+        String url = CFG_URL_PREFIX + "nodeId=" + grid(0).localNode().id() + '@' + CFG_URL;
+
+        try (Connection conn = DriverManager.getConnection(url)) {
+            assertNotNull(conn);
+        }
+
+        url = CFG_URL_PREFIX + "cache=" + CUSTOM_CACHE_NAME + ":nodeId=" + grid(0).localNode().id() + '@' + CFG_URL;
+
+        try (Connection conn = DriverManager.getConnection(url)) {
+            assertNotNull(conn);
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testWrongNodeId() throws Exception {
+        UUID wrongId = UUID.randomUUID();
+
+        final String url = CFG_URL_PREFIX + "nodeId=" + wrongId + '@' + CFG_URL;
+
+        GridTestUtils.assertThrows(
+                log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        try (Connection conn = DriverManager.getConnection(url)) {
+                            return conn;
+                        }
+                    }
+                },
+                SQLException.class,
+                "Failed to establish connection with node (is it a server node?): " + wrongId
+        );
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testClientNodeId() throws Exception {
+        client = true;
+
+        IgniteEx client = (IgniteEx)startGrid();
+
+        UUID clientId = client.localNode().id();
+
+        final String url = CFG_URL_PREFIX + "nodeId=" + clientId + '@' + CFG_URL;
+
+        GridTestUtils.assertThrows(
+                log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        try (Connection conn = DriverManager.getConnection(url)) {
+                            return conn;
+                        }
+                    }
+                },
+                SQLException.class,
+                "Failed to establish connection with node (is it a server node?): " + clientId
+        );
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testDaemonNodeId() throws Exception {
+        daemon = true;
+
+        IgniteEx daemon = startGrid(GRID_CNT);
+
+        UUID daemonId = daemon.localNode().id();
+
+        final String url = CFG_URL_PREFIX + "nodeId=" + daemonId + '@' + CFG_URL;
+
+        GridTestUtils.assertThrows(
+            log,
+            new Callable<Object>() {
+                @Override public Object call() throws Exception {
+                    try (Connection conn = DriverManager.getConnection(url)) {
+                        return conn;
+                    }
+                }
+            },
+            SQLException.class,
+            "Failed to establish connection with node (is it a server node?): " + daemonId
+        );
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testCustomCache() throws Exception {
+        String url = CFG_URL_PREFIX + "cache=" + CUSTOM_CACHE_NAME + '@' + CFG_URL;
+
+        try (Connection conn = DriverManager.getConnection(url)) {
+            assertNotNull(conn);
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testWrongCache() throws Exception {
+        final String url = CFG_URL_PREFIX + "cache=wrongCacheName@" + CFG_URL;
+
+        GridTestUtils.assertThrows(
+            log,
+            new Callable<Object>() {
+                @Override public Object call() throws Exception {
+                    try (Connection conn = DriverManager.getConnection(url)) {
+                        return conn;
+                    }
+                }
+            },
+            SQLException.class,
+            "Client is invalid. Probably cache name is wrong."
+        );
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testClose() throws Exception {
+        String url = CFG_URL_PREFIX + CFG_URL;
+
+        try(final Connection conn = DriverManager.getConnection(url)) {
+            assertNotNull(conn);
+            assertFalse(conn.isClosed());
+
+            conn.close();
+
+            assertTrue(conn.isClosed());
+
+            GridTestUtils.assertThrows(
+                log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        conn.isValid(2);
+
+                        return null;
+                    }
+                },
+                SQLException.class,
+                "Connection is closed."
+            );
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcEmptyCacheSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcEmptyCacheSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcEmptyCacheSelfTest.java
new file mode 100644
index 0000000..adf1368
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcEmptyCacheSelfTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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 org.apache.ignite.configuration.*;
+import org.apache.ignite.spi.discovery.tcp.*;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.*;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.*;
+import org.apache.ignite.testframework.junits.common.*;
+
+import java.sql.*;
+
+import static org.apache.ignite.IgniteJdbcDriver.*;
+import static org.apache.ignite.cache.CacheMode.*;
+import static org.apache.ignite.cache.CacheWriteSynchronizationMode.*;
+
+/**
+ * Tests for empty cache.
+ */
+public class JdbcEmptyCacheSelfTest extends GridCommonAbstractTest {
+    /** IP finder. */
+    private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
+
+    /** Cache name. */
+    private static final String CACHE_NAME = "cache";
+
+    /** JDBC URL. */
+    private static final String BASE_URL =
+        CFG_URL_PREFIX + "cache=" + CACHE_NAME + "@modules/clients/src/test/config/jdbc-config.xml";
+
+    /** Statement. */
+    private Statement stmt;
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String gridName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(gridName);
+
+        CacheConfiguration cache = defaultCacheConfiguration();
+
+        cache.setName(CACHE_NAME);
+        cache.setCacheMode(PARTITIONED);
+        cache.setBackups(1);
+        cache.setWriteSynchronizationMode(FULL_SYNC);
+        cache.setIndexedTypes(
+            Byte.class, Byte.class
+        );
+
+        cfg.setCacheConfiguration(cache);
+
+        TcpDiscoverySpi disco = new TcpDiscoverySpi();
+
+        disco.setIpFinder(IP_FINDER);
+
+        cfg.setDiscoverySpi(disco);
+
+        cfg.setConnectorConfiguration(new ConnectorConfiguration());
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        startGrid();
+
+        Class.forName("org.apache.ignite.IgniteJdbcDriver");
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        stopAllGrids();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        stmt = DriverManager.getConnection(BASE_URL).createStatement();
+
+        assert stmt != null;
+        assert !stmt.isClosed();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        if (stmt != null) {
+            stmt.getConnection().close();
+            stmt.close();
+
+            assert stmt.isClosed();
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testSelectNumber() throws Exception {
+        ResultSet rs = stmt.executeQuery("select 1");
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            assert rs.getInt(1) == 1;
+            assert "1".equals(rs.getString(1));
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testSelectString() throws Exception {
+        ResultSet rs = stmt.executeQuery("select 'str'");
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            assertEquals("str", rs.getString(1));
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcLocalCachesSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcLocalCachesSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcLocalCachesSelfTest.java
new file mode 100644
index 0000000..a8988f9
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcLocalCachesSelfTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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 org.apache.ignite.*;
+import org.apache.ignite.configuration.*;
+import org.apache.ignite.spi.discovery.tcp.*;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.*;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.*;
+import org.apache.ignite.testframework.junits.common.*;
+
+import java.sql.*;
+import java.util.*;
+
+import static org.apache.ignite.IgniteJdbcDriver.*;
+import static org.apache.ignite.cache.CacheMode.*;
+import static org.apache.ignite.cache.CacheWriteSynchronizationMode.*;
+
+/**
+ * Test JDBC with several local caches.
+ */
+public class JdbcLocalCachesSelfTest extends GridCommonAbstractTest {
+    /** IP finder. */
+    private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
+
+    /** Cache name. */
+    private static final String CACHE_NAME = "cache";
+
+    /** JDBC URL. */
+    private static final String BASE_URL =
+        CFG_URL_PREFIX + "cache=" + CACHE_NAME + "@modules/clients/src/test/config/jdbc-config.xml";
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override protected IgniteConfiguration getConfiguration(String gridName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(gridName);
+
+        CacheConfiguration cache = defaultCacheConfiguration();
+
+        cache.setName(CACHE_NAME);
+        cache.setCacheMode(LOCAL);
+        cache.setWriteSynchronizationMode(FULL_SYNC);
+        cache.setIndexedTypes(
+            String.class, Integer.class
+        );
+
+        cfg.setCacheConfiguration(cache);
+
+        TcpDiscoverySpi disco = new TcpDiscoverySpi();
+
+        disco.setIpFinder(IP_FINDER);
+
+        cfg.setDiscoverySpi(disco);
+
+        cfg.setConnectorConfiguration(new ConnectorConfiguration());
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        startGridsMultiThreaded(2);
+
+        IgniteCache<Object, Object> cache1 = grid(0).cache(CACHE_NAME);
+
+        assert cache1 != null;
+
+        cache1.put("key1", 1);
+        cache1.put("key2", 2);
+
+        IgniteCache<Object, Object> cache2 = grid(1).cache(CACHE_NAME);
+
+        assert cache2 != null;
+
+        cache2.put("key1", 3);
+        cache2.put("key2", 4);
+
+        Class.forName("org.apache.ignite.IgniteJdbcDriver");
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        stopAllGrids();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testCache1() throws Exception {
+        Properties cfg = new Properties();
+
+        cfg.setProperty(PROP_NODE_ID, grid(0).localNode().id().toString());
+
+        Connection conn = null;
+
+        try {
+            conn = DriverManager.getConnection(BASE_URL, cfg);
+
+            ResultSet rs = conn.createStatement().executeQuery("select _val from Integer order by _val");
+
+            int cnt = 0;
+
+            while (rs.next())
+                assertEquals(++cnt, rs.getInt(1));
+
+            assertEquals(2, cnt);
+        }
+        finally {
+            if (conn != null)
+                conn.close();
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testCache2() throws Exception {
+        Properties cfg = new Properties();
+
+        cfg.setProperty(PROP_NODE_ID, grid(1).localNode().id().toString());
+
+        Connection conn = null;
+
+        try {
+            conn = DriverManager.getConnection(BASE_URL, cfg);
+
+            ResultSet rs = conn.createStatement().executeQuery("select _val from Integer order by _val");
+
+            int cnt = 0;
+
+            while (rs.next())
+                assertEquals(++cnt + 2, rs.getInt(1));
+
+            assertEquals(2, cnt);
+        }
+        finally {
+            if (conn != null)
+                conn.close();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
new file mode 100644
index 0000000..f601dbc
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
@@ -0,0 +1,334 @@
+/*
+ * 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 org.apache.ignite.*;
+import org.apache.ignite.cache.affinity.*;
+import org.apache.ignite.cache.query.annotations.*;
+import org.apache.ignite.configuration.*;
+import org.apache.ignite.internal.util.typedef.*;
+import org.apache.ignite.spi.discovery.tcp.*;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.*;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.*;
+import org.apache.ignite.testframework.junits.common.*;
+
+import java.io.*;
+import java.sql.*;
+import java.util.*;
+
+import static java.sql.Types.*;
+import static org.apache.ignite.IgniteJdbcDriver.*;
+import static org.apache.ignite.cache.CacheMode.*;
+import static org.apache.ignite.cache.CacheWriteSynchronizationMode.*;
+
+/**
+ * Metadata tests.
+ */
+public class JdbcMetadataSelfTest extends GridCommonAbstractTest {
+    /** IP finder. */
+    private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
+
+    /** JDBC URL. */
+    private static final String BASE_URL = CFG_URL_PREFIX + "modules/clients/src/test/config/jdbc-config.xml";
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String gridName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(gridName);
+
+        CacheConfiguration<?,?> cache = defaultCacheConfiguration();
+
+        cache.setCacheMode(PARTITIONED);
+        cache.setBackups(1);
+        cache.setWriteSynchronizationMode(FULL_SYNC);
+        cache.setIndexedTypes(String.class, Organization.class, AffinityKey.class, Person.class);
+
+        cfg.setCacheConfiguration(cache);
+
+        TcpDiscoverySpi disco = new TcpDiscoverySpi();
+
+        disco.setIpFinder(IP_FINDER);
+
+        cfg.setDiscoverySpi(disco);
+
+        cfg.setConnectorConfiguration(new ConnectorConfiguration());
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        startGridsMultiThreaded(3);
+
+        IgniteCache<String, Organization> orgCache = grid(0).cache(null);
+
+        orgCache.put("o1", new Organization(1, "A"));
+        orgCache.put("o2", new Organization(2, "B"));
+
+        IgniteCache<AffinityKey<String>, Person> personCache = grid(0).cache(null);
+
+        personCache.put(new AffinityKey<>("p1", "o1"), new Person("John White", 25, 1));
+        personCache.put(new AffinityKey<>("p2", "o1"), new Person("Joe Black", 35, 1));
+        personCache.put(new AffinityKey<>("p3", "o2"), new Person("Mike Green", 40, 2));
+
+        Class.forName("org.apache.ignite.IgniteJdbcDriver");
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        stopAllGrids();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testResultSetMetaData() throws Exception {
+        try (Connection conn = DriverManager.getConnection(BASE_URL)) {
+            Statement stmt = conn.createStatement();
+
+            ResultSet rs = stmt.executeQuery(
+                "select p.name, o.id as orgId from Person p, Organization o where p.orgId = o.id");
+
+            assertNotNull(rs);
+
+            ResultSetMetaData meta = rs.getMetaData();
+
+            assertNotNull(meta);
+
+            assertEquals(2, meta.getColumnCount());
+
+            assertTrue("Person".equalsIgnoreCase(meta.getTableName(1)));
+            assertTrue("name".equalsIgnoreCase(meta.getColumnName(1)));
+            assertTrue("name".equalsIgnoreCase(meta.getColumnLabel(1)));
+            assertEquals(VARCHAR, meta.getColumnType(1));
+            assertEquals("VARCHAR", meta.getColumnTypeName(1));
+            assertEquals("java.lang.String", meta.getColumnClassName(1));
+
+            assertTrue("Organization".equalsIgnoreCase(meta.getTableName(2)));
+            assertTrue("orgId".equalsIgnoreCase(meta.getColumnName(2)));
+            assertTrue("orgId".equalsIgnoreCase(meta.getColumnLabel(2)));
+            assertEquals(INTEGER, meta.getColumnType(2));
+            assertEquals("INTEGER", meta.getColumnTypeName(2));
+            assertEquals("java.lang.Integer", meta.getColumnClassName(2));
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testGetTables() throws Exception {
+        try (Connection conn = DriverManager.getConnection(BASE_URL)) {
+            DatabaseMetaData meta = conn.getMetaData();
+
+            Collection<String> names = new ArrayList<>(2);
+
+            names.add("PERSON");
+            names.add("ORGANIZATION");
+
+            ResultSet rs = meta.getTables("", "PUBLIC", "%", new String[]{"TABLE"});
+
+            assertNotNull(rs);
+
+            int cnt = 0;
+
+            while (rs.next()) {
+                assertEquals("TABLE", rs.getString("TABLE_TYPE"));
+                assertTrue(names.remove(rs.getString("TABLE_NAME")));
+
+                cnt++;
+            }
+
+            assertTrue(names.isEmpty());
+            assertEquals(2, cnt);
+
+            names.add("PERSON");
+            names.add("ORGANIZATION");
+
+            rs = meta.getTables("", "PUBLIC", "%", null);
+
+            assertNotNull(rs);
+
+            cnt = 0;
+
+            while (rs.next()) {
+                assertEquals("TABLE", rs.getString("TABLE_TYPE"));
+                assertTrue(names.remove(rs.getString("TABLE_NAME")));
+
+                cnt++;
+            }
+
+            assertTrue(names.isEmpty());
+            assertEquals(2, cnt);
+
+            rs = meta.getTables("", "PUBLIC", "", new String[]{"WRONG"});
+
+            assertFalse(rs.next());
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testGetColumns() throws Exception {
+        try (Connection conn = DriverManager.getConnection(BASE_URL)) {
+            DatabaseMetaData meta = conn.getMetaData();
+
+            ResultSet rs = meta.getColumns("", "PUBLIC", "Person", "%");
+
+            assertNotNull(rs);
+
+            Collection<String> names = new ArrayList<>(2);
+
+            names.add("NAME");
+            names.add("AGE");
+            names.add("ORGID");
+            names.add("_KEY");
+            names.add("_VAL");
+
+            int cnt = 0;
+
+            while (rs.next()) {
+                String name = rs.getString("COLUMN_NAME");
+
+                assertTrue(names.remove(name));
+
+                if ("NAME".equals(name)) {
+                    assertEquals(VARCHAR, rs.getInt("DATA_TYPE"));
+                    assertEquals("VARCHAR", rs.getString("TYPE_NAME"));
+                    assertEquals(1, rs.getInt("NULLABLE"));
+                } else if ("AGE".equals(name) || "ORGID".equals(name)) {
+                    assertEquals(INTEGER, rs.getInt("DATA_TYPE"));
+                    assertEquals("INTEGER", rs.getString("TYPE_NAME"));
+                    assertEquals(0, rs.getInt("NULLABLE"));
+                }
+                if ("_KEY".equals(name)) {
+                    assertEquals(OTHER, rs.getInt("DATA_TYPE"));
+                    assertEquals("OTHER", rs.getString("TYPE_NAME"));
+                    assertEquals(0, rs.getInt("NULLABLE"));
+                }
+                if ("_VAL".equals(name)) {
+                    assertEquals(OTHER, rs.getInt("DATA_TYPE"));
+                    assertEquals("OTHER", rs.getString("TYPE_NAME"));
+                    assertEquals(0, rs.getInt("NULLABLE"));
+                }
+
+                cnt++;
+            }
+
+            assertTrue(names.isEmpty());
+            assertEquals(5, cnt);
+
+            rs = meta.getColumns("", "PUBLIC", "Organization", "%");
+
+            assertNotNull(rs);
+
+            names.add("ID");
+            names.add("NAME");
+            names.add("_KEY");
+            names.add("_VAL");
+
+            cnt = 0;
+
+            while (rs.next()) {
+                String name = rs.getString("COLUMN_NAME");
+
+                assertTrue(names.remove(name));
+
+                if ("id".equals(name)) {
+                    assertEquals(INTEGER, rs.getInt("DATA_TYPE"));
+                    assertEquals("INTEGER", rs.getString("TYPE_NAME"));
+                    assertEquals(0, rs.getInt("NULLABLE"));
+                } else if ("name".equals(name)) {
+                    assertEquals(VARCHAR, rs.getInt("DATA_TYPE"));
+                    assertEquals("VARCHAR", rs.getString("TYPE_NAME"));
+                    assertEquals(1, rs.getInt("NULLABLE"));
+                }
+                if ("_KEY".equals(name)) {
+                    assertEquals(VARCHAR, rs.getInt("DATA_TYPE"));
+                    assertEquals("VARCHAR", rs.getString("TYPE_NAME"));
+                    assertEquals(0, rs.getInt("NULLABLE"));
+                }
+                if ("_VAL".equals(name)) {
+                    assertEquals(OTHER, rs.getInt("DATA_TYPE"));
+                    assertEquals("OTHER", rs.getString("TYPE_NAME"));
+                    assertEquals(0, rs.getInt("NULLABLE"));
+                }
+
+                cnt++;
+            }
+
+            assertTrue(names.isEmpty());
+            assertEquals(4, cnt);
+        }
+    }
+
+    /**
+     * Person.
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    private static class Person implements Serializable {
+        /** Name. */
+        @QuerySqlField(index = false)
+        private final String name;
+
+        /** Age. */
+        @QuerySqlField
+        private final int age;
+
+        /** Organization ID. */
+        @QuerySqlField
+        private final int orgId;
+
+        /**
+         * @param name Name.
+         * @param age Age.
+         * @param orgId Organization ID.
+         */
+        private Person(String name, int age, int orgId) {
+            assert !F.isEmpty(name);
+            assert age > 0;
+            assert orgId > 0;
+
+            this.name = name;
+            this.age = age;
+            this.orgId = orgId;
+        }
+    }
+
+    /**
+     * Organization.
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    private static class Organization implements Serializable {
+        /** ID. */
+        @QuerySqlField
+        private final int id;
+
+        /** Name. */
+        @QuerySqlField(index = false)
+        private final String name;
+
+        /**
+         * @param id ID.
+         * @param name Name.
+         */
+        private Organization(int id, String name) {
+            this.id = id;
+            this.name = name;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/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
new file mode 100644
index 0000000..ea586b2
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcPreparedStatementSelfTest.java
@@ -0,0 +1,730 @@
+/*
+ * 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 org.apache.ignite.*;
+import org.apache.ignite.cache.query.annotations.*;
+import org.apache.ignite.configuration.*;
+import org.apache.ignite.spi.discovery.tcp.*;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.*;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.*;
+import org.apache.ignite.testframework.junits.common.*;
+
+import java.io.*;
+import java.math.*;
+import java.net.*;
+import java.sql.*;
+import java.util.Date;
+
+import static java.sql.Types.*;
+import static org.apache.ignite.IgniteJdbcDriver.*;
+import static org.apache.ignite.cache.CacheMode.*;
+import static org.apache.ignite.cache.CacheWriteSynchronizationMode.*;
+
+/**
+ * Prepared statement test.
+ */
+public class JdbcPreparedStatementSelfTest extends GridCommonAbstractTest {
+    /** IP finder. */
+    private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
+
+    /** JDBC URL. */
+    private static final String BASE_URL = CFG_URL_PREFIX + "modules/clients/src/test/config/jdbc-config.xml";
+
+    /** Connection. */
+    private Connection conn;
+
+    /** Statement. */
+    private PreparedStatement stmt;
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String gridName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(gridName);
+
+        CacheConfiguration<?,?> cache = defaultCacheConfiguration();
+
+        cache.setCacheMode(PARTITIONED);
+        cache.setBackups(1);
+        cache.setWriteSynchronizationMode(FULL_SYNC);
+        cache.setIndexedTypes(
+            Integer.class, TestObject.class
+        );
+
+        cfg.setCacheConfiguration(cache);
+
+        TcpDiscoverySpi disco = new TcpDiscoverySpi();
+
+        disco.setIpFinder(IP_FINDER);
+
+        cfg.setDiscoverySpi(disco);
+
+        cfg.setConnectorConfiguration(new ConnectorConfiguration());
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        startGridsMultiThreaded(3);
+
+        IgniteCache<Integer, TestObject> cache = grid(0).cache(null);
+
+        assert cache != null;
+
+        TestObject o = new TestObject(1);
+
+        o.boolVal = true;
+        o.byteVal = 1;
+        o.shortVal = 1;
+        o.intVal = 1;
+        o.longVal = 1L;
+        o.floatVal = 1.0f;
+        o.doubleVal = 1.0d;
+        o.bigVal = new BigDecimal(1);
+        o.strVal = "str";
+        o.arrVal = new byte[] {1};
+        o.dateVal = new Date(1);
+        o.timeVal = new Time(1);
+        o.tsVal = new Timestamp(1);
+        o.urlVal = new URL("http://abc.com/");
+
+        cache.put(1, o);
+        cache.put(2, new TestObject(2));
+
+        Class.forName("org.apache.ignite.IgniteJdbcDriver");
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        stopAllGrids();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        conn = DriverManager.getConnection(BASE_URL);
+
+        assert conn != null;
+        assert !conn.isClosed();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        if (stmt != null) {
+            stmt.close();
+
+            assert stmt.isClosed();
+        }
+
+        if (conn != null) {
+            conn.close();
+
+            assert conn.isClosed();
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testBoolean() throws Exception {
+        stmt = conn.prepareStatement("select * from TestObject where boolVal is not distinct from ?");
+
+        stmt.setBoolean(1, true);
+
+        ResultSet rs = stmt.executeQuery();
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 1;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+
+        stmt.setNull(1, BOOLEAN);
+
+        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 testByte() throws Exception {
+        stmt = conn.prepareStatement("select * from TestObject where byteVal is not distinct from ?");
+
+        stmt.setByte(1, (byte)1);
+
+        ResultSet rs = stmt.executeQuery();
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 1;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+
+        stmt.setNull(1, TINYINT);
+
+        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 testShort() throws Exception {
+        stmt = conn.prepareStatement("select * from TestObject where shortVal is not distinct from ?");
+
+        stmt.setShort(1, (short)1);
+
+        ResultSet rs = stmt.executeQuery();
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 1;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+
+        stmt.setNull(1, SMALLINT);
+
+        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 testInteger() throws Exception {
+        stmt = conn.prepareStatement("select * from TestObject where intVal is not distinct from ?");
+
+        stmt.setInt(1, 1);
+
+        ResultSet rs = stmt.executeQuery();
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 1;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+
+        stmt.setNull(1, INTEGER);
+
+        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 testLong() throws Exception {
+        stmt = conn.prepareStatement("select * from TestObject where longVal is not distinct from ?");
+
+        stmt.setLong(1, 1L);
+
+        ResultSet rs = stmt.executeQuery();
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 1;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+
+        stmt.setNull(1, BIGINT);
+
+        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 testFloat() throws Exception {
+        stmt = conn.prepareStatement("select * from TestObject where floatVal is not distinct from ?");
+
+        stmt.setFloat(1, 1.0f);
+
+        ResultSet rs = stmt.executeQuery();
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 1;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+
+        stmt.setNull(1, FLOAT);
+
+        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 testDouble() throws Exception {
+        stmt = conn.prepareStatement("select * from TestObject where doubleVal is not distinct from ?");
+
+        stmt.setDouble(1, 1.0d);
+
+        ResultSet rs = stmt.executeQuery();
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 1;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+
+        stmt.setNull(1, DOUBLE);
+
+        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 testBigDecimal() throws Exception {
+        stmt = conn.prepareStatement("select * from TestObject where bigVal is not distinct from ?");
+
+        stmt.setBigDecimal(1, new BigDecimal(1));
+
+        ResultSet rs = stmt.executeQuery();
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 1;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+
+        stmt.setNull(1, OTHER);
+
+        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 testString() throws Exception {
+        stmt = conn.prepareStatement("select * from TestObject where strVal is not distinct from ?");
+
+        stmt.setString(1, "str");
+
+        ResultSet rs = stmt.executeQuery();
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 1;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+
+        stmt.setNull(1, VARCHAR);
+
+        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 testArray() throws Exception {
+        stmt = conn.prepareStatement("select * from TestObject where arrVal is not distinct from ?");
+
+        stmt.setBytes(1, new byte[] {1});
+
+        ResultSet rs = stmt.executeQuery();
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 1;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+
+        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 ?");
+
+        stmt.setObject(1, new Date(1));
+
+        ResultSet rs = stmt.executeQuery();
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 1;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+
+        stmt.setNull(1, DATE);
+
+        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 testTime() throws Exception {
+        stmt = conn.prepareStatement("select * from TestObject where timeVal is not distinct from ?");
+
+        stmt.setTime(1, new Time(1));
+
+        ResultSet rs = stmt.executeQuery();
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 1;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+
+        stmt.setNull(1, TIME);
+
+        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 testTimestamp() throws Exception {
+        stmt = conn.prepareStatement("select * from TestObject where tsVal is not distinct from ?");
+
+        stmt.setTimestamp(1, new Timestamp(1));
+
+        ResultSet rs = stmt.executeQuery();
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 1;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+
+        stmt.setNull(1, TIMESTAMP);
+
+        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 testUrl() throws Exception {
+        stmt = conn.prepareStatement("select * from TestObject where urlVal is not distinct from ?");
+
+        stmt.setURL(1, new URL("http://abc.com/"));
+
+        ResultSet rs = stmt.executeQuery();
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 1;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+
+        stmt.setNull(1, DATALINK);
+
+        rs = stmt.executeQuery();
+
+        cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0)
+                assert rs.getInt("id") == 2;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * Test object.
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    private static class TestObject implements Serializable {
+        /** */
+        @QuerySqlField(index = false)
+        private final int id;
+
+        /** */
+        @QuerySqlField
+        private Boolean boolVal;
+
+        /** */
+        @QuerySqlField
+        private Byte byteVal;
+
+        /** */
+        @QuerySqlField
+        private Short shortVal;
+
+        /** */
+        @QuerySqlField
+        private Integer intVal;
+
+        /** */
+        @QuerySqlField
+        private Long longVal;
+
+        /** */
+        @QuerySqlField
+        private Float floatVal;
+
+        /** */
+        @QuerySqlField
+        private Double doubleVal;
+
+        /** */
+        @QuerySqlField
+        private BigDecimal bigVal;
+
+        /** */
+        @QuerySqlField
+        private String strVal;
+
+        /** */
+        @QuerySqlField
+        private byte[] arrVal;
+
+        /** */
+        @QuerySqlField
+        private Date dateVal;
+
+        /** */
+        @QuerySqlField
+        private Time timeVal;
+
+        /** */
+        @QuerySqlField
+        private Timestamp tsVal;
+
+        /** */
+        @QuerySqlField
+        private URL urlVal;
+
+        /**
+         * @param id ID.
+         */
+        private TestObject(int id) {
+            this.id = id;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcResultSetSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcResultSetSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcResultSetSelfTest.java
new file mode 100644
index 0000000..3607f53
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcResultSetSelfTest.java
@@ -0,0 +1,751 @@
+/*
+ * 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.Serializable;
+import java.math.BigDecimal;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.sql.Date;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.query.annotations.QuerySqlField;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.ConnectorConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+
+import static org.apache.ignite.IgniteJdbcDriver.CFG_URL_PREFIX;
+import static org.apache.ignite.cache.CacheMode.PARTITIONED;
+import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC;
+
+/**
+ * Result set test.
+ */
+@SuppressWarnings("FloatingPointEquality")
+public class JdbcResultSetSelfTest extends GridCommonAbstractTest {
+    /** IP finder. */
+    private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
+
+    /** JDBC URL. */
+    private static final String BASE_URL = CFG_URL_PREFIX + "modules/clients/src/test/config/jdbc-config.xml";
+
+    /** SQL query. */
+    private static final String SQL =
+        "select id, boolVal, byteVal, shortVal, intVal, longVal, floatVal, " +
+            "doubleVal, bigVal, strVal, arrVal, dateVal, timeVal, tsVal, urlVal, f1, f2, f3, _val " +
+            "from TestObject where id = 1";
+
+    /** Statement. */
+    private Statement stmt;
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String gridName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(gridName);
+
+        CacheConfiguration<?,?> cache = defaultCacheConfiguration();
+
+        cache.setCacheMode(PARTITIONED);
+        cache.setBackups(1);
+        cache.setWriteSynchronizationMode(FULL_SYNC);
+        cache.setIndexedTypes(
+            Integer.class, TestObject.class
+        );
+
+        cfg.setCacheConfiguration(cache);
+
+        TcpDiscoverySpi disco = new TcpDiscoverySpi();
+
+        disco.setIpFinder(IP_FINDER);
+
+        cfg.setDiscoverySpi(disco);
+
+        cfg.setConnectorConfiguration(new ConnectorConfiguration());
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        startGridsMultiThreaded(3);
+
+        IgniteCache<Integer, TestObject> cache = grid(0).cache(null);
+
+        assert cache != null;
+
+        TestObject o = createObjectWithData(1);
+
+        cache.put(1, o);
+        cache.put(2, new TestObject(2));
+        cache.put(3, new TestObject(3));
+
+        Class.forName("org.apache.ignite.IgniteJdbcDriver");
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        stopAllGrids();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        stmt = DriverManager.getConnection(BASE_URL).createStatement();
+
+        assert stmt != null;
+        assert !stmt.isClosed();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        if (stmt != null) {
+            stmt.getConnection().close();
+            stmt.close();
+
+            assert stmt.isClosed();
+        }
+    }
+
+    /**
+     * @param id ID.
+     * @return Object.
+     * @throws MalformedURLException If URL in incorrect.
+     */
+    @SuppressWarnings("deprecation")
+    private TestObject createObjectWithData(int id) throws MalformedURLException {
+        TestObject o = new TestObject(id);
+
+        o.boolVal = true;
+        o.byteVal = 1;
+        o.shortVal = 1;
+        o.intVal = 1;
+        o.longVal = 1L;
+        o.floatVal = 1.0f;
+        o.doubleVal = 1.0d;
+        o.bigVal = new BigDecimal(1);
+        o.strVal = "str";
+        o.arrVal = new byte[] {1};
+        o.dateVal = new Date(1, 1, 1);
+        o.timeVal = new Time(1, 1, 1);
+        o.tsVal = new Timestamp(1);
+        o.urlVal = new URL("http://abc.com/");
+
+        return o;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testBoolean() throws Exception {
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0) {
+                assert rs.getBoolean("boolVal");
+                assert rs.getBoolean(2);
+            }
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testByte() throws Exception {
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0) {
+                assert rs.getByte("byteVal") == 1;
+                assert rs.getByte(3) == 1;
+            }
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testShort() throws Exception {
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0) {
+                assert rs.getShort("shortVal") == 1;
+                assert rs.getShort(4) == 1;
+            }
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testInteger() throws Exception {
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0) {
+                assert rs.getInt("intVal") == 1;
+                assert rs.getInt(5) == 1;
+            }
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testLong() throws Exception {
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0) {
+                assert rs.getLong("longVal") == 1;
+                assert rs.getLong(6) == 1;
+            }
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testFloat() throws Exception {
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0) {
+                assert rs.getFloat("floatVal") == 1.0;
+                assert rs.getFloat(7) == 1.0;
+            }
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testDouble() throws Exception {
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0) {
+                assert rs.getDouble("doubleVal") == 1.0;
+                assert rs.getDouble(8) == 1.0;
+            }
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testBigDecimal() throws Exception {
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0) {
+                assert rs.getBigDecimal("bigVal").intValue() == 1;
+                assert rs.getBigDecimal(9).intValue() == 1;
+            }
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testString() throws Exception {
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0) {
+                assert "str".equals(rs.getString("strVal"));
+                assert "str".equals(rs.getString(10));
+            }
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testArray() throws Exception {
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0) {
+                assert Arrays.equals(rs.getBytes("arrVal"), new byte[] {1});
+                assert Arrays.equals(rs.getBytes(11), new byte[] {1});
+            }
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @SuppressWarnings("deprecation")
+    public void testDate() throws Exception {
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0) {
+                assert rs.getDate("dateVal").equals(new Date(1, 1, 1));
+                assert rs.getDate(12).equals(new Date(1, 1, 1));
+            }
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @SuppressWarnings("deprecation")
+    public void testTime() throws Exception {
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0) {
+                assert rs.getTime("timeVal").equals(new Time(1, 1, 1));
+                assert rs.getTime(13).equals(new Time(1, 1, 1));
+            }
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testTimestamp() throws Exception {
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0) {
+                assert rs.getTimestamp("tsVal").getTime() == 1;
+                assert rs.getTimestamp(14).getTime() == 1;
+            }
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testUrl() throws Exception {
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            if (cnt == 0) {
+                assert "http://abc.com/".equals(rs.getURL("urlVal").toString());
+                assert "http://abc.com/".equals(rs.getURL(15).toString());
+            }
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testObject() throws Exception {
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        TestObjectField f1 = new TestObjectField(100, "AAAA");
+        TestObjectField f2 = new TestObjectField(500, "BBBB");
+
+        TestObject o = createObjectWithData(1);
+
+        assertTrue(rs.next());
+
+        assertEquals(f1.toString(), rs.getObject("f1"));
+        assertEquals(f1.toString(), rs.getObject(16));
+
+        assertEquals(f2.toString(), rs.getObject("f2"));
+        assertEquals(f2.toString(), rs.getObject(17));
+
+        assertNull(rs.getObject("f3"));
+        assertTrue(rs.wasNull());
+        assertNull(rs.getObject(18));
+        assertTrue(rs.wasNull());
+
+        assertEquals(o.toString(), rs.getObject("_val"));
+        assertEquals(o.toString(), rs.getObject(19));
+
+        assertFalse(rs.next());
+    }
+
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testNavigation() throws Exception {
+        ResultSet rs = stmt.executeQuery("select * from TestObject where id > 0");
+
+        assertTrue(rs.isBeforeFirst());
+        assertFalse(rs.isAfterLast());
+        assertFalse(rs.isFirst());
+        assertFalse(rs.isLast());
+        assertEquals(0, rs.getRow());
+
+        assertTrue(rs.next());
+
+        assertFalse(rs.isBeforeFirst());
+        assertFalse(rs.isAfterLast());
+        assertTrue(rs.isFirst());
+        assertFalse(rs.isLast());
+        assertEquals(1, rs.getRow());
+
+        assertTrue(rs.next());
+
+        assertFalse(rs.isBeforeFirst());
+        assertFalse(rs.isAfterLast());
+        assertFalse(rs.isFirst());
+        assertFalse(rs.isLast());
+        assertEquals(2, rs.getRow());
+
+        assertTrue(rs.next());
+
+        assertFalse(rs.isBeforeFirst());
+        assertFalse(rs.isAfterLast());
+        assertFalse(rs.isFirst());
+        assertTrue(rs.isLast());
+        assertEquals(3, rs.getRow());
+
+        assertFalse(rs.next());
+
+        assertFalse(rs.isBeforeFirst());
+        assertTrue(rs.isAfterLast());
+        assertFalse(rs.isFirst());
+        assertFalse(rs.isLast());
+        assertEquals(0, rs.getRow());
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testFetchSize() throws Exception {
+        stmt.setFetchSize(1);
+
+        ResultSet rs = stmt.executeQuery("select * from TestObject where id > 0");
+
+        assertTrue(rs.next());
+        assertTrue(rs.next());
+        assertTrue(rs.next());
+
+        stmt.setFetchSize(0);
+    }
+
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testFindColumn() throws Exception {
+        final ResultSet rs = stmt.executeQuery(SQL);
+
+        assertNotNull(rs);
+        assertTrue(rs.next());
+
+        assert rs.findColumn("id") == 1;
+
+        GridTestUtils.assertThrows(
+            log,
+            new Callable<Object>() {
+                @Override public Object call() throws Exception {
+                    rs.findColumn("wrong");
+
+                    return null;
+                }
+            },
+            SQLException.class,
+            "Column not found: wrong"
+        );
+    }
+
+    /**
+     * Test object.
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    private static class TestObject implements Serializable {
+        /** */
+        @QuerySqlField
+        private final int id;
+
+        /** */
+        @QuerySqlField(index = false)
+        private Boolean boolVal;
+
+        /** */
+        @QuerySqlField(index = false)
+        private Byte byteVal;
+
+        /** */
+        @QuerySqlField(index = false)
+        private Short shortVal;
+
+        /** */
+        @QuerySqlField(index = false)
+        private Integer intVal;
+
+        /** */
+        @QuerySqlField(index = false)
+        private Long longVal;
+
+        /** */
+        @QuerySqlField(index = false)
+        private Float floatVal;
+
+        /** */
+        @QuerySqlField(index = false)
+        private Double doubleVal;
+
+        /** */
+        @QuerySqlField(index = false)
+        private BigDecimal bigVal;
+
+        /** */
+        @QuerySqlField(index = false)
+        private String strVal;
+
+        /** */
+        @QuerySqlField(index = false)
+        private byte[] arrVal;
+
+        /** */
+        @QuerySqlField(index = false)
+        private Date dateVal;
+
+        /** */
+        @QuerySqlField(index = false)
+        private Time timeVal;
+
+        /** */
+        @QuerySqlField(index = false)
+        private Timestamp tsVal;
+
+        /** */
+        @QuerySqlField(index = false)
+        private URL urlVal;
+
+        /** */
+        @QuerySqlField(index = false)
+        private TestObjectField f1 = new TestObjectField(100, "AAAA");
+
+        /** */
+        @QuerySqlField(index = false)
+        private TestObjectField f2 = new TestObjectField(500, "BBBB");
+
+        /** */
+        @QuerySqlField(index = false)
+        private TestObjectField f3;
+
+        /**
+         * @param id ID.
+         */
+        private TestObject(int id) {
+            this.id = id;
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(TestObject.class, this);
+        }
+
+        /** {@inheritDoc} */
+        @SuppressWarnings({"BigDecimalEquals", "EqualsHashCodeCalledOnUrl", "RedundantIfStatement"})
+        @Override public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            TestObject that = (TestObject)o;
+
+            if (id != that.id) return false;
+            if (!Arrays.equals(arrVal, that.arrVal)) return false;
+            if (bigVal != null ? !bigVal.equals(that.bigVal) : that.bigVal != null) return false;
+            if (boolVal != null ? !boolVal.equals(that.boolVal) : that.boolVal != null) return false;
+            if (byteVal != null ? !byteVal.equals(that.byteVal) : that.byteVal != null) return false;
+            if (dateVal != null ? !dateVal.equals(that.dateVal) : that.dateVal != null) return false;
+            if (doubleVal != null ? !doubleVal.equals(that.doubleVal) : that.doubleVal != null) return false;
+            if (f1 != null ? !f1.equals(that.f1) : that.f1 != null) return false;
+            if (f2 != null ? !f2.equals(that.f2) : that.f2 != null) return false;
+            if (f3 != null ? !f3.equals(that.f3) : that.f3 != null) return false;
+            if (floatVal != null ? !floatVal.equals(that.floatVal) : that.floatVal != null) return false;
+            if (intVal != null ? !intVal.equals(that.intVal) : that.intVal != null) return false;
+            if (longVal != null ? !longVal.equals(that.longVal) : that.longVal != null) return false;
+            if (shortVal != null ? !shortVal.equals(that.shortVal) : that.shortVal != null) return false;
+            if (strVal != null ? !strVal.equals(that.strVal) : that.strVal != null) return false;
+            if (timeVal != null ? !timeVal.equals(that.timeVal) : that.timeVal != null) return false;
+            if (tsVal != null ? !tsVal.equals(that.tsVal) : that.tsVal != null) return false;
+            if (urlVal != null ? !urlVal.equals(that.urlVal) : that.urlVal != null) return false;
+
+            return true;
+        }
+
+        /** {@inheritDoc} */
+        @SuppressWarnings("EqualsHashCodeCalledOnUrl")
+        @Override public int hashCode() {
+            int res = id;
+
+            res = 31 * res + (boolVal != null ? boolVal.hashCode() : 0);
+            res = 31 * res + (byteVal != null ? byteVal.hashCode() : 0);
+            res = 31 * res + (shortVal != null ? shortVal.hashCode() : 0);
+            res = 31 * res + (intVal != null ? intVal.hashCode() : 0);
+            res = 31 * res + (longVal != null ? longVal.hashCode() : 0);
+            res = 31 * res + (floatVal != null ? floatVal.hashCode() : 0);
+            res = 31 * res + (doubleVal != null ? doubleVal.hashCode() : 0);
+            res = 31 * res + (bigVal != null ? bigVal.hashCode() : 0);
+            res = 31 * res + (strVal != null ? strVal.hashCode() : 0);
+            res = 31 * res + (arrVal != null ? Arrays.hashCode(arrVal) : 0);
+            res = 31 * res + (dateVal != null ? dateVal.hashCode() : 0);
+            res = 31 * res + (timeVal != null ? timeVal.hashCode() : 0);
+            res = 31 * res + (tsVal != null ? tsVal.hashCode() : 0);
+            res = 31 * res + (urlVal != null ? urlVal.hashCode() : 0);
+            res = 31 * res + (f1 != null ? f1.hashCode() : 0);
+            res = 31 * res + (f2 != null ? f2.hashCode() : 0);
+            res = 31 * res + (f3 != null ? f3.hashCode() : 0);
+
+            return res;
+        }
+    }
+
+    /**
+     * Test object field.
+     */
+    @SuppressWarnings("PackageVisibleField")
+    private static class TestObjectField implements Serializable {
+        /** */
+        final int a;
+
+        /** */
+        final String b;
+
+        /**
+         * @param a A.
+         * @param b B.
+         */
+        private TestObjectField(int a, String b) {
+            this.a = a;
+            this.b = b;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            TestObjectField that = (TestObjectField)o;
+
+            return a == that.a && !(b != null ? !b.equals(that.b) : that.b != null);
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            int res = a;
+
+            res = 31 * res + (b != null ? b.hashCode() : 0);
+
+            return res;
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(TestObjectField.class, this);
+        }
+    }
+}


[3/4] ignite git commit: ignite-1250 JDBC driver: migration to embedded Ignite client node

Posted by vk...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcStatementSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcStatementSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcStatementSelfTest.java
new file mode 100644
index 0000000..7898bc8
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcStatementSelfTest.java
@@ -0,0 +1,292 @@
+/*
+ * 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.Serializable;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.query.annotations.QuerySqlField;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.ConnectorConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+
+import static org.apache.ignite.IgniteJdbcDriver.CFG_URL_PREFIX;
+import static org.apache.ignite.cache.CacheMode.PARTITIONED;
+import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC;
+
+/**
+ * Statement test.
+ */
+public class JdbcStatementSelfTest extends GridCommonAbstractTest {
+    /** IP finder. */
+    private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
+
+    /** JDBC URL. */
+    private static final String BASE_URL = CFG_URL_PREFIX + "modules/clients/src/test/config/jdbc-config.xml";
+
+    /** SQL query. */
+    private static final String SQL = "select * from Person where age > 30";
+
+    /** Connection. */
+    private Connection conn;
+
+    /** Statement. */
+    private Statement stmt;
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String gridName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(gridName);
+
+        CacheConfiguration<?,?> cache = defaultCacheConfiguration();
+
+        cache.setCacheMode(PARTITIONED);
+        cache.setBackups(1);
+        cache.setWriteSynchronizationMode(FULL_SYNC);
+        cache.setIndexedTypes(
+            String.class, Person.class
+        );
+
+        cfg.setCacheConfiguration(cache);
+
+        TcpDiscoverySpi disco = new TcpDiscoverySpi();
+
+        disco.setIpFinder(IP_FINDER);
+
+        cfg.setDiscoverySpi(disco);
+
+        cfg.setConnectorConfiguration(new ConnectorConfiguration());
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        startGridsMultiThreaded(3);
+
+        IgniteCache<String, Person> cache = grid(0).cache(null);
+
+        assert cache != null;
+
+        cache.put("p1", new Person(1, "John", "White", 25));
+        cache.put("p2", new Person(2, "Joe", "Black", 35));
+        cache.put("p3", new Person(3, "Mike", "Green", 40));
+
+        Class.forName("org.apache.ignite.IgniteJdbcDriver");
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        stopAllGrids();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        conn = DriverManager.getConnection(BASE_URL);
+        stmt = conn.createStatement();
+
+        assertNotNull(stmt);
+        assertFalse(stmt.isClosed());
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        if (stmt != null && !stmt.isClosed())
+            stmt.close();
+
+        conn.close();
+
+        assertTrue(stmt.isClosed());
+        assertTrue(conn.isClosed());
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testExecuteQuery() throws Exception {
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        assert rs != null;
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            int id = rs.getInt("id");
+
+            if (id == 2) {
+                assert "Joe".equals(rs.getString("firstName"));
+                assert "Black".equals(rs.getString("lastName"));
+                assert rs.getInt("age") == 35;
+            }
+            else if (id == 3) {
+                assert "Mike".equals(rs.getString("firstName"));
+                assert "Green".equals(rs.getString("lastName"));
+                assert rs.getInt("age") == 40;
+            }
+            else
+                assert false : "Wrong ID: " + id;
+
+            cnt++;
+        }
+
+        assert cnt == 2;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testExecute() throws Exception {
+        assert stmt.execute(SQL);
+
+        ResultSet rs = stmt.getResultSet();
+
+        assert rs != null;
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            int id = rs.getInt("id");
+
+            if (id == 2) {
+                assert "Joe".equals(rs.getString("firstName"));
+                assert "Black".equals(rs.getString("lastName"));
+                assert rs.getInt("age") == 35;
+            }
+            else if (id == 3) {
+                assert "Mike".equals(rs.getString("firstName"));
+                assert "Green".equals(rs.getString("lastName"));
+                assert rs.getInt("age") == 40;
+            }
+            else
+                assert false : "Wrong ID: " + id;
+
+            cnt++;
+        }
+
+        assert cnt == 2;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testMaxRows() throws Exception {
+        stmt.setMaxRows(1);
+
+        ResultSet rs = stmt.executeQuery(SQL);
+
+        assert rs != null;
+
+        int cnt = 0;
+
+        while (rs.next()) {
+            int id = rs.getInt("id");
+
+            if (id == 2) {
+                assert "Joe".equals(rs.getString("firstName"));
+                assert "Black".equals(rs.getString("lastName"));
+                assert rs.getInt("age") == 35;
+            }
+            else if (id == 3) {
+                assert "Mike".equals(rs.getString("firstName"));
+                assert "Green".equals(rs.getString("lastName"));
+                assert rs.getInt("age") == 40;
+            }
+            else
+                assert false : "Wrong ID: " + id;
+
+            cnt++;
+        }
+
+        assert cnt == 1;
+
+        stmt.setMaxRows(0);
+
+        rs = stmt.executeQuery(SQL);
+
+        assert rs != null;
+
+        cnt = 0;
+
+        while (rs.next()) {
+            int id = rs.getInt("id");
+
+            if (id == 2) {
+                assert "Joe".equals(rs.getString("firstName"));
+                assert "Black".equals(rs.getString("lastName"));
+                assert rs.getInt("age") == 35;
+            }
+            else if (id == 3) {
+                assert "Mike".equals(rs.getString("firstName"));
+                assert "Green".equals(rs.getString("lastName"));
+                assert rs.getInt("age") == 40;
+            }
+            else
+                assert false : "Wrong ID: " + id;
+
+            cnt++;
+        }
+
+        assert cnt == 2;
+    }
+
+    /**
+     * Person.
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    private static class Person implements Serializable {
+        /** ID. */
+        @QuerySqlField
+        private final int id;
+
+        /** First name. */
+        @QuerySqlField(index = false)
+        private final String firstName;
+
+        /** Last name. */
+        @QuerySqlField(index = false)
+        private final String lastName;
+
+        /** Age. */
+        @QuerySqlField
+        private final int age;
+
+        /**
+         * @param id ID.
+         * @param firstName First name.
+         * @param lastName Last name.
+         * @param age Age.
+         */
+        private Person(int id, String firstName, String lastName, int age) {
+            assert !F.isEmpty(firstName);
+            assert !F.isEmpty(lastName);
+            assert age > 0;
+
+            this.id = id;
+            this.firstName = firstName;
+            this.lastName = lastName;
+            this.age = age;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/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 b0c0c58..bac2f60 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
@@ -38,6 +38,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
         suite.addTest(new TestSuite(JdbcConnectionSelfTest.class));
         suite.addTest(new TestSuite(JdbcStatementSelfTest.class));
         suite.addTest(new TestSuite(JdbcPreparedStatementSelfTest.class));
@@ -47,6 +48,16 @@ public class IgniteJdbcDriverTestSuite extends TestSuite {
         suite.addTest(new TestSuite(JdbcEmptyCacheSelfTest.class));
         suite.addTest(new TestSuite(JdbcLocalCachesSelfTest.class));
 
+        // Ignite client node based driver tests
+        suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcConnectionSelfTest.class));
+        suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcStatementSelfTest.class));
+        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.JdbcMetadataSelfTest.class));
+        suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcEmptyCacheSelfTest.class));
+        suite.addTest(new TestSuite(org.apache.ignite.internal.jdbc2.JdbcLocalCachesSelfTest.class));
+
         return suite;
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/core/src/main/java/org/apache/ignite/IgniteJdbcDriver.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteJdbcDriver.java b/modules/core/src/main/java/org/apache/ignite/IgniteJdbcDriver.java
index 6ba362e..7f8b523 100644
--- a/modules/core/src/main/java/org/apache/ignite/IgniteJdbcDriver.java
+++ b/modules/core/src/main/java/org/apache/ignite/IgniteJdbcDriver.java
@@ -17,17 +17,19 @@
 
 package org.apache.ignite;
 
-
 import java.sql.Connection;
 import java.sql.Driver;
 import java.sql.DriverManager;
 import java.sql.DriverPropertyInfo;
 import java.sql.SQLException;
 import java.sql.SQLFeatureNotSupportedException;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Properties;
 import java.util.logging.Logger;
 import org.apache.ignite.cache.affinity.AffinityKey;
 import org.apache.ignite.internal.jdbc.JdbcConnection;
+import org.apache.ignite.logger.java.JavaLogger;
 
 /**
  * JDBC driver implementation for In-Memory Data Grid.
@@ -66,8 +68,47 @@ import org.apache.ignite.internal.jdbc.JdbcConnection;
  * {@code IGNITE_HOME/libs} folder. So if you are using JDBC driver in any external tool,
  * you have to add main Ignite JAR will all dependencies to its classpath.
  * <h1 class="header">Configuration</h1>
- * Internally JDBC driver <b>is based on Ignite Java client</b>. Therefore, all client
- * configuration properties can be applied to JDBC connection.
+ *
+ * JDBC driver can return two different types of connection: Ignite Java client based connection and
+ * Ignite client node based connection. Java client best connection is deprecated and left only for
+ * compatibility with previous version, so you should always use Ignite client node based mode.
+ * It is also preferable because it has much better performance.
+ *
+ * The type of returned connection depends on provided JDBC connection URL.
+ *
+ * <h2 class="header">Configuration of Ignite client node based connection</h2>
+ *
+ * JDBC connection URL has the following pattern: {@code jdbc:ignite:cfg://[<params>@]<config_url>}.<br>
+ *
+ * {@code <config_url>} represents any valid URL which points to Ignite configuration file. It is required.<br>
+ *
+ * {@code <params>} are optional and have the following format: {@code param1=value1:param2=value2:...:paramN=valueN}.<br>
+ *
+ * The following parameters are supported:
+ * <ul>
+ *     <li>{@code cache} - cache name. If it is not defined than default cache will be used.</li>
+ *     <li>
+ *         {@code nodeId} - ID of node where query will be executed.
+ *         It can be useful for querying through local caches.
+ *         If node with provided ID doesn't exist, exception is thrown.
+ *     </li>
+ *     <li>
+ *         {@code local} - query will be executed only on local node. Use this parameter with {@code nodeId} parameter.
+ *         Default value is {@code false}.
+ *     </li>
+ *     <li>
+ *          {@code collocated} - flag that used for optimization purposes. Whenever Ignite executes
+ *          a distributed query, it sends sub-queries to individual cluster members.
+ *          If you know in advance that the elements of your query selection are collocated
+ *          together on the same node, usually based on some <b>affinity-key</b>, Ignite
+ *          can make significant performance and network optimizations.
+ *          Default value is {@code false}.
+ *     </li>
+ * </ul>
+ *
+ * <h2 class="header">Configuration of Ignite Java client based connection</h2>
+ *
+ * All Ignite Java client configuration properties can be applied to JDBC connection of this type.
  * <p>
  * JDBC connection URL has the following pattern:
  * {@code jdbc:ignite://<hostname>:<port>/<cache_name>?nodeId=<UUID>}<br>
@@ -197,10 +238,10 @@ import org.apache.ignite.internal.jdbc.JdbcConnection;
  * <h1 class="header">Example</h1>
  * <pre name="code" class="java">
  * // Register JDBC driver.
- * Class.forName("org.apache.ignite.jdbc.IgniteJdbcDriver");
+ * Class.forName("org.apache.ignite.IgniteJdbcDriver");
  *
  * // Open JDBC connection.
- * Connection conn = DriverManager.getConnection("jdbc:ignite://localhost/cache");
+ * Connection conn = DriverManager.getConnection("jdbc:ignite:cfg//cache=persons@file:///etc/configs/ignite-jdbc.xml");
  *
  * // Query persons' names
  * ResultSet rs = conn.createStatement().executeQuery("select name from Person");
@@ -231,6 +272,18 @@ public class IgniteJdbcDriver implements Driver {
     /** Prefix for property names. */
     private static final String PROP_PREFIX = "ignite.jdbc.";
 
+    /** Node ID parameter name. */
+    private static final String PARAM_NODE_ID = "nodeId";
+
+    /** Cache parameter name. */
+    private static final String PARAM_CACHE = "cache";
+
+    /** Local parameter name. */
+    private static final String PARAM_LOCAL = "local";
+
+    /** Collocated parameter name. */
+    private static final String PARAM_COLLOCATED = "collocated";
+
     /** Hostname property name. */
     public static final String PROP_HOST = PROP_PREFIX + "host";
 
@@ -238,14 +291,26 @@ public class IgniteJdbcDriver implements Driver {
     public static final String PROP_PORT = PROP_PREFIX + "port";
 
     /** Cache name property name. */
-    public static final String PROP_CACHE = PROP_PREFIX + "cache";
+    public static final String PROP_CACHE = PROP_PREFIX + PARAM_CACHE;
 
     /** Node ID property name. */
-    public static final String PROP_NODE_ID = PROP_PREFIX + "nodeId";
+    public static final String PROP_NODE_ID = PROP_PREFIX + PARAM_NODE_ID;
+
+    /** Local property name. */
+    public static final String PROP_LOCAL = PROP_PREFIX + PARAM_LOCAL;
+
+    /** Collocated property name. */
+    public static final String PROP_COLLOCATED = PROP_PREFIX + PARAM_COLLOCATED;
+
+    /** Cache name property name. */
+    public static final String PROP_CFG = PROP_PREFIX + "cfg";
 
     /** URL prefix. */
     public static final String URL_PREFIX = "jdbc:ignite://";
 
+    /** Config URL prefix. */
+    public static final String CFG_URL_PREFIX = "jdbc:ignite:cfg://";
+
     /** Default port. */
     public static final int DFLT_PORT = 11211;
 
@@ -255,6 +320,9 @@ public class IgniteJdbcDriver implements Driver {
     /** Minor version. */
     private static final int MINOR_VER = 0;
 
+    /** Logger. */
+    private static final IgniteLogger LOG = new JavaLogger();
+
     /**
      * Register driver.
      */
@@ -272,12 +340,19 @@ public class IgniteJdbcDriver implements Driver {
         if (!parseUrl(url, props))
             throw new SQLException("URL is invalid: " + url);
 
-        return new JdbcConnection(url, props);
+        if (url.startsWith(URL_PREFIX)) {
+            if (props.getProperty(PROP_CFG) != null)
+                LOG.warning(PROP_CFG + " property is not applicable for this URL.");
+
+            return new JdbcConnection(url, props);
+        }
+        else
+            return new org.apache.ignite.internal.jdbc2.JdbcConnection(url, props);
     }
 
     /** {@inheritDoc} */
     @Override public boolean acceptsURL(String url) throws SQLException {
-        return url.startsWith(URL_PREFIX);
+        return url.startsWith(URL_PREFIX) || url.startsWith(CFG_URL_PREFIX);
     }
 
     /** {@inheritDoc} */
@@ -285,49 +360,72 @@ public class IgniteJdbcDriver implements Driver {
         if (!parseUrl(url, info))
             throw new SQLException("URL is invalid: " + url);
 
-        DriverPropertyInfo[] props = new DriverPropertyInfo[20];
-
-        props[0] = new PropertyInfo("Hostname", info.getProperty(PROP_HOST), true);
-        props[1] = new PropertyInfo("Port number", info.getProperty(PROP_PORT), "");
-        props[2] = new PropertyInfo("Cache name", info.getProperty(PROP_CACHE), "");
-        props[3] = new PropertyInfo("Node ID", info.getProperty(PROP_NODE_ID, ""));
-        props[4] = new PropertyInfo("ignite.client.protocol", info.getProperty("ignite.client.protocol", "TCP"),
-            "Communication protocol (TCP or HTTP).");
-        props[5] = new PropertyInfo("ignite.client.connectTimeout", info.getProperty("ignite.client.connectTimeout", "0"),
-            "Socket connection timeout.");
-        props[6] = new PropertyInfo("ignite.client.tcp.noDelay", info.getProperty("ignite.client.tcp.noDelay", "true"),
-            "Flag indicating whether TCP_NODELAY flag should be enabled for outgoing connections.");
-        props[7] = new PropertyInfo("ignite.client.ssl.enabled", info.getProperty("ignite.client.ssl.enabled", "false"),
-            "Flag indicating that SSL is needed for connection.");
-        props[8] = new PropertyInfo("ignite.client.ssl.protocol", info.getProperty("ignite.client.ssl.protocol", "TLS"),
-            "SSL protocol.");
-        props[9] = new PropertyInfo("ignite.client.ssl.key.algorithm", info.getProperty("ignite.client.ssl.key.algorithm",
-            "SunX509"), "Key manager algorithm.");
-        props[10] = new PropertyInfo("ignite.client.ssl.keystore.location",
-            info.getProperty("ignite.client.ssl.keystore.location", ""),
-            "Key store to be used by client to connect with Ignite topology.");
-        props[11] = new PropertyInfo("ignite.client.ssl.keystore.password",
-            info.getProperty("ignite.client.ssl.keystore.password", ""), "Key store password.");
-        props[12] = new PropertyInfo("ignite.client.ssl.keystore.type", info.getProperty("ignite.client.ssl.keystore.type",
-            "jks"), "Key store type.");
-        props[13] = new PropertyInfo("ignite.client.ssl.truststore.location",
-            info.getProperty("ignite.client.ssl.truststore.location", ""),
-            "Trust store to be used by client to connect with Ignite topology.");
-        props[14] = new PropertyInfo("ignite.client.ssl.keystore.password",
-            info.getProperty("ignite.client.ssl.truststore.password", ""), "Trust store password.");
-        props[15] = new PropertyInfo("ignite.client.ssl.truststore.type", info.getProperty("ignite.client.ssl.truststore.type",
-            "jks"), "Trust store type.");
-        props[16] = new PropertyInfo("ignite.client.credentials", info.getProperty("ignite.client.credentials", ""),
-            "Client credentials used in authentication process.");
-        props[17] = new PropertyInfo("ignite.client.cache.top", info.getProperty("ignite.client.cache.top", "false"),
-            "Flag indicating that topology is cached internally. Cache will be refreshed in the background with " +
-                "interval defined by topologyRefreshFrequency property (see below).");
-        props[18] = new PropertyInfo("ignite.client.topology.refresh", info.getProperty("ignite.client.topology.refresh",
-            "2000"), "Topology cache refresh frequency (ms).");
-        props[19] = new PropertyInfo("ignite.client.idleTimeout", info.getProperty("ignite.client.idleTimeout", "30000"),
-            "Maximum amount of time that connection can be idle before it is closed (ms).");
-
-        return props;
+        List<DriverPropertyInfo> props = Arrays.<DriverPropertyInfo>asList(
+            new PropertyInfo("Hostname", info.getProperty(PROP_HOST), ""),
+            new PropertyInfo("Port number", info.getProperty(PROP_PORT), ""),
+            new PropertyInfo("Cache name", info.getProperty(PROP_CACHE), ""),
+            new PropertyInfo("Node ID", info.getProperty(PROP_NODE_ID), ""),
+            new PropertyInfo("Local", info.getProperty(PROP_LOCAL), ""),
+            new PropertyInfo("Collocated", info.getProperty(PROP_COLLOCATED), "")
+        );
+
+        if (info.getProperty(PROP_CFG) != null)
+            props.add(new PropertyInfo("Configuration path", info.getProperty(PROP_CFG), ""));
+        else
+            props.addAll(Arrays.<DriverPropertyInfo>asList(
+                new PropertyInfo("ignite.client.protocol",
+                    info.getProperty("ignite.client.protocol", "TCP"),
+                    "Communication protocol (TCP or HTTP)."),
+                new PropertyInfo("ignite.client.connectTimeout",
+                    info.getProperty("ignite.client.connectTimeout", "0"),
+                    "Socket connection timeout."),
+                new PropertyInfo("ignite.client.tcp.noDelay",
+                    info.getProperty("ignite.client.tcp.noDelay", "true"),
+                    "Flag indicating whether TCP_NODELAY flag should be enabled for outgoing connections."),
+                new PropertyInfo("ignite.client.ssl.enabled",
+                    info.getProperty("ignite.client.ssl.enabled", "false"),
+                    "Flag indicating that SSL is needed for connection."),
+                new PropertyInfo("ignite.client.ssl.protocol",
+                    info.getProperty("ignite.client.ssl.protocol", "TLS"),
+                    "SSL protocol."),
+                new PropertyInfo("ignite.client.ssl.key.algorithm",
+                    info.getProperty("ignite.client.ssl.key.algorithm", "SunX509"),
+                    "Key manager algorithm."),
+                new PropertyInfo("ignite.client.ssl.keystore.location",
+                    info.getProperty("ignite.client.ssl.keystore.location", ""),
+                    "Key store to be used by client to connect with Ignite topology."),
+                new PropertyInfo("ignite.client.ssl.keystore.password",
+                    info.getProperty("ignite.client.ssl.keystore.password", ""),
+                    "Key store password."),
+                new PropertyInfo("ignite.client.ssl.keystore.type",
+                    info.getProperty("ignite.client.ssl.keystore.type", "jks"),
+                    "Key store type."),
+                new PropertyInfo("ignite.client.ssl.truststore.location",
+                    info.getProperty("ignite.client.ssl.truststore.location", ""),
+                    "Trust store to be used by client to connect with Ignite topology."),
+                new PropertyInfo("ignite.client.ssl.keystore.password",
+                    info.getProperty("ignite.client.ssl.truststore.password", ""),
+                    "Trust store password."),
+                new PropertyInfo("ignite.client.ssl.truststore.type",
+                    info.getProperty("ignite.client.ssl.truststore.type", "jks"),
+                    "Trust store type."),
+                new PropertyInfo("ignite.client.credentials",
+                    info.getProperty("ignite.client.credentials", ""),
+                    "Client credentials used in authentication process."),
+                new PropertyInfo("ignite.client.cache.top",
+                    info.getProperty("ignite.client.cache.top", "false"),
+                    "Flag indicating that topology is cached internally. Cache will be refreshed in the " +
+                        "background with interval defined by topologyRefreshFrequency property (see below)."),
+                new PropertyInfo("ignite.client.topology.refresh",
+                    info.getProperty("ignite.client.topology.refresh", "2000"),
+                    "Topology cache refresh frequency (ms)."),
+                new PropertyInfo("ignite.client.idleTimeout",
+                    info.getProperty("ignite.client.idleTimeout", "30000"),
+                    "Maximum amount of time that connection can be idle before it is closed (ms).")
+                )
+            );
+
+        return props.toArray(new DriverPropertyInfo[0]);
     }
 
     /** {@inheritDoc} */
@@ -358,9 +456,44 @@ public class IgniteJdbcDriver implements Driver {
      * @return Whether URL is valid.
      */
     private boolean parseUrl(String url, Properties props) {
-        if (url == null || !url.startsWith(URL_PREFIX) || url.length() == URL_PREFIX.length())
+        if (url == null)
+            return false;
+
+        if (url.startsWith(URL_PREFIX) && url.length() > URL_PREFIX.length())
+            return parseJdbcUrl(url, props);
+        else if (url.startsWith(CFG_URL_PREFIX) && url.length() > CFG_URL_PREFIX.length())
+            return parseJdbcConfigUrl(url, props);
+
+        return false;
+    }
+
+    /**
+     * @param url Url.
+     * @param props Properties.
+     */
+    private boolean parseJdbcConfigUrl(String url, Properties props) {
+        url = url.substring(CFG_URL_PREFIX.length());
+
+        String[] parts = url.split("@");
+
+        if (parts.length > 2)
             return false;
 
+        if (parts.length == 2) {
+            if (!parseParameters(parts[0], ":", props))
+                return false;
+        }
+
+        props.setProperty(PROP_CFG, parts[parts.length - 1]);
+
+        return true;
+    }
+
+    /**
+     * @param url Url.
+     * @param props Properties.
+     */
+    private boolean parseJdbcUrl(String url, Properties props) {
         url = url.substring(URL_PREFIX.length());
 
         String[] parts = url.split("\\?");
@@ -369,7 +502,7 @@ public class IgniteJdbcDriver implements Driver {
             return false;
 
         if (parts.length == 2)
-            if (!parseUrlParameters(parts[1], props))
+            if (!parseParameters(parts[1], "&", props))
                 return false;
 
         parts = parts[0].split("/");
@@ -406,12 +539,13 @@ public class IgniteJdbcDriver implements Driver {
     /**
      * Validates and parses URL parameters.
      *
-     * @param urlParams URL parameters string.
+     * @param val Parameters string.
+     * @param delim Delimiter.
      * @param props Properties.
      * @return Whether URL parameters string is valid.
      */
-    private boolean parseUrlParameters(String urlParams, Properties props) {
-        String[] params = urlParams.split("&");
+    private boolean parseParameters(String val, String delim, Properties props) {
+        String[] params = val.split(delim);
 
         for (String param : params) {
             String[] pair = param.split("=");
@@ -430,13 +564,6 @@ public class IgniteJdbcDriver implements Driver {
      * convenient constructors.
      */
     private static class PropertyInfo extends DriverPropertyInfo {
-        /**
-         * @param name Name.
-         * @param val Value.
-         */
-        private PropertyInfo(String name, String val) {
-            super(name, val);
-        }
 
         /**
          * @param name Name.
@@ -448,29 +575,5 @@ public class IgniteJdbcDriver implements Driver {
 
             description = desc;
         }
-
-        /**
-         * @param name Name.
-         * @param val Value.
-         * @param required Required flag.
-         */
-        private PropertyInfo(String name, String val, boolean required) {
-            super(name, val);
-
-            this.required = required;
-        }
-
-        /**
-         * @param name Name.
-         * @param val Value.
-         * @param desc Description.
-         * @param required Required flag.
-         */
-        private PropertyInfo(String name, String val, String desc, boolean required) {
-            super(name, val);
-
-            description = desc;
-            this.required = required;
-        }
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
index 546a33d..1e4c8b7 100644
--- a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
+++ b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
@@ -356,6 +356,9 @@ public final class IgniteSystemProperties {
     /** Number of times pending cache objects will be dumped to the log in case of partition exchange timeout. */
     public static final String IGNITE_DUMP_PENDING_OBJECTS_THRESHOLD = "IGNITE_DUMP_PENDING_OBJECTS_THRESHOLD";
 
+    /** JDBC driver cursor remove delay. */
+    public static final String IGNITE_JDBC_DRIVER_CURSOR_REMOVE_DELAY = "IGNITE_JDBC_DRIVER_CURSOR_RMV_DELAY";
+
     /**
      * Enforces singleton.
      */
@@ -517,4 +520,4 @@ public final class IgniteSystemProperties {
     public static Properties snapshot() {
         return (Properties)System.getProperties().clone();
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
index 0116ace..a4be6f5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
@@ -56,7 +56,11 @@ import static org.apache.ignite.IgniteJdbcDriver.PROP_PORT;
 
 /**
  * JDBC connection implementation.
+ *
+ * @deprecated Using Ignite client node based JDBC driver is preferable.
+ * See documentation of {@link org.apache.ignite.IgniteJdbcDriver} for details.
  */
+@Deprecated
 public class JdbcConnection implements Connection {
     /** Validation task name. */
     private static final String VALID_TASK_NAME =

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnectionInfo.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnectionInfo.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnectionInfo.java
deleted file mode 100644
index 36fa0aa..0000000
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnectionInfo.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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.jdbc;
-
-/**
- * Connection properties.
- */
-public class JdbcConnectionInfo {
-    /** URL. */
-    private final String url;
-
-    /** Hostname. */
-    private String hostname;
-
-    /** Port number. */
-    private int port;
-
-    /** Cache name. */
-    private String cacheName;
-
-    /**
-     * @param url URL.
-     */
-    JdbcConnectionInfo(String url) {
-        this.url = url;
-    }
-
-    /**
-     * @return URL.
-     */
-    public String url() {
-        return url;
-    }
-
-    /**
-     * @return Hostname.
-     */
-    public String hostname() {
-        return hostname;
-    }
-
-    /**
-     * @param hostname Hostname.
-     */
-    public void hostname(String hostname) {
-        this.hostname = hostname;
-    }
-
-    /**
-     * @return Port number.
-     */
-    public int port() {
-        return port;
-    }
-
-    /**
-     * @param port Port number.
-     */
-    public void port(int port) {
-        this.port = port;
-    }
-
-    /**
-     * @return Cache name.
-     */
-    public String cacheName() {
-        return cacheName;
-    }
-
-    /**
-     * @param cacheName Cache name.
-     */
-    public void cacheName(String cacheName) {
-        this.cacheName = cacheName;
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcDatabaseMetadata.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcDatabaseMetadata.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcDatabaseMetadata.java
index df26412..e2fbe05 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcDatabaseMetadata.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcDatabaseMetadata.java
@@ -39,8 +39,12 @@ import static java.sql.RowIdLifetime.ROWID_UNSUPPORTED;
 
 /**
  * JDBC database metadata implementation.
+ *
+ * @deprecated Using Ignite client node based JDBC driver is preferable.
+ * See documentation of {@link org.apache.ignite.IgniteJdbcDriver} for details.
  */
 @SuppressWarnings("RedundantCast")
+@Deprecated
 public class JdbcDatabaseMetadata implements DatabaseMetaData {
     /** Task name. */
     private static final String TASK_NAME =

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcPreparedStatement.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcPreparedStatement.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcPreparedStatement.java
index 6dfaa18..7e5358b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcPreparedStatement.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcPreparedStatement.java
@@ -41,7 +41,11 @@ import java.util.Calendar;
 
 /**
  * JDBC prepared statement implementation.
+ *
+ * @deprecated Using Ignite client node based JDBC driver is preferable.
+ * See documentation of {@link org.apache.ignite.IgniteJdbcDriver} for details.
  */
+@Deprecated
 public class JdbcPreparedStatement extends JdbcStatement implements PreparedStatement {
     /** SQL query. */
     private final String sql;

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
index 1566006..5961279 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
@@ -49,7 +49,11 @@ import org.apache.ignite.internal.util.typedef.internal.U;
 
 /**
  * JDBC result set implementation.
+ *
+ * @deprecated Using Ignite client node based JDBC driver is preferable.
+ * See documentation of {@link org.apache.ignite.IgniteJdbcDriver} for details.
  */
+@Deprecated
 public class JdbcResultSet implements ResultSet {
     /** Task name. */
     private static final String TASK_NAME =

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSetMetadata.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSetMetadata.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSetMetadata.java
index afe1d28..75fe522 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSetMetadata.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSetMetadata.java
@@ -23,7 +23,11 @@ import java.util.List;
 
 /**
  * JDBC result set metadata implementation.
+ *
+ * @deprecated Using Ignite client node based JDBC driver is preferable.
+ * See documentation of {@link org.apache.ignite.IgniteJdbcDriver} for details.
  */
+@Deprecated
 public class JdbcResultSetMetadata implements ResultSetMetaData {
     /** Column width. */
     private static final int COL_WIDTH = 30;

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java
index caa8495..0f4e08c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcStatement.java
@@ -36,7 +36,11 @@ import static java.sql.ResultSet.TYPE_FORWARD_ONLY;
 
 /**
  * JDBC statement implementation.
+ *
+ * @deprecated Using Ignite client node based JDBC driver is preferable.
+ * See documentation of {@link org.apache.ignite.IgniteJdbcDriver} for details.
  */
+@Deprecated
 public class JdbcStatement implements Statement {
     /** Task name. */
     private static final String TASK_NAME =

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcUtils.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcUtils.java
index 46e3cfa..ecea21f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/JdbcUtils.java
@@ -47,7 +47,11 @@ import static java.sql.Types.VARCHAR;
 
 /**
  * Utility methods for JDBC driver.
+ *
+ * @deprecated Using Ignite client node based JDBC driver is preferable.
+ * See documentation of {@link org.apache.ignite.IgniteJdbcDriver} for details.
  */
+@Deprecated
 class JdbcUtils {
     /** Marshaller. */
     private static final Marshaller MARSHALLER = new JdkMarshaller();

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebb9e2e9/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
new file mode 100644
index 0000000..00eb6b5
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java
@@ -0,0 +1,777 @@
+/*
+ * 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.sql.Array;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.NClob;
+import java.sql.PreparedStatement;
+import java.sql.SQLClientInfoException;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.sql.Struct;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteClientDisconnectedException;
+import org.apache.ignite.IgniteCompute;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteJdbcDriver;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.cluster.ClusterGroup;
+import org.apache.ignite.compute.ComputeTaskTimeoutException;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgnitionEx;
+import org.apache.ignite.internal.processors.resource.GridSpringResourceContext;
+import org.apache.ignite.internal.util.future.GridFutureAdapter;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.lang.IgniteCallable;
+import org.apache.ignite.resources.IgniteInstanceResource;
+
+import static java.sql.ResultSet.CONCUR_READ_ONLY;
+import static java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT;
+import static java.sql.ResultSet.TYPE_FORWARD_ONLY;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.ignite.IgniteJdbcDriver.PROP_CACHE;
+import static org.apache.ignite.IgniteJdbcDriver.PROP_CFG;
+import static org.apache.ignite.IgniteJdbcDriver.PROP_COLLOCATED;
+import static org.apache.ignite.IgniteJdbcDriver.PROP_LOCAL;
+import static org.apache.ignite.IgniteJdbcDriver.PROP_NODE_ID;
+
+/**
+ * JDBC connection implementation.
+ */
+public class JdbcConnection implements Connection {
+    /**
+     * Ignite nodes cache.
+     *
+     * The key is result of concatenation of the following properties:
+     * <ol>
+     *     <li>{@link IgniteJdbcDriver#PROP_CFG}</li>
+     * </ol>
+     */
+    private static final ConcurrentMap<String, IgniteNodeFuture> NODES = new ConcurrentHashMap<>();
+
+    /** Ignite ignite. */
+    private final Ignite ignite;
+
+    /** Node key. */
+    private final String cfg;
+
+    /** Cache name. */
+    private String cacheName;
+
+    /** Closed flag. */
+    private boolean closed;
+
+    /** URL. */
+    private String url;
+
+    /** Node ID. */
+    private UUID nodeId;
+
+    /** Local query flag. */
+    private boolean locQry;
+
+    /** Collocated query flag. */
+    private boolean collocatedQry;
+
+    /** Statements. */
+    final Set<JdbcStatement> statements = new HashSet<>();
+
+    /**
+     * Creates new connection.
+     *
+     * @param url Connection URL.
+     * @param props Additional properties.
+     * @throws SQLException In case Ignite node failed to start.
+     */
+    public JdbcConnection(String url, Properties props) throws SQLException {
+        assert url != null;
+        assert props != null;
+
+        this.url = url;
+
+        this.cacheName = props.getProperty(PROP_CACHE);
+        this.locQry = Boolean.parseBoolean(props.getProperty(PROP_LOCAL));
+        this.collocatedQry = Boolean.parseBoolean(props.getProperty(PROP_COLLOCATED));
+
+        String nodeIdProp = props.getProperty(PROP_NODE_ID);
+
+        if (nodeIdProp != null)
+            this.nodeId = UUID.fromString(nodeIdProp);
+
+        try {
+            cfg = props.getProperty(PROP_CFG);
+
+            ignite = getIgnite(cfg);
+
+            if (!isValid(2))
+                throw new SQLException("Client is invalid. Probably cache name is wrong.");
+        }
+        catch (Exception e) {
+            close();
+
+            if (e instanceof SQLException)
+                throw (SQLException)e;
+            else
+                throw new SQLException("Failed to start Ignite node.", e);
+        }
+    }
+
+    /**
+     * @param cfgUrl Config url.
+     */
+    private Ignite getIgnite(String cfgUrl) throws IgniteCheckedException {
+        while (true) {
+            IgniteNodeFuture fut = NODES.get(cfg);
+
+            if (fut == null) {
+                fut = new IgniteNodeFuture();
+
+                IgniteNodeFuture old = NODES.putIfAbsent(cfg, fut);
+
+                if (old != null)
+                    fut = old;
+                else {
+                    try {
+                        Ignite ignite = Ignition.start(loadConfiguration(cfgUrl));
+
+                        fut.onDone(ignite);
+                    }
+                    catch (IgniteException e) {
+                        fut.onDone(e);
+                    }
+
+                    return fut.get();
+                }
+            }
+
+            if (fut.acquire())
+                return fut.get();
+            else
+                NODES.remove(cfg, fut);
+        }
+    }
+
+    /**
+     * @param cfgUrl Config URL.
+     */
+    private IgniteConfiguration loadConfiguration(String cfgUrl) {
+        try {
+            IgniteBiTuple<Collection<IgniteConfiguration>, ? extends GridSpringResourceContext> cfgMap =
+                IgnitionEx.loadConfigurations(cfgUrl);
+
+            IgniteConfiguration cfg = F.first(cfgMap.get1());
+
+            if (cfg.getGridName() == null)
+                cfg.setGridName("ignite-jdbc-driver-" + UUID.randomUUID().toString());
+
+            return cfg;
+        }
+        catch (IgniteCheckedException e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public Statement createStatement() throws SQLException {
+        return createStatement(TYPE_FORWARD_ONLY, CONCUR_READ_ONLY, HOLD_CURSORS_OVER_COMMIT);
+    }
+
+    /** {@inheritDoc} */
+    @Override public PreparedStatement prepareStatement(String sql) throws SQLException {
+        ensureNotClosed();
+
+        return prepareStatement(sql, TYPE_FORWARD_ONLY, CONCUR_READ_ONLY, HOLD_CURSORS_OVER_COMMIT);
+    }
+
+    /** {@inheritDoc} */
+    @Override public CallableStatement prepareCall(String sql) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Callable functions are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public String nativeSQL(String sql) throws SQLException {
+        ensureNotClosed();
+
+        return sql;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setAutoCommit(boolean autoCommit) throws SQLException {
+        ensureNotClosed();
+
+        if (!autoCommit)
+            throw new SQLFeatureNotSupportedException("Transactions are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean getAutoCommit() throws SQLException {
+        ensureNotClosed();
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void commit() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Transactions are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void rollback() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Transactions are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void close() throws SQLException {
+        if (closed)
+            return;
+
+        closed = true;
+
+        IgniteNodeFuture fut = NODES.get(cfg);
+
+        if (fut != null && fut.release()) {
+            NODES.remove(cfg);
+
+            if (ignite != null)
+                ignite.close();
+        }
+
+        for (Iterator<JdbcStatement> it = statements.iterator(); it.hasNext();) {
+            JdbcStatement stmt = it.next();
+
+            stmt.closeInternal();
+
+            it.remove();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isClosed() throws SQLException {
+        return closed;
+    }
+
+    /** {@inheritDoc} */
+    @Override public DatabaseMetaData getMetaData() throws SQLException {
+        ensureNotClosed();
+
+        return new JdbcDatabaseMetadata(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setReadOnly(boolean readOnly) throws SQLException {
+        ensureNotClosed();
+
+        if (!readOnly)
+            throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isReadOnly() throws SQLException {
+        ensureNotClosed();
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setCatalog(String catalog) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Catalogs are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getCatalog() throws SQLException {
+        ensureNotClosed();
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setTransactionIsolation(int level) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Transactions are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getTransactionIsolation() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Transactions are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public SQLWarning getWarnings() throws SQLException {
+        ensureNotClosed();
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void clearWarnings() throws SQLException {
+        ensureNotClosed();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Statement createStatement(int resSetType, int resSetConcurrency) throws SQLException {
+        return createStatement(resSetType, resSetConcurrency, HOLD_CURSORS_OVER_COMMIT);
+    }
+
+    /** {@inheritDoc} */
+    @Override public PreparedStatement prepareStatement(String sql, int resSetType,
+        int resSetConcurrency) throws SQLException {
+        ensureNotClosed();
+
+        return prepareStatement(sql, resSetType, resSetConcurrency, HOLD_CURSORS_OVER_COMMIT);
+    }
+
+    /** {@inheritDoc} */
+    @Override public CallableStatement prepareCall(String sql, int resSetType,
+        int resSetConcurrency) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Callable functions are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Map<String, Class<?>> getTypeMap() throws SQLException {
+        throw new SQLFeatureNotSupportedException("Types mapping is not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Types mapping is not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setHoldability(int holdability) throws SQLException {
+        ensureNotClosed();
+
+        if (holdability != HOLD_CURSORS_OVER_COMMIT)
+            throw new SQLFeatureNotSupportedException("Invalid holdability (transactions are not supported).");
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getHoldability() throws SQLException {
+        ensureNotClosed();
+
+        return HOLD_CURSORS_OVER_COMMIT;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Savepoint setSavepoint() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Transactions are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Savepoint setSavepoint(String name) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Transactions are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void rollback(Savepoint savepoint) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Transactions are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void releaseSavepoint(Savepoint savepoint) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Transactions are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Statement createStatement(int resSetType, int resSetConcurrency,
+        int resSetHoldability) throws SQLException {
+        ensureNotClosed();
+
+        if (resSetType != TYPE_FORWARD_ONLY)
+            throw new SQLFeatureNotSupportedException("Invalid result set type (only forward is supported.)");
+
+        if (resSetConcurrency != CONCUR_READ_ONLY)
+            throw new SQLFeatureNotSupportedException("Invalid concurrency (updates are not supported).");
+
+        if (resSetHoldability != HOLD_CURSORS_OVER_COMMIT)
+            throw new SQLFeatureNotSupportedException("Invalid holdability (transactions are not supported).");
+
+        JdbcStatement stmt = new JdbcStatement(this);
+
+        statements.add(stmt);
+
+        return stmt;
+    }
+
+    /** {@inheritDoc} */
+    @Override public PreparedStatement prepareStatement(String sql, int resSetType, int resSetConcurrency,
+        int resSetHoldability) throws SQLException {
+        ensureNotClosed();
+
+        if (resSetType != TYPE_FORWARD_ONLY)
+            throw new SQLFeatureNotSupportedException("Invalid result set type (only forward is supported.)");
+
+        if (resSetConcurrency != CONCUR_READ_ONLY)
+            throw new SQLFeatureNotSupportedException("Invalid concurrency (updates are not supported).");
+
+        if (resSetHoldability != HOLD_CURSORS_OVER_COMMIT)
+            throw new SQLFeatureNotSupportedException("Invalid holdability (transactions are not supported).");
+
+        JdbcPreparedStatement stmt = new JdbcPreparedStatement(this, sql);
+
+        statements.add(stmt);
+
+        return stmt;
+    }
+
+    /** {@inheritDoc} */
+    @Override public CallableStatement prepareCall(String sql, int resSetType, int resSetConcurrency,
+        int resSetHoldability) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Callable functions are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public PreparedStatement prepareStatement(String sql, int[] colIndexes) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public PreparedStatement prepareStatement(String sql, String[] colNames) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("Updates are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Clob createClob() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Blob createBlob() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public NClob createNClob() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public SQLXML createSQLXML() throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isValid(int timeout) throws SQLException {
+        ensureNotClosed();
+
+        if (timeout < 0)
+            throw new SQLException("Invalid timeout: " + timeout);
+
+        try {
+            JdbcConnectionValidationTask task = new JdbcConnectionValidationTask(cacheName,
+                nodeId == null ? ignite : null);
+
+            if (nodeId != null) {
+                ClusterGroup grp = ignite.cluster().forServers().forNodeId(nodeId);
+
+                if (grp.nodes().isEmpty())
+                    throw new SQLException("Failed to establish connection with node (is it a server node?): " +
+                        nodeId);
+
+                assert grp.nodes().size() == 1;
+
+                if (grp.node().isDaemon())
+                    throw new SQLException("Failed to establish connection with node (is it a server node?): " +
+                        nodeId);
+
+                IgniteCompute compute = ignite.compute(grp).withAsync();
+
+                compute.call(task);
+
+                return compute.<Boolean>future().get(timeout, SECONDS);
+            }
+            else
+                return task.call();
+        }
+        catch (IgniteClientDisconnectedException | ComputeTaskTimeoutException e) {
+            throw new SQLException("Failed to establish connection.", e);
+        }
+        catch (IgniteException ignored) {
+            return false;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setClientInfo(String name, String val) throws SQLClientInfoException {
+        throw new UnsupportedOperationException("Client info is not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setClientInfo(Properties props) throws SQLClientInfoException {
+        throw new UnsupportedOperationException("Client info is not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getClientInfo(String name) throws SQLException {
+        ensureNotClosed();
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Properties getClientInfo() throws SQLException {
+        ensureNotClosed();
+
+        return new Properties();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public Struct createStruct(String typeName, Object[] attrs) throws SQLException {
+        ensureNotClosed();
+
+        throw new SQLFeatureNotSupportedException("SQL-specific types are not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override public <T> T unwrap(Class<T> iface) throws SQLException {
+        if (!isWrapperFor(iface))
+            throw new SQLException("Connection is not a wrapper for " + iface.getName());
+
+        return (T)this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isWrapperFor(Class<?> iface) throws SQLException {
+        return iface != null && iface == Connection.class;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setSchema(String schema) throws SQLException {
+        cacheName = schema;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getSchema() throws SQLException {
+        return cacheName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void abort(Executor executor) throws SQLException {
+        close();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setNetworkTimeout(Executor executor, int ms) throws SQLException {
+        throw new SQLFeatureNotSupportedException("Network timeout is not supported.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getNetworkTimeout() throws SQLException {
+        throw new SQLFeatureNotSupportedException("Network timeout is not supported.");
+    }
+
+    /**
+     * @return Ignite node.
+     */
+    Ignite ignite() {
+        return ignite;
+    }
+
+    /**
+     * @return Cache name.
+     */
+    String cacheName() {
+        return cacheName;
+    }
+
+    /**
+     * @return URL.
+     */
+    String url() {
+        return url;
+    }
+
+    /**
+     * @return Node ID.
+     */
+    UUID nodeId() {
+        return nodeId;
+    }
+
+    /**
+     * @return Local query flag.
+     */
+    boolean isLocalQuery() {
+        return locQry;
+    }
+
+    /**
+     * @return Collocated query flag.
+     */
+    boolean isCollocatedQuery() {
+        return collocatedQry;
+    }
+
+    /**
+     * Ensures that connection is not closed.
+     *
+     * @throws SQLException If connection is closed.
+     */
+    private void ensureNotClosed() throws SQLException {
+        if (closed)
+            throw new SQLException("Connection is closed.");
+    }
+
+    /**
+     * @return Internal statement.
+     * @throws SQLException In case of error.
+     */
+    JdbcStatement createStatement0() throws SQLException {
+        return (JdbcStatement)createStatement();
+    }
+
+    /**
+     * JDBC connection validation task.
+     */
+    private static class JdbcConnectionValidationTask implements IgniteCallable<Boolean> {
+        /** Serial version uid. */
+        private static final long serialVersionUID = 0L;
+
+        /** Cache name. */
+        private final String cacheName;
+
+        /** Ignite. */
+        @IgniteInstanceResource
+        private Ignite ignite;
+
+        /**
+         * @param cacheName Cache name.
+         * @param ignite Ignite instance.
+         */
+        public JdbcConnectionValidationTask(String cacheName, Ignite ignite) {
+            this.cacheName = cacheName;
+            this.ignite = ignite;
+        }
+
+        /** {@inheritDoc} */
+        @Override public Boolean call() {
+            return ignite.cache(cacheName) != null;
+        }
+    }
+
+    /**
+     *
+     */
+    private static class IgniteNodeFuture extends GridFutureAdapter<Ignite> {
+        /** Reference count. */
+        private final AtomicInteger refCnt = new AtomicInteger(1);
+
+        /**
+         *
+         */
+        public boolean acquire() {
+            while (true) {
+                int cur = refCnt.get();
+
+                if (cur == 0)
+                    return false;
+
+                if (refCnt.compareAndSet(cur, cur + 1))
+                    return true;
+            }
+        }
+
+        /**
+         *
+         */
+        public boolean release() {
+            while (true) {
+                int cur = refCnt.get();
+
+                assert cur > 0;
+
+                if (refCnt.compareAndSet(cur, cur - 1))
+                    // CASed to 0.
+                    return cur == 1;
+            }
+        }
+    }
+}