You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sb...@apache.org on 2015/09/15 08:00:51 UTC

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

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;
+            }
+        }
+    }
+}