IGNITE-5233: JDBC thind driver: implemented metadata methods. This closes #2079.
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/0e803144 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/0e803144 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/0e803144 Branch: refs/heads/ignite-5578 Commit: 0e8031444b2f0d68fd3fb5a9ba03ca4d6a0c4e2d Parents: 37e58ba Author: tledkov-gridgain <[email protected]> Authored: Thu Aug 17 13:41:37 2017 +0300 Committer: devozerov <[email protected]> Committed: Thu Aug 17 13:41:37 2017 +0300 ---------------------------------------------------------------------- .../jdbc/thin/JdbcThinMetadataSelfTest.java | 337 +++- .../internal/jdbc/thin/JdbcThinConnection.java | 20 +- .../jdbc/thin/JdbcThinDatabaseMetadata.java | 1587 ++++++++++++++++++ .../jdbc/thin/JdbcThinParameterMetadata.java | 115 ++ .../jdbc/thin/JdbcThinPreparedStatement.java | 27 +- .../internal/jdbc/thin/JdbcThinResultSet.java | 47 +- .../internal/jdbc/thin/JdbcThinStatement.java | 4 +- .../internal/jdbc/thin/JdbcThinTcpIo.java | 117 +- .../processors/odbc/SqlListenerNioListener.java | 9 +- .../odbc/SqlListenerRequestHandler.java | 9 + .../odbc/jdbc/JdbcBatchExecuteRequest.java | 20 +- .../odbc/jdbc/JdbcBatchExecuteResult.java | 6 + .../processors/odbc/jdbc/JdbcColumnMeta.java | 75 +- .../processors/odbc/jdbc/JdbcIndexMeta.java | 192 +++ .../odbc/jdbc/JdbcMetaColumnsRequest.java | 102 ++ .../odbc/jdbc/JdbcMetaColumnsResult.java | 99 ++ .../odbc/jdbc/JdbcMetaIndexesRequest.java | 88 + .../odbc/jdbc/JdbcMetaIndexesResult.java | 98 ++ .../odbc/jdbc/JdbcMetaParamsRequest.java | 87 + .../odbc/jdbc/JdbcMetaParamsResult.java | 97 ++ .../odbc/jdbc/JdbcMetaPrimaryKeysRequest.java | 88 + .../odbc/jdbc/JdbcMetaPrimaryKeysResult.java | 99 ++ .../odbc/jdbc/JdbcMetaSchemasRequest.java | 73 + .../odbc/jdbc/JdbcMetaSchemasResult.java | 73 + .../odbc/jdbc/JdbcMetaTablesRequest.java | 87 + .../odbc/jdbc/JdbcMetaTablesResult.java | 97 ++ .../processors/odbc/jdbc/JdbcParameterMeta.java | 165 ++ .../odbc/jdbc/JdbcPrimaryKeyMeta.java | 131 ++ .../odbc/jdbc/JdbcQueryCloseRequest.java | 4 +- .../odbc/jdbc/JdbcQueryExecuteRequest.java | 8 +- .../odbc/jdbc/JdbcQueryExecuteResult.java | 12 +- .../odbc/jdbc/JdbcQueryFetchRequest.java | 4 +- .../odbc/jdbc/JdbcQueryFetchResult.java | 12 +- .../odbc/jdbc/JdbcQueryMetadataRequest.java | 18 +- .../odbc/jdbc/JdbcQueryMetadataResult.java | 14 +- .../processors/odbc/jdbc/JdbcRequest.java | 67 +- .../odbc/jdbc/JdbcRequestHandler.java | 273 ++- .../processors/odbc/jdbc/JdbcResult.java | 58 +- .../processors/odbc/jdbc/JdbcTableMeta.java | 82 + .../processors/odbc/jdbc/JdbcUtils.java | 37 +- .../odbc/odbc/OdbcRequestHandler.java | 6 + .../processors/query/GridQueryProcessor.java | 2 +- .../query/GridQueryTypeDescriptor.java | 7 + .../query/QueryTypeDescriptorImpl.java | 15 + .../internal/processors/query/QueryUtils.java | 7 +- .../h2/GridIndexingSpiAbstractSelfTest.java | 18 +- 46 files changed, 4472 insertions(+), 121 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/0e803144/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java index 2dae107..01b2e8a 100644 --- a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java @@ -21,16 +21,27 @@ import java.io.Serializable; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.DriverManager; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.Statement; +import java.sql.Types; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Set; import org.apache.ignite.IgniteCache; +import org.apache.ignite.cache.QueryEntity; +import org.apache.ignite.cache.QueryIndex; import org.apache.ignite.cache.affinity.AffinityKey; -import org.apache.ignite.cache.query.annotations.QuerySqlField; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteVersionUtils; +import org.apache.ignite.internal.binary.BinaryMarshaller; 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; @@ -66,15 +77,18 @@ public class JdbcThinMetadataSelfTest extends JdbcThinAbstractSelfTest { } /** + * @param qryEntity Query entity. * @return Cache configuration. */ - protected CacheConfiguration cacheConfiguration() { + protected CacheConfiguration cacheConfiguration(QueryEntity qryEntity) { CacheConfiguration<?,?> cache = defaultCacheConfiguration(); cache.setCacheMode(PARTITIONED); cache.setBackups(1); cache.setWriteSynchronizationMode(FULL_SYNC); + cache.setQueryEntities(Collections.singletonList(qryEntity)); + return cache; } @@ -84,22 +98,49 @@ public class JdbcThinMetadataSelfTest extends JdbcThinAbstractSelfTest { startGridsMultiThreaded(3); - IgniteCache<String, Organization> orgCache = jcache(grid(0), cacheConfiguration(), "org", - String.class, Organization.class); + IgniteCache<String, Organization> orgCache = jcache(grid(0), + cacheConfiguration(new QueryEntity(String.class.getName(), Organization.class.getName()) + .addQueryField("id", Integer.class.getName(), null) + .addQueryField("name", String.class.getName(), null) + .setIndexes(Arrays.asList( + new QueryIndex("id"), + new QueryIndex("name", false, "org_name_index") + ))), "org"); assert orgCache != null; orgCache.put("o1", new Organization(1, "A")); orgCache.put("o2", new Organization(2, "B")); - IgniteCache<AffinityKey, Person> personCache = jcache(grid(0), cacheConfiguration(), "pers", - AffinityKey.class, Person.class); + LinkedHashMap<String, Boolean> persFields = new LinkedHashMap<>(); + + persFields.put("name", true); + persFields.put("age", false); + + IgniteCache<AffinityKey, Person> personCache = jcache(grid(0), cacheConfiguration( + new QueryEntity(AffinityKey.class.getName(), Person.class.getName()) + .addQueryField("name", String.class.getName(), null) + .addQueryField("age", Integer.class.getName(), null) + .addQueryField("orgId", Integer.class.getName(), null) + .setIndexes(Arrays.asList( + new QueryIndex("orgId"), + new QueryIndex().setFields(persFields))) + ), "pers"); assert personCache != null; personCache.put(new AffinityKey<>("p1", "o1"), new Person("John White", 25, 1)); personCache.put(new AffinityKey<>("p2", "o1"), new Person("Joe Black", 35, 1)); personCache.put(new AffinityKey<>("p3", "o2"), new Person("Mike Green", 40, 2)); + + try (Connection conn = DriverManager.getConnection(URL)) { + Statement stmt = conn.createStatement(); + + stmt.execute("CREATE TABLE TEST (ID INT, NAME VARCHAR(50), VAL VARCHAR(50), PRIMARY KEY (ID, NAME))"); + stmt.execute("CREATE TABLE \"Quoted\" (\"Id\" INT primary key, \"Name\" VARCHAR(50))"); + stmt.execute("CREATE INDEX \"MyTestIndex quoted\" on \"Quoted\" (\"Id\" DESC)"); + stmt.execute("CREATE INDEX IDX ON TEST (ID ASC)"); + } } /** {@inheritDoc} */ @@ -147,8 +188,6 @@ public class JdbcThinMetadataSelfTest extends JdbcThinAbstractSelfTest { * @throws Exception If failed. */ public void testGetTables() throws Exception { - fail("https://issues.apache.org/jira/browse/IGNITE-5233"); - try (Connection conn = DriverManager.getConnection(URL)) { DatabaseMetaData meta = conn.getMetaData(); @@ -184,15 +223,43 @@ public class JdbcThinMetadataSelfTest extends JdbcThinAbstractSelfTest { /** * @throws Exception If failed. */ + public void testGetAllTables() throws Exception { + try (Connection conn = DriverManager.getConnection(URL)) { + DatabaseMetaData meta = conn.getMetaData(); + + ResultSet rs = meta.getTables(null, null, null, null); + + Set<String> expectedTbls = new HashSet<>(Arrays.asList( + "org.ORGANIZATION", + "pers.PERSON", + "PUBLIC.TEST", + "PUBLIC.Quoted")); + + Set<String> actualTbls = new HashSet<>(expectedTbls.size()); + + while(rs.next()) { + actualTbls.add(rs.getString("TABLE_SCHEM") + '.' + + rs.getString("TABLE_NAME")); + } + + assert expectedTbls.equals(actualTbls) : "expectedTbls=" + expectedTbls + + ", actualTbls" + actualTbls; + } + } + + /** + * @throws Exception If failed. + */ public void testGetColumns() throws Exception { - fail("https://issues.apache.org/jira/browse/IGNITE-5233"); + final boolean primitivesInformationIsLostAfterStore = ignite(0).configuration().getMarshaller() + instanceof BinaryMarshaller; try (Connection conn = DriverManager.getConnection(URL)) { conn.setSchema("pers"); DatabaseMetaData meta = conn.getMetaData(); - ResultSet rs = meta.getColumns("", "pers", "Person", "%"); + ResultSet rs = meta.getColumns("", "pers", "PERSON", "%"); assert rs != null; @@ -216,7 +283,7 @@ public class JdbcThinMetadataSelfTest extends JdbcThinAbstractSelfTest { } else if ("AGE".equals(name) || "ORGID".equals(name)) { assert rs.getInt("DATA_TYPE") == INTEGER; assert "INTEGER".equals(rs.getString("TYPE_NAME")); - assert rs.getInt("NULLABLE") == 0; + assertEquals(primitivesInformationIsLostAfterStore ? 1 : 0, rs.getInt("NULLABLE")); } if ("_KEY".equals(name)) { assert rs.getInt("DATA_TYPE") == OTHER; @@ -235,7 +302,7 @@ public class JdbcThinMetadataSelfTest extends JdbcThinAbstractSelfTest { assert names.isEmpty(); assert cnt == 3; - rs = meta.getColumns("", "org", "Organization", "%"); + rs = meta.getColumns("", "org", "ORGANIZATION", "%"); assert rs != null; @@ -280,22 +347,243 @@ public class JdbcThinMetadataSelfTest extends JdbcThinAbstractSelfTest { /** * @throws Exception If failed. */ - public void testMetadataResultSetClose() throws Exception { - fail("https://issues.apache.org/jira/browse/IGNITE-5233"); + public void testGetAllColumns() throws Exception { + try (Connection conn = DriverManager.getConnection(URL)) { + DatabaseMetaData meta = conn.getMetaData(); + + ResultSet rs = meta.getColumns(null, null, null, null); + + Set<String> expectedCols = new HashSet<>(Arrays.asList( + "org.ORGANIZATION.ID", + "org.ORGANIZATION.NAME", + "pers.PERSON.ORGID", + "pers.PERSON.AGE", + "pers.PERSON.NAME", + "PUBLIC.TEST.ID", + "PUBLIC.TEST.NAME", + "PUBLIC.TEST.VAL", + "PUBLIC.Quoted.Id", + "PUBLIC.Quoted.Name")); + + Set<String> actualCols = new HashSet<>(expectedCols.size()); + + while(rs.next()) { + actualCols.add(rs.getString("TABLE_SCHEM") + '.' + + rs.getString("TABLE_NAME") + "." + + rs.getString("COLUMN_NAME")); + } + + assert expectedCols.equals(actualCols) : "expectedCols=" + expectedCols + + ", actualCols" + actualCols; + } + } + + /** + * @throws Exception If failed. + */ + public void testInvalidCatalog() throws Exception { + try (Connection conn = DriverManager.getConnection(URL)) { + DatabaseMetaData meta = conn.getMetaData(); + + ResultSet rs = meta.getSchemas("q", null); + + assert !rs.next() : "Results must be empty"; + + rs = meta.getTables("q", null, null, null); + + assert !rs.next() : "Results must be empty"; + + rs = meta.getColumns("q", null, null, null); + + assert !rs.next() : "Results must be empty"; + + rs = meta.getIndexInfo("q", null, null, false, false); + + assert !rs.next() : "Results must be empty"; + + rs = meta.getPrimaryKeys("q", null, null); + + assert !rs.next() : "Results must be empty"; + } + } + + /** + * @throws Exception If failed. + */ + public void testIndexMetadata() throws Exception { + try (Connection conn = DriverManager.getConnection(URL); + ResultSet rs = conn.getMetaData().getIndexInfo(null, "pers", "PERSON", false, false)) { + + int cnt = 0; + + while (rs.next()) { + String idxName = rs.getString("INDEX_NAME"); + String field = rs.getString("COLUMN_NAME"); + String ascOrDesc = rs.getString("ASC_OR_DESC"); + + assert rs.getShort("TYPE") == DatabaseMetaData.tableIndexOther; + + if ("PERSON_ORGID_ASC_IDX".equals(idxName)) { + assert "ORGID".equals(field); + assert "A".equals(ascOrDesc); + } + else if ("PERSON_NAME_ASC_AGE_DESC_IDX".equals(idxName)) { + if ("NAME".equals(field)) + assert "A".equals(ascOrDesc); + else if ("AGE".equals(field)) + assert "D".equals(ascOrDesc); + else + fail("Unexpected field: " + field); + } + else + fail("Unexpected index: " + idxName); + + cnt++; + } + + assert cnt == 3; + } + } + + /** + * @throws Exception If failed. + */ + public void testGetAllIndexes() throws Exception { + try (Connection conn = DriverManager.getConnection(URL)) { + ResultSet rs = conn.getMetaData().getIndexInfo(null, null, null, false, false); + + Set<String> expectedIdxs = new HashSet<>(Arrays.asList( + "org.ORGANIZATION.ORGANIZATION_ID_ASC_IDX", + "org.ORGANIZATION.ORG_NAME_INDEX", + "pers.PERSON.PERSON_ORGID_ASC_IDX", + "pers.PERSON.PERSON_NAME_ASC_AGE_DESC_IDX", + "PUBLIC.TEST.IDX", + "PUBLIC.Quoted.MyTestIndex quoted")); + + Set<String> actualIdxs = new HashSet<>(expectedIdxs.size()); + + while(rs.next()) { + actualIdxs.add(rs.getString("TABLE_SCHEM") + + '.' + rs.getString("TABLE_NAME") + + '.' + rs.getString("INDEX_NAME")); + } + + assert expectedIdxs.equals(actualIdxs) : "expectedIdxs=" + expectedIdxs + + ", actualIdxs" + actualIdxs; + } + } + /** + * @throws Exception If failed. + */ + public void testPrimaryKeyMetadata() throws Exception { try (Connection conn = DriverManager.getConnection(URL); - ResultSet tbls = conn.getMetaData().getTables(null, null, "%", null)) { - int colCnt = tbls.getMetaData().getColumnCount(); + ResultSet rs = conn.getMetaData().getPrimaryKeys(null, "pers", "PERSON")) { + + int cnt = 0; + + while (rs.next()) { + assert "_KEY".equals(rs.getString("COLUMN_NAME")); + + cnt++; + } + + assert cnt == 1; + } + } + + /** + * @throws Exception If failed. + */ + public void testGetAllPrimaryKeys() throws Exception { + try (Connection conn = DriverManager.getConnection(URL)) { + ResultSet rs = conn.getMetaData().getPrimaryKeys(null, null, null); + + Set<String> expectedPks = new HashSet<>(Arrays.asList( + "org.ORGANIZATION.PK_org_ORGANIZATION._KEY", + "pers.PERSON.PK_pers_PERSON._KEY", + "PUBLIC.TEST.PK_PUBLIC_TEST.ID", + "PUBLIC.TEST.PK_PUBLIC_TEST.NAME", + "PUBLIC.Quoted.PK_PUBLIC_Quoted.Id")); + + Set<String> actualPks = new HashSet<>(expectedPks.size()); + + while(rs.next()) { + actualPks.add(rs.getString("TABLE_SCHEM") + + '.' + rs.getString("TABLE_NAME") + + '.' + rs.getString("PK_NAME") + + '.' + rs.getString("COLUMN_NAME")); + } + + assert expectedPks.equals(actualPks) : "expectedPks=" + expectedPks + + ", actualPks" + actualPks; + } + } + + /** + * @throws Exception If failed. + */ + public void testParametersMetadata() throws Exception { + try (Connection conn = DriverManager.getConnection(URL)) { + conn.setSchema("pers"); + + PreparedStatement stmt = conn.prepareStatement("select orgId from Person p where p.name > ? and p.orgId > ?"); + + ParameterMetaData meta = stmt.getParameterMetaData(); + + assert meta != null; + + assert meta.getParameterCount() == 2; + + assert meta.getParameterType(1) == Types.VARCHAR; + assert meta.isNullable(1) == ParameterMetaData.parameterNullableUnknown; + assert meta.getPrecision(1) == Integer.MAX_VALUE; + + assert meta.getParameterType(2) == Types.INTEGER; + assert meta.isNullable(2) == ParameterMetaData.parameterNullableUnknown; + } + } + + /** + * @throws Exception If failed. + */ + public void testSchemasMetadata() throws Exception { + try (Connection conn = DriverManager.getConnection(URL)) { + ResultSet rs = conn.getMetaData().getSchemas(); + + Set<String> expectedSchemas = new HashSet<>(Arrays.asList("PUBLIC", "pers", "org")); - while (tbls.next()) { - for (int i = 0; i < colCnt; i++) - tbls.getObject(i + 1); + Set<String> schemas = new HashSet<>(); + + while (rs.next()) { + schemas.add(rs.getString(1)); + + assert rs.getString(2) == null; } + + assert expectedSchemas.equals(schemas) : "Unexpected schemas: " + schemas + + ". Expected schemas: " + expectedSchemas; } - catch (Exception e) { - log.error("Unexpected exception", e); + } - fail(); + /** + * @throws Exception If failed. + */ + public void testEmptySchemasMetadata() throws Exception { + try (Connection conn = DriverManager.getConnection(URL)) { + ResultSet rs = conn.getMetaData().getSchemas(null, "qqq"); + + assert !rs.next() : "Empty result set is expected"; + } + } + + /** + * @throws Exception If failed. + */ + public void testVersions() throws Exception { + try (Connection conn = DriverManager.getConnection(URL)) { + assert conn.getMetaData().getDatabaseProductVersion().equals(IgniteVersionUtils.VER.toString()); + assert conn.getMetaData().getDriverVersion().equals(IgniteVersionUtils.VER.toString()); } } @@ -305,15 +593,12 @@ public class JdbcThinMetadataSelfTest extends JdbcThinAbstractSelfTest { @SuppressWarnings("UnusedDeclaration") private static class Person implements Serializable { /** Name. */ - @QuerySqlField(index = false) private final String name; /** Age. */ - @QuerySqlField private final int age; /** Organization ID. */ - @QuerySqlField private final int orgId; /** @@ -338,11 +623,9 @@ public class JdbcThinMetadataSelfTest extends JdbcThinAbstractSelfTest { @SuppressWarnings("UnusedDeclaration") private static class Organization implements Serializable { /** ID. */ - @QuerySqlField private final int id; /** Name. */ - @QuerySqlField(index = false) private final String name; /** http://git-wip-us.apache.org/repos/asf/ignite/blob/0e803144/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java index 89ef2fc..8836cd5 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java @@ -64,6 +64,9 @@ public class JdbcThinConnection implements Connection { /** Logger. */ private static final Logger LOG = Logger.getLogger(JdbcThinConnection.class.getName()); + /** Connection URL. */ + private String url; + /** Schema name. */ private String schemaName; @@ -88,6 +91,9 @@ public class JdbcThinConnection implements Connection { /** Ignite endpoint. */ private JdbcThinTcpIo cliIo; + /** Jdbc metadata. Cache the JDBC object on the first access */ + private JdbcThinDatabaseMetadata metadata; + /** * Creates new connection. * @@ -99,6 +105,8 @@ public class JdbcThinConnection implements Connection { assert url != null; assert props != null; + this.url = url; + holdability = HOLD_CURSORS_OVER_COMMIT; autoCommit = true; txIsolation = Connection.TRANSACTION_NONE; @@ -274,7 +282,10 @@ public class JdbcThinConnection implements Connection { @Override public DatabaseMetaData getMetaData() throws SQLException { ensureNotClosed(); - return null; + if (metadata == null) + metadata = new JdbcThinDatabaseMetadata(this); + + return metadata; } /** {@inheritDoc} */ @@ -665,4 +676,11 @@ public class JdbcThinConnection implements Connection { ", value=" + strVal + ']'); } } + + /** + * @return Connection URL. + */ + public String url() { + return url; + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/0e803144/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinDatabaseMetadata.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinDatabaseMetadata.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinDatabaseMetadata.java new file mode 100644 index 0000000..583bcec --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinDatabaseMetadata.java @@ -0,0 +1,1587 @@ +/* + * 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.thin; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.RowIdLifetime; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.internal.IgniteVersionUtils; +import org.apache.ignite.internal.jdbc2.JdbcUtils; +import org.apache.ignite.internal.processors.odbc.jdbc.JdbcColumnMeta; +import org.apache.ignite.internal.processors.odbc.jdbc.JdbcIndexMeta; +import org.apache.ignite.internal.processors.odbc.jdbc.JdbcMetaColumnsResult; +import org.apache.ignite.internal.processors.odbc.jdbc.JdbcMetaIndexesResult; +import org.apache.ignite.internal.processors.odbc.jdbc.JdbcMetaPrimaryKeysResult; +import org.apache.ignite.internal.processors.odbc.jdbc.JdbcMetaSchemasResult; +import org.apache.ignite.internal.processors.odbc.jdbc.JdbcMetaTablesResult; +import org.apache.ignite.internal.processors.odbc.jdbc.JdbcPrimaryKeyMeta; +import org.apache.ignite.internal.processors.odbc.jdbc.JdbcTableMeta; +import org.apache.ignite.internal.util.typedef.F; + +import static java.sql.Connection.TRANSACTION_NONE; +import static java.sql.ResultSet.CONCUR_READ_ONLY; +import static java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT; +import static java.sql.ResultSet.TYPE_FORWARD_ONLY; +import static java.sql.RowIdLifetime.ROWID_UNSUPPORTED; + +/** + * JDBC database metadata implementation. + */ +@SuppressWarnings("RedundantCast") +public class JdbcThinDatabaseMetadata implements DatabaseMetaData { + /** Connection. */ + private final JdbcThinConnection conn; + + /** + * @param conn Connection. + */ + JdbcThinDatabaseMetadata(JdbcThinConnection conn) { + this.conn = conn; + } + + /** {@inheritDoc} */ + @Override public boolean allProceduresAreCallable() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean allTablesAreSelectable() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public String getURL() throws SQLException { + return conn.url(); + } + + /** {@inheritDoc} */ + @Override public String getUserName() throws SQLException { + return ""; + } + + /** {@inheritDoc} */ + @Override public boolean isReadOnly() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean nullsAreSortedHigh() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean nullsAreSortedLow() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean nullsAreSortedAtStart() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean nullsAreSortedAtEnd() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public String getDatabaseProductName() throws SQLException { + return "Ignite"; + } + + /** {@inheritDoc} */ + @Override public String getDatabaseProductVersion() throws SQLException { + return conn.io().igniteVersion().toString(); + } + + /** {@inheritDoc} */ + @Override public String getDriverName() throws SQLException { + return "Ignite JDBC Thin Driver"; + } + + /** {@inheritDoc} */ + @Override public String getDriverVersion() throws SQLException { + return IgniteVersionUtils.VER.toString(); + } + + /** {@inheritDoc} */ + @Override public int getDriverMajorVersion() { + return IgniteVersionUtils.VER.major(); + } + + /** {@inheritDoc} */ + @Override public int getDriverMinorVersion() { + return IgniteVersionUtils.VER.minor(); + } + + /** {@inheritDoc} */ + @Override public boolean usesLocalFiles() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean usesLocalFilePerTable() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsMixedCaseIdentifiers() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean storesUpperCaseIdentifiers() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean storesLowerCaseIdentifiers() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean storesMixedCaseIdentifiers() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean storesUpperCaseQuotedIdentifiers() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean storesLowerCaseQuotedIdentifiers() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean storesMixedCaseQuotedIdentifiers() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public String getIdentifierQuoteString() throws SQLException { + return "\""; + } + + /** {@inheritDoc} */ + @Override public String getSQLKeywords() throws SQLException { + return "LIMIT,MINUS,ROWNUM,SYSDATE,SYSTIME,SYSTIMESTAMP,TODAY"; + } + + /** {@inheritDoc} */ + @Override public String getNumericFunctions() throws SQLException { + // TODO: IGNITE-6028 + return ""; + } + + /** {@inheritDoc} */ + @Override public String getStringFunctions() throws SQLException { + // TODO: IGNITE-6028 + return ""; + } + + /** {@inheritDoc} */ + @Override public String getSystemFunctions() throws SQLException { + // TODO: IGNITE-6028 + return ""; + } + + /** {@inheritDoc} */ + @Override public String getTimeDateFunctions() throws SQLException { + // TODO: IGNITE-6028 + return ""; + } + + /** {@inheritDoc} */ + @Override public String getSearchStringEscape() throws SQLException { + return "\\"; + } + + /** {@inheritDoc} */ + @Override public String getExtraNameCharacters() throws SQLException { + return ""; + } + + /** {@inheritDoc} */ + @Override public boolean supportsAlterTableWithAddColumn() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsAlterTableWithDropColumn() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsColumnAliasing() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean nullPlusNonNullIsNull() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsConvert() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsConvert(int fromType, int toType) throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsTableCorrelationNames() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsDifferentTableCorrelationNames() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsExpressionsInOrderBy() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsOrderByUnrelated() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsGroupBy() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsGroupByUnrelated() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsGroupByBeyondSelect() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsLikeEscapeClause() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsMultipleResultSets() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsMultipleTransactions() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsNonNullableColumns() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsMinimumSQLGrammar() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsCoreSQLGrammar() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsExtendedSQLGrammar() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsANSI92EntryLevelSQL() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsANSI92IntermediateSQL() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsANSI92FullSQL() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsIntegrityEnhancementFacility() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsOuterJoins() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsFullOuterJoins() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsLimitedOuterJoins() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public String getSchemaTerm() throws SQLException { + return ""; + } + + /** {@inheritDoc} */ + @Override public String getProcedureTerm() throws SQLException { + return ""; + } + + /** {@inheritDoc} */ + @Override public String getCatalogTerm() throws SQLException { + return ""; + } + + /** {@inheritDoc} */ + @Override public boolean isCatalogAtStart() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public String getCatalogSeparator() throws SQLException { + return ""; + } + + /** {@inheritDoc} */ + @Override public boolean supportsSchemasInDataManipulation() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsSchemasInProcedureCalls() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsSchemasInTableDefinitions() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsSchemasInIndexDefinitions() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsCatalogsInDataManipulation() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsCatalogsInProcedureCalls() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsCatalogsInTableDefinitions() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsCatalogsInIndexDefinitions() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsPositionedDelete() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsPositionedUpdate() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsSelectForUpdate() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsStoredProcedures() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsSubqueriesInComparisons() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsSubqueriesInExists() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsSubqueriesInIns() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsSubqueriesInQuantifieds() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsCorrelatedSubqueries() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsUnion() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsUnionAll() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean supportsOpenCursorsAcrossCommit() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsOpenCursorsAcrossRollback() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsOpenStatementsAcrossCommit() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsOpenStatementsAcrossRollback() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public int getMaxBinaryLiteralLength() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxCharLiteralLength() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxColumnNameLength() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxColumnsInGroupBy() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxColumnsInIndex() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxColumnsInOrderBy() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxColumnsInSelect() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxColumnsInTable() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxConnections() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxCursorNameLength() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxIndexLength() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxSchemaNameLength() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxProcedureNameLength() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxCatalogNameLength() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxRowSize() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public boolean doesMaxRowSizeIncludeBlobs() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public int getMaxStatementLength() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxStatements() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxTableNameLength() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxTablesInSelect() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getMaxUserNameLength() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getDefaultTransactionIsolation() throws SQLException { + return TRANSACTION_NONE; + } + + /** {@inheritDoc} */ + @Override public boolean supportsTransactions() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsTransactionIsolationLevel(int level) throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsDataManipulationTransactionsOnly() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean dataDefinitionCausesTransactionCommit() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean dataDefinitionIgnoredInTransactions() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public ResultSet getProcedures(String catalog, String schemaPtrn, + String procedureNamePtrn) throws SQLException { + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "PROCEDURE_CAT", String.class), + new JdbcColumnMeta(null, null, "PROCEDURE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "PROCEDURE_NAME", String.class), + new JdbcColumnMeta(null, null, "REMARKS", String.class), + new JdbcColumnMeta(null, null, "PROCEDURE_TYPE", String.class), + new JdbcColumnMeta(null, null, "SPECIFIC_NAME", String.class) + )); + } + + /** {@inheritDoc} */ + @Override public ResultSet getProcedureColumns(String catalog, String schemaPtrn, String procedureNamePtrn, + String colNamePtrn) throws SQLException { + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "PROCEDURE_CAT", String.class), + new JdbcColumnMeta(null, null, "PROCEDURE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "PROCEDURE_NAME", String.class), + new JdbcColumnMeta(null, null, "COLUMN_NAME", String.class), + new JdbcColumnMeta(null, null, "COLUMN_TYPE", Short.class), + new JdbcColumnMeta(null, null, "COLUMN_TYPE", Integer.class), + new JdbcColumnMeta(null, null, "TYPE_NAME", String.class), + new JdbcColumnMeta(null, null, "PRECISION", Integer.class), + new JdbcColumnMeta(null, null, "LENGTH", Integer.class), + new JdbcColumnMeta(null, null, "SCALE", Short.class), + new JdbcColumnMeta(null, null, "RADIX", Short.class), + new JdbcColumnMeta(null, null, "NULLABLE", Short.class), + new JdbcColumnMeta(null, null, "REMARKS", String.class), + new JdbcColumnMeta(null, null, "COLUMN_DEF", String.class), + new JdbcColumnMeta(null, null, "SQL_DATA_TYPE", Integer.class), + new JdbcColumnMeta(null, null, "SQL_DATETIME_SUB", Integer.class), + new JdbcColumnMeta(null, null, "CHAR_OCTET_LENGTH", Integer.class), + new JdbcColumnMeta(null, null, "ORDINAL_POSITION", Integer.class), + new JdbcColumnMeta(null, null, "IS_NULLABLE", String.class), + new JdbcColumnMeta(null, null, "SPECIFIC_NAME", String.class) + )); + } + + /** {@inheritDoc} */ + @Override public ResultSet getTables(String catalog, String schemaPtrn, String tblNamePtrn, + String[] tblTypes) throws SQLException { + if (conn.isClosed()) + throw new SQLException("Connection is closed."); + + final List<JdbcColumnMeta> meta = Arrays.asList( + new JdbcColumnMeta(null, null, "TABLE_CAT", String.class), + new JdbcColumnMeta(null, null, "TABLE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "TABLE_NAME", String.class), + new JdbcColumnMeta(null, null, "TABLE_TYPE", String.class), + new JdbcColumnMeta(null, null, "REMARKS", String.class), + new JdbcColumnMeta(null, null, "TYPE_CAT", String.class), + new JdbcColumnMeta(null, null, "TYPE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "TYPE_NAME", String.class), + new JdbcColumnMeta(null, null, "SELF_REFERENCING_COL_NAME", String.class), + new JdbcColumnMeta(null, null, "REF_GENERATION", String.class)); + + boolean tblTypeMatch = false; + + if (tblTypes == null) + tblTypeMatch = true; + else { + for (String type : tblTypes) { + if ("TABLE".equals(type)) { + tblTypeMatch = true; + + break; + } + } + } + + if (!validCatalogPattern(catalog) || !tblTypeMatch) + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), meta); + + try { + JdbcMetaTablesResult res = conn.io().tablesMeta(schemaPtrn, tblNamePtrn); + + List<List<Object>> rows = new LinkedList<>(); + + for (JdbcTableMeta tblMeta : res.meta()) + rows.add(tableRow(tblMeta)); + + return new JdbcThinResultSet(rows, meta); + } + catch (IOException e) { + conn.close(); + + throw new SQLException("Failed to query Ignite.", e); + } + catch (IgniteCheckedException e) { + throw new SQLException("Failed to query Ignite.", e); + } + } + + /** + * @param tblMeta Table metadata. + * @return Table metadata row. + */ + private List<Object> tableRow(JdbcTableMeta tblMeta) { + List<Object> row = new ArrayList<>(10); + + row.add(null); + row.add(tblMeta.schemaName()); + row.add(tblMeta.tableName()); + row.add("TABLE"); + row.add(null); + row.add(null); + row.add(null); + row.add(null); + row.add(null); + row.add(null); + + return row; + } + + /** {@inheritDoc} */ + @Override public ResultSet getSchemas() throws SQLException { + return getSchemas(null, "%"); + } + + /** {@inheritDoc} */ + @SuppressWarnings("ArraysAsListWithZeroOrOneArgument") + @Override public ResultSet getCatalogs() throws SQLException { + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), + Arrays.asList(new JdbcColumnMeta(null, null, "TABLE_CAT", String.class))); + } + + /** {@inheritDoc} */ + @SuppressWarnings("ArraysAsListWithZeroOrOneArgument") + @Override public ResultSet getTableTypes() throws SQLException { + return new JdbcThinResultSet(Collections.singletonList(Collections.<Object>singletonList("TABLE")), + Arrays.asList(new JdbcColumnMeta(null, null, "TABLE_TYPE", String.class))); + } + + /** {@inheritDoc} */ + @Override public ResultSet getColumns(String catalog, String schemaPtrn, String tblNamePtrn, + String colNamePtrn) throws SQLException { + if (conn.isClosed()) + throw new SQLException("Connection is closed."); + + final List<JdbcColumnMeta> meta = Arrays.asList( + new JdbcColumnMeta(null, null, "TABLE_CAT", String.class), + new JdbcColumnMeta(null, null, "TABLE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "TABLE_NAME", String.class), + new JdbcColumnMeta(null, null, "COLUMN_NAME", String.class), + new JdbcColumnMeta(null, null, "DATA_TYPE", Short.class), + new JdbcColumnMeta(null, null, "TYPE_NAME", String.class), + new JdbcColumnMeta(null, null, "COLUMN_SIZE", Integer.class), + new JdbcColumnMeta(null, null, "DECIMAL_DIGITS", Integer.class), + new JdbcColumnMeta(null, null, "NUM_PREC_RADIX", Short.class), + new JdbcColumnMeta(null, null, "NULLABLE", Short.class), + new JdbcColumnMeta(null, null, "REMARKS", String.class), + new JdbcColumnMeta(null, null, "COLUMN_DEF", String.class), + new JdbcColumnMeta(null, null, "CHAR_OCTET_LENGTH", Integer.class), + new JdbcColumnMeta(null, null, "ORDINAL_POSITION", Integer.class), + new JdbcColumnMeta(null, null, "IS_NULLABLE", String.class), + new JdbcColumnMeta(null, null, "SCOPE_CATLOG", String.class), + new JdbcColumnMeta(null, null, "SCOPE_SCHEMA", String.class), + new JdbcColumnMeta(null, null, "SCOPE_TABLE", String.class), + new JdbcColumnMeta(null, null, "SOURCE_DATA_TYPE", Short.class), + new JdbcColumnMeta(null, null, "IS_AUTOINCREMENT", String.class)); + + if (!validCatalogPattern(catalog)) + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), meta); + + try { + JdbcMetaColumnsResult res = conn.io().columnsMeta(schemaPtrn, tblNamePtrn, colNamePtrn); + + List<List<Object>> rows = new LinkedList<>(); + + for (int i = 0; i < res.meta().size(); ++i) + rows.add(columnRow(res.meta().get(i), i + 1)); + + return new JdbcThinResultSet(rows, meta); + } + catch (IOException e) { + conn.close(); + + throw new SQLException("Failed to query Ignite.", e); + } + catch (IgniteCheckedException e) { + throw new SQLException("Failed to query Ignite.", e); + } + } + + /** + * @param colMeta Column metadata. + * @param pos Ordinal position. + * @return Column metadata row. + */ + private List<Object> columnRow(JdbcColumnMeta colMeta, int pos) { + List<Object> row = new ArrayList<>(20); + + row.add((String)null); + row.add(colMeta.schemaName()); + row.add(colMeta.tableName()); + row.add(colMeta.columnName()); + row.add(colMeta.dataType()); + row.add(colMeta.dataTypeName()); + row.add((Integer)null); + row.add((Integer)null); + row.add(10); + row.add(JdbcUtils.nullable(colMeta.columnName(), colMeta.dataTypeClass()) ? 1 : 0 ); + row.add((String)null); + row.add((String)null); + row.add(Integer.MAX_VALUE); + row.add(pos); + row.add("YES"); + row.add((String)null); + row.add((String)null); + row.add((String)null); + row.add((Short)null); + row.add("NO"); + + return row; + } + + /** {@inheritDoc} */ + @Override public ResultSet getColumnPrivileges(String catalog, String schema, String tbl, + String colNamePtrn) throws SQLException { + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "TABLE_CAT", String.class), + new JdbcColumnMeta(null, null, "TABLE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "TABLE_NAME", String.class), + new JdbcColumnMeta(null, null, "COLUMN_NAME", String.class), + new JdbcColumnMeta(null, null, "GRANTOR", String.class), + new JdbcColumnMeta(null, null, "GRANTEE", String.class), + new JdbcColumnMeta(null, null, "PRIVILEGE", String.class), + new JdbcColumnMeta(null, null, "IS_GRANTABLE", String.class) + )); + } + + /** {@inheritDoc} */ + @Override public ResultSet getTablePrivileges(String catalog, String schemaPtrn, + String tblNamePtrn) throws SQLException { + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "TABLE_CAT", String.class), + new JdbcColumnMeta(null, null, "TABLE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "TABLE_NAME", String.class), + new JdbcColumnMeta(null, null, "GRANTOR", String.class), + new JdbcColumnMeta(null, null, "GRANTEE", String.class), + new JdbcColumnMeta(null, null, "PRIVILEGE", String.class), + new JdbcColumnMeta(null, null, "IS_GRANTABLE", String.class) + )); + } + + /** {@inheritDoc} */ + @Override public ResultSet getBestRowIdentifier(String catalog, String schema, String tbl, int scope, + boolean nullable) throws SQLException { + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "SCOPE", Short.class), + new JdbcColumnMeta(null, null, "COLUMN_NAME", String.class), + new JdbcColumnMeta(null, null, "DATA_TYPE", Integer.class), + new JdbcColumnMeta(null, null, "TYPE_NAME", String.class), + new JdbcColumnMeta(null, null, "COLUMN_SIZE", Integer.class), + new JdbcColumnMeta(null, null, "BUFFER_LENGTH", Integer.class), + new JdbcColumnMeta(null, null, "DECIMAL_DIGITS", Short.class), + new JdbcColumnMeta(null, null, "PSEUDO_COLUMN", Short.class) + )); + } + + /** {@inheritDoc} */ + @Override public ResultSet getVersionColumns(String catalog, String schema, String tbl) throws SQLException { + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "SCOPE", Short.class), + new JdbcColumnMeta(null, null, "COLUMN_NAME", String.class), + new JdbcColumnMeta(null, null, "DATA_TYPE", Integer.class), + new JdbcColumnMeta(null, null, "TYPE_NAME", String.class), + new JdbcColumnMeta(null, null, "COLUMN_SIZE", Integer.class), + new JdbcColumnMeta(null, null, "BUFFER_LENGTH", Integer.class), + new JdbcColumnMeta(null, null, "DECIMAL_DIGITS", Short.class), + new JdbcColumnMeta(null, null, "PSEUDO_COLUMN", Short.class) + )); + } + + /** {@inheritDoc} */ + @Override public ResultSet getPrimaryKeys(String catalog, String schema, String tbl) throws SQLException { + if (conn.isClosed()) + throw new SQLException("Connection is closed."); + + final List<JdbcColumnMeta> meta = Arrays.asList( + new JdbcColumnMeta(null, null, "TABLE_CAT", String.class), + new JdbcColumnMeta(null, null, "TABLE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "TABLE_NAME", String.class), + new JdbcColumnMeta(null, null, "COLUMN_NAME", String.class), + new JdbcColumnMeta(null, null, "KEY_SEQ", Short.class), + new JdbcColumnMeta(null, null, "PK_NAME", String.class)); + + if (!validCatalogPattern(catalog)) + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), meta); + + try { + JdbcMetaPrimaryKeysResult res = conn.io().primaryKeysMeta(schema, tbl); + + List<List<Object>> rows = new LinkedList<>(); + + for (JdbcPrimaryKeyMeta pkMeta : res.meta()) + rows.addAll(primaryKeyRows(pkMeta)); + + return new JdbcThinResultSet(rows, meta); + } + catch (IOException e) { + conn.close(); + + throw new SQLException("Failed to query Ignite.", e); + } + catch (IgniteCheckedException e) { + throw new SQLException("Failed to query Ignite.", e); + } + } + + /** + * @param pkMeta Primary key metadata. + * @return Result set rows for primary key. + */ + private List<List<Object>> primaryKeyRows(JdbcPrimaryKeyMeta pkMeta) { + List<List<Object>> rows = new ArrayList<>(pkMeta.fields().size()); + + for (int i = 0; i < pkMeta.fields().size(); ++i) { + List<Object> row = new ArrayList<>(6); + + row.add((String)null); // table catalog + row.add(pkMeta.schemaName()); + row.add(pkMeta.tableName()); + row.add(pkMeta.fields().get(i)); + row.add((Integer)i + 1); // sequence number + row.add(pkMeta.name()); + + rows.add(row); + } + + return rows; + } + + /** {@inheritDoc} */ + @Override public ResultSet getImportedKeys(String catalog, String schema, String tbl) throws SQLException { + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "PKTABLE_CAT", String.class), + new JdbcColumnMeta(null, null, "PKTABLE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "PKTABLE_NAME", String.class), + new JdbcColumnMeta(null, null, "PKCOLUMN_NAME", String.class), + new JdbcColumnMeta(null, null, "FKTABLE_CAT", String.class), + new JdbcColumnMeta(null, null, "FKTABLE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "FKTABLE_NAME", String.class), + new JdbcColumnMeta(null, null, "FKCOLUMN_NAME", String.class), + new JdbcColumnMeta(null, null, "KEY_SEQ", Short.class), + new JdbcColumnMeta(null, null, "UPDATE_RULE", Short.class), + new JdbcColumnMeta(null, null, "DELETE_RULE", Short.class), + new JdbcColumnMeta(null, null, "FK_NAME", String.class), + new JdbcColumnMeta(null, null, "PK_NAME", String.class), + new JdbcColumnMeta(null, null, "DEFERRABILITY", Short.class) + )); + } + + /** {@inheritDoc} */ + @Override public ResultSet getExportedKeys(String catalog, String schema, String tbl) throws SQLException { + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "PKTABLE_CAT", String.class), + new JdbcColumnMeta(null, null, "PKTABLE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "PKTABLE_NAME", String.class), + new JdbcColumnMeta(null, null, "PKCOLUMN_NAME", String.class), + new JdbcColumnMeta(null, null, "FKTABLE_CAT", String.class), + new JdbcColumnMeta(null, null, "FKTABLE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "FKTABLE_NAME", String.class), + new JdbcColumnMeta(null, null, "FKCOLUMN_NAME", String.class), + new JdbcColumnMeta(null, null, "KEY_SEQ", Short.class), + new JdbcColumnMeta(null, null, "UPDATE_RULE", Short.class), + new JdbcColumnMeta(null, null, "DELETE_RULE", Short.class), + new JdbcColumnMeta(null, null, "FK_NAME", String.class), + new JdbcColumnMeta(null, null, "PK_NAME", String.class), + new JdbcColumnMeta(null, null, "DEFERRABILITY", Short.class) + )); + } + + /** {@inheritDoc} */ + @Override public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTbl, + String foreignCatalog, String foreignSchema, String foreignTbl) throws SQLException { + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "PKTABLE_CAT", String.class), + new JdbcColumnMeta(null, null, "PKTABLE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "PKTABLE_NAME", String.class), + new JdbcColumnMeta(null, null, "PKCOLUMN_NAME", String.class), + new JdbcColumnMeta(null, null, "FKTABLE_CAT", String.class), + new JdbcColumnMeta(null, null, "FKTABLE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "FKTABLE_NAME", String.class), + new JdbcColumnMeta(null, null, "FKCOLUMN_NAME", String.class), + new JdbcColumnMeta(null, null, "KEY_SEQ", Short.class), + new JdbcColumnMeta(null, null, "UPDATE_RULE", Short.class), + new JdbcColumnMeta(null, null, "DELETE_RULE", Short.class), + new JdbcColumnMeta(null, null, "FK_NAME", String.class), + new JdbcColumnMeta(null, null, "PK_NAME", String.class), + new JdbcColumnMeta(null, null, "DEFERRABILITY", Short.class) + )); + } + + /** {@inheritDoc} */ + @Override public ResultSet getTypeInfo() throws SQLException { + List<List<Object>> types = new ArrayList<>(21); + + types.add(Arrays.<Object>asList("BOOLEAN", Types.BOOLEAN, 1, null, null, null, + (short)typeNullable, false, (short)typeSearchable, false, false, false, "BOOLEAN", 0, 0, + Types.BOOLEAN, 0, 10)); + + types.add(Arrays.<Object>asList("TINYINT", Types.TINYINT, 3, null, null, null, + (short)typeNullable, false, (short)typeSearchable, false, false, false, "TINYINT", 0, 0, + Types.TINYINT, 0, 10)); + + types.add(Arrays.<Object>asList("SMALLINT", Types.SMALLINT, 5, null, null, null, + (short)typeNullable, false, (short)typeSearchable, false, false, false, "SMALLINT", 0, 0, + Types.SMALLINT, 0, 10)); + + types.add(Arrays.<Object>asList("INTEGER", Types.INTEGER, 10, null, null, null, + (short)typeNullable, false, (short)typeSearchable, false, false, false, "INTEGER", 0, 0, + Types.INTEGER, 0, 10)); + + types.add(Arrays.<Object>asList("BIGINT", Types.BIGINT, 19, null, null, null, + (short)typeNullable, false, (short)typeSearchable, false, false, false, "BIGINT", 0, 0, + Types.BIGINT, 0, 10)); + + types.add(Arrays.<Object>asList("FLOAT", Types.FLOAT, 17, null, null, null, + (short)typeNullable, false, (short)typeSearchable, false, false, false, "FLOAT", 0, 0, + Types.FLOAT, 0, 10)); + + types.add(Arrays.<Object>asList("REAL", Types.REAL, 7, null, null, null, + (short)typeNullable, false, (short)typeSearchable, false, false, false, "REAL", 0, 0, + Types.REAL, 0, 10)); + + types.add(Arrays.<Object>asList("DOUBLE", Types.DOUBLE, 17, null, null, null, + (short)typeNullable, false, (short)typeSearchable, false, false, false, "DOUBLE", 0, 0, + Types.DOUBLE, 0, 10)); + + types.add(Arrays.<Object>asList("NUMERIC", Types.NUMERIC, Integer.MAX_VALUE, null, null, "PRECISION,SCALE", + (short)typeNullable, false, (short)typeSearchable, false, false, false, "NUMERIC", 0, 0, + Types.NUMERIC, 0, 10)); + + types.add(Arrays.<Object>asList("DECIMAL", Types.DECIMAL, Integer.MAX_VALUE, null, null, "PRECISION,SCALE", + (short)typeNullable, false, (short)typeSearchable, false, false, false, "DECIMAL", 0, 0, + Types.DECIMAL, 0, 10)); + + types.add(Arrays.<Object>asList("DATE", Types.DATE, 8, "DATE '", "'", null, + (short)typeNullable, false, (short)typeSearchable, false, false, false, "DATE", 0, 0, + Types.DATE, 0, null)); + + types.add(Arrays.<Object>asList("TIME", Types.TIME, 6, "TIME '", "'", null, + (short)typeNullable, false, (short)typeSearchable, false, false, false, "TIME", 0, 0, + Types.TIME, 0, null)); + + types.add(Arrays.<Object>asList("TIMESTAMP", Types.TIMESTAMP, 23, "TIMESTAMP '", "'", null, + (short)typeNullable, false, (short)typeSearchable, false, false, false, "TIMESTAMP", 0, 10, + Types.TIMESTAMP, 0, null)); + + types.add(Arrays.<Object>asList("CHAR", Types.CHAR, Integer.MAX_VALUE, "'", "'", "LENGTH", + (short)typeNullable, true, (short)typeSearchable, false, false, false, "CHAR", 0, 0, + Types.CHAR, 0, null)); + + types.add(Arrays.<Object>asList("VARCHAR", Types.VARCHAR, Integer.MAX_VALUE, "'", "'", "LENGTH", + (short)typeNullable, true, (short)typeSearchable, false, false, false, "VARCHAR", 0, 0, + Types.VARCHAR, 0, null)); + + types.add(Arrays.<Object>asList("LONGVARCHAR", Types.LONGVARCHAR, Integer.MAX_VALUE, "'", "'", "LENGTH", + (short)typeNullable, true, (short)typeSearchable, false, false, false, "LONGVARCHAR", 0, 0, + Types.LONGVARCHAR, 0, null)); + + types.add(Arrays.<Object>asList("BINARY", Types.BINARY, Integer.MAX_VALUE, "'", "'", "LENGTH", + (short)typeNullable, false, (short)typeSearchable, false, false, false, "BINARY", 0, 0, + Types.BINARY, 0, null)); + + types.add(Arrays.<Object>asList("VARBINARY", Types.VARBINARY, Integer.MAX_VALUE, "'", "'", "LENGTH", + (short)typeNullable, false, (short)typeSearchable, false, false, false, "VARBINARY", 0, 0, + Types.VARBINARY, 0, null)); + + types.add(Arrays.<Object>asList("LONGVARBINARY", Types.LONGVARBINARY, Integer.MAX_VALUE, "'", "'", "LENGTH", + (short)typeNullable, false, (short)typeSearchable, false, false, false, "LONGVARBINARY", 0, 0, + Types.LONGVARBINARY, 0, null)); + + types.add(Arrays.<Object>asList("OTHER", Types.OTHER, Integer.MAX_VALUE, "'", "'", "LENGTH", + (short)typeNullable, false, (short)typeSearchable, false, false, false, "OTHER", 0, 0, + Types.OTHER, 0, null)); + + types.add(Arrays.<Object>asList("ARRAY", Types.ARRAY, 0, "(", "')", null, + (short)typeNullable, false, (short)typeSearchable, false, false, false, "ARRAY", 0, 0, + Types.ARRAY, 0, null)); + + return new JdbcThinResultSet(types, Arrays.asList( + new JdbcColumnMeta(null, null, "TYPE_NAME", String.class), + new JdbcColumnMeta(null, null, "DATA_TYPE", Integer.class), + new JdbcColumnMeta(null, null, "PRECISION", Integer.class), + new JdbcColumnMeta(null, null, "LITERAL_PREFIX", String.class), + new JdbcColumnMeta(null, null, "LITERAL_SUFFIX", String.class), + new JdbcColumnMeta(null, null, "CREATE_PARAMS", String.class), + new JdbcColumnMeta(null, null, "NULLABLE", Short.class), + new JdbcColumnMeta(null, null, "CASE_SENSITIVE", Boolean.class), + new JdbcColumnMeta(null, null, "SEARCHABLE", Short.class), + new JdbcColumnMeta(null, null, "UNSIGNED_ATTRIBUTE", Boolean.class), + new JdbcColumnMeta(null, null, "FIXED_PREC_SCALE", Boolean.class), + new JdbcColumnMeta(null, null, "AUTO_INCREMENT", Boolean.class), + new JdbcColumnMeta(null, null, "LOCAL_TYPE_NAME", String.class), + new JdbcColumnMeta(null, null, "MINIMUM_SCALE", Short.class), + new JdbcColumnMeta(null, null, "MAXIMUM_SCALE", Short.class), + new JdbcColumnMeta(null, null, "SQL_DATA_TYPE", Integer.class), + new JdbcColumnMeta(null, null, "SQL_DATETIME_SUB", Integer.class), + new JdbcColumnMeta(null, null, "NUM_PREC_RADIX", Integer.class) + )); + } + + /** {@inheritDoc} */ + @Override public ResultSet getIndexInfo(String catalog, String schema, String tbl, boolean unique, + boolean approximate) throws SQLException { + if (conn.isClosed()) + throw new SQLException("Connection is closed."); + + final List<JdbcColumnMeta> meta = Arrays.asList( + new JdbcColumnMeta(null, null, "TABLE_CAT", String.class), + new JdbcColumnMeta(null, null, "TABLE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "TABLE_NAME", String.class), + new JdbcColumnMeta(null, null, "NON_UNIQUE", Boolean.class), + new JdbcColumnMeta(null, null, "INDEX_QUALIFIER", String.class), + new JdbcColumnMeta(null, null, "INDEX_NAME", String.class), + new JdbcColumnMeta(null, null, "TYPE", Short.class), + new JdbcColumnMeta(null, null, "ORDINAL_POSITION", Short.class), + new JdbcColumnMeta(null, null, "COLUMN_NAME", String.class), + new JdbcColumnMeta(null, null, "ASC_OR_DESC", String.class), + new JdbcColumnMeta(null, null, "CARDINALITY", Integer.class), + new JdbcColumnMeta(null, null, "PAGES", Integer.class), + new JdbcColumnMeta(null, null, "FILTER_CONDITION", String.class)); + + if (!validCatalogPattern(catalog)) + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), meta); + + try { + JdbcMetaIndexesResult res = conn.io().indexMeta(schema, tbl); + + List<List<Object>> rows = new LinkedList<>(); + + for (JdbcIndexMeta idxMeta : res.meta()) + rows.addAll(indexRows(idxMeta)); + + return new JdbcThinResultSet(rows, meta); + } + catch (IOException e) { + conn.close(); + + throw new SQLException("Failed to query Ignite.", e); + } + catch (IgniteCheckedException e) { + throw new SQLException("Failed to query Ignite.", e); + } + } + + /** + * @param idxMeta Index metadata. + * @return List of result rows correspond to index. + */ + private List<List<Object>> indexRows(JdbcIndexMeta idxMeta) { + List<List<Object>> rows = new ArrayList<>(idxMeta.fields().size()); + + for (int i = 0; i < idxMeta.fields().size(); ++i) { + List<Object> row = new ArrayList<>(13); + + row.add((String)null); // table catalog + row.add(idxMeta.schemaName()); + row.add(idxMeta.tableName()); + row.add(true); // non unique + row.add(null); // index qualifier (index catalog) + row.add(idxMeta.indexName()); + row.add((short)tableIndexOther); // type + row.add((Integer)i); // field ordinal position in index + row.add(idxMeta.fields().get(i)); + row.add(idxMeta.fieldsAsc().get(i) ? "A" : "D"); + row.add((Integer)0); // cardinality + row.add((Integer)0); // pages + row.add((String)null); // filer condition + + rows.add(row); + } + + return rows; + } + + /** {@inheritDoc} */ + @Override public boolean supportsResultSetType(int type) throws SQLException { + return type == TYPE_FORWARD_ONLY; + } + + /** {@inheritDoc} */ + @Override public boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException { + return supportsResultSetType(type) && concurrency == CONCUR_READ_ONLY; + } + + /** {@inheritDoc} */ + @Override public boolean ownUpdatesAreVisible(int type) throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean ownDeletesAreVisible(int type) throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean ownInsertsAreVisible(int type) throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean othersUpdatesAreVisible(int type) throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean othersDeletesAreVisible(int type) throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean othersInsertsAreVisible(int type) throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean updatesAreDetected(int type) throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean deletesAreDetected(int type) throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean insertsAreDetected(int type) throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsBatchUpdates() throws SQLException { + return true; + } + + /** {@inheritDoc} */ + @Override public ResultSet getUDTs(String catalog, String schemaPtrn, String typeNamePtrn, + int[] types) throws SQLException { + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "TYPE_CAT", String.class), + new JdbcColumnMeta(null, null, "TYPE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "TYPE_NAME", String.class), + new JdbcColumnMeta(null, null, "CLASS_NAME", String.class), + new JdbcColumnMeta(null, null, "DATA_TYPE", Integer.class), + new JdbcColumnMeta(null, null, "REMARKS", String.class), + new JdbcColumnMeta(null, null, "BASE_TYPE", Short.class) + )); + } + + /** {@inheritDoc} */ + @Override public Connection getConnection() throws SQLException { + return conn; + } + + /** {@inheritDoc} */ + @Override public boolean supportsSavepoints() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsNamedParameters() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsMultipleOpenResults() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsGetGeneratedKeys() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public ResultSet getSuperTypes(String catalog, String schemaPtrn, + String typeNamePtrn) throws SQLException { + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "TYPE_CAT", String.class), + new JdbcColumnMeta(null, null, "TYPE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "TYPE_NAME", String.class), + new JdbcColumnMeta(null, null, "SUPERTYPE_CAT", String.class), + new JdbcColumnMeta(null, null, "SUPERTYPE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "SUPERTYPE_NAME", String.class) + )); + } + + /** {@inheritDoc} */ + @Override public ResultSet getSuperTables(String catalog, String schemaPtrn, + String tblNamePtrn) throws SQLException { + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "TABLE_CAT", String.class), + new JdbcColumnMeta(null, null, "TABLE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "TABLE_NAME", String.class), + new JdbcColumnMeta(null, null, "SUPERTABLE_NAME", String.class) + )); + } + + /** {@inheritDoc} */ + @Override public ResultSet getAttributes(String catalog, String schemaPtrn, String typeNamePtrn, + String attributeNamePtrn) throws SQLException { + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "TYPE_CAT", String.class), + new JdbcColumnMeta(null, null, "TYPE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "TYPE_NAME", String.class), + new JdbcColumnMeta(null, null, "ATTR_NAME", String.class), + new JdbcColumnMeta(null, null, "DATA_TYPE", Integer.class), + new JdbcColumnMeta(null, null, "ATTR_TYPE_NAME", String.class), + new JdbcColumnMeta(null, null, "ATTR_SIZE", Integer.class), + new JdbcColumnMeta(null, null, "DECIMAL_DIGITS", Integer.class), + new JdbcColumnMeta(null, null, "NUM_PREC_RADIX", Integer.class), + new JdbcColumnMeta(null, null, "NULLABLE", Integer.class), + new JdbcColumnMeta(null, null, "REMARKS", String.class), + new JdbcColumnMeta(null, null, "ATTR_DEF", String.class), + new JdbcColumnMeta(null, null, "SQL_DATA_TYPE", Integer.class), + new JdbcColumnMeta(null, null, "SQL_DATETIME_SUB", Integer.class), + new JdbcColumnMeta(null, null, "CHAR_OCTET_LENGTH", Integer.class), + new JdbcColumnMeta(null, null, "ORDINAL_POSITION", Integer.class), + new JdbcColumnMeta(null, null, "IS_NULLABLE", String.class), + new JdbcColumnMeta(null, null, "SCOPE_CATALOG", String.class), + new JdbcColumnMeta(null, null, "SCOPE_SCHEMA", String.class), + new JdbcColumnMeta(null, null, "SCOPE_TABLE", String.class), + new JdbcColumnMeta(null, null, "SOURCE_DATA_TYPE", Short.class) + )); + } + + /** {@inheritDoc} */ + @Override public boolean supportsResultSetHoldability(int holdability) throws SQLException { + return holdability == HOLD_CURSORS_OVER_COMMIT; + } + + /** {@inheritDoc} */ + @Override public int getResultSetHoldability() throws SQLException { + return HOLD_CURSORS_OVER_COMMIT; + } + + /** {@inheritDoc} */ + @Override public int getDatabaseMajorVersion() throws SQLException { + return conn.io().igniteVersion().major(); + } + + /** {@inheritDoc} */ + @Override public int getDatabaseMinorVersion() throws SQLException { + return conn.io().igniteVersion().minor(); + } + + /** {@inheritDoc} */ + @Override public int getJDBCMajorVersion() throws SQLException { + return 4; + } + + /** {@inheritDoc} */ + @Override public int getJDBCMinorVersion() throws SQLException { + return 0; + } + + /** {@inheritDoc} */ + @Override public int getSQLStateType() throws SQLException { + return DatabaseMetaData.sqlStateSQL99; + } + + /** {@inheritDoc} */ + @Override public boolean locatorsUpdateCopy() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean supportsStatementPooling() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public RowIdLifetime getRowIdLifetime() throws SQLException { + return ROWID_UNSUPPORTED; + } + + /** {@inheritDoc} */ + @Override public ResultSet getSchemas(String catalog, String schemaPtrn) throws SQLException { + if (conn.isClosed()) + throw new SQLException("Connection is closed."); + + final List<JdbcColumnMeta> meta = Arrays.asList( + new JdbcColumnMeta(null, null, "TABLE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "TABLE_CATALOG", String.class) + ); + + if (!validCatalogPattern(catalog)) + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), meta); + + try { + JdbcMetaSchemasResult res = conn.io().schemasMeta(schemaPtrn); + + List<List<Object>> rows = new LinkedList<>(); + + for (String schema : res.schemas()) { + List<Object> row = new ArrayList<>(2); + + row.add(schema); + row.add(null); + + rows.add(row); + } + + return new JdbcThinResultSet(rows, meta); + } + catch (IOException e) { + conn.close(); + + throw new SQLException("Failed to query Ignite.", e); + } + catch (IgniteCheckedException e) { + throw new SQLException("Failed to query Ignite.", e); + } + } + + /** {@inheritDoc} */ + @Override public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public boolean autoCommitFailureClosesAllResultSets() throws SQLException { + return false; + } + + /** {@inheritDoc} */ + @Override public ResultSet getClientInfoProperties() throws SQLException { + // TODO: IGNITE-5425. + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "NAME", String.class), + new JdbcColumnMeta(null, null, "MAX_LEN", Integer.class), + new JdbcColumnMeta(null, null, "DEFAULT_VALUE", String.class), + new JdbcColumnMeta(null, null, "DESCRIPTION", String.class) + )); + } + + /** {@inheritDoc} */ + @Override public ResultSet getFunctions(String catalog, String schemaPtrn, + String functionNamePtrn) throws SQLException { + // TODO: IGNITE-6028 + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "FUNCTION_CAT", String.class), + new JdbcColumnMeta(null, null, "FUNCTION_SCHEM", String.class), + new JdbcColumnMeta(null, null, "FUNCTION_NAME", String.class), + new JdbcColumnMeta(null, null, "REMARKS", String.class), + new JdbcColumnMeta(null, null, "FUNCTION_TYPE", String.class), + new JdbcColumnMeta(null, null, "SPECIFIC_NAME", String.class) + )); + } + + /** {@inheritDoc} */ + @Override public ResultSet getFunctionColumns(String catalog, String schemaPtrn, String functionNamePtrn, + String colNamePtrn) throws SQLException { + // TODO: IGNITE-6028 + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "FUNCTION_CAT", String.class), + new JdbcColumnMeta(null, null, "FUNCTION_SCHEM", String.class), + new JdbcColumnMeta(null, null, "FUNCTION_NAME", String.class), + new JdbcColumnMeta(null, null, "COLUMN_NAME", String.class), + new JdbcColumnMeta(null, null, "COLUMN_TYPE", Short.class), + new JdbcColumnMeta(null, null, "DATA_TYPE", Integer.class), + new JdbcColumnMeta(null, null, "TYPE_NAME", String.class), + new JdbcColumnMeta(null, null, "PRECISION", Integer.class), + new JdbcColumnMeta(null, null, "LENGTH", Integer.class), + new JdbcColumnMeta(null, null, "SCALE", Short.class), + new JdbcColumnMeta(null, null, "RADIX", Short.class), + new JdbcColumnMeta(null, null, "NULLABLE", Short.class), + new JdbcColumnMeta(null, null, "REMARKS", String.class), + new JdbcColumnMeta(null, null, "CHAR_OCTET_LENGTH", Integer.class), + new JdbcColumnMeta(null, null, "ORDINAL_POSITION", Integer.class), + new JdbcColumnMeta(null, null, "IS_NULLABLE", String.class), + new JdbcColumnMeta(null, null, "SPECIFIC_NAME", String.class) + )); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override public <T> T unwrap(Class<T> iface) throws SQLException { + if (!isWrapperFor(iface)) + throw new SQLException("Database meta data is not a wrapper for " + iface.getName()); + + return (T)this; + } + + /** {@inheritDoc} */ + @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { + return iface != null && iface.isAssignableFrom(JdbcThinDatabaseMetadata.class); + } + + /** {@inheritDoc} */ + @Override public ResultSet getPseudoColumns(String catalog, String schemaPtrn, String tblNamePtrn, + String colNamePtrn) throws SQLException { + return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList( + new JdbcColumnMeta(null, null, "TABLE_CAT", String.class), + new JdbcColumnMeta(null, null, "TABLE_SCHEM", String.class), + new JdbcColumnMeta(null, null, "TABLE_NAME", String.class), + new JdbcColumnMeta(null, null, "COLUMN_NAME", String.class), + new JdbcColumnMeta(null, null, "DATA_TYPE", Integer.class), + new JdbcColumnMeta(null, null, "COLUMN_SIZE", Integer.class), + new JdbcColumnMeta(null, null, "DECIMAL_DIGITS", Integer.class), + new JdbcColumnMeta(null, null, "NUM_PREC_RADIX", Integer.class), + new JdbcColumnMeta(null, null, "COLUMN_USAGE", Integer.class), + new JdbcColumnMeta(null, null, "REMARKS", String.class), + new JdbcColumnMeta(null, null, "CHAR_OCTET_LENGTH", Integer.class), + new JdbcColumnMeta(null, null, "IS_NULLABLE", String.class) + )); + } + + /** + * @param catalog Catalog pattern. + * @return {@code true} If patter is valid for Ignite (null, empty, or '%' wildcard). + * Otherwise returns {@code false}. + */ + private static boolean validCatalogPattern(String catalog) { + return F.isEmpty(catalog) || "%".equals(catalog); + } + + /** {@inheritDoc} */ + @Override public boolean generatedKeyAlwaysReturned() throws SQLException { + return false; + } +} \ No newline at end of file
