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