IGNITE-15188 JDBC driver for 3.0: Database metadata (#339)
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/JdbcQueryEventHandler.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/JdbcQueryEventHandler.java
index 5f90229..f2f1750 100644
--- a/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/JdbcQueryEventHandler.java
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/JdbcQueryEventHandler.java
@@ -19,6 +19,14 @@
import org.apache.ignite.client.proto.query.event.BatchExecuteRequest;
import org.apache.ignite.client.proto.query.event.BatchExecuteResult;
+import org.apache.ignite.client.proto.query.event.JdbcMetaColumnsRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaColumnsResult;
+import org.apache.ignite.client.proto.query.event.JdbcMetaPrimaryKeysRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaPrimaryKeysResult;
+import org.apache.ignite.client.proto.query.event.JdbcMetaSchemasRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaSchemasResult;
+import org.apache.ignite.client.proto.query.event.JdbcMetaTablesRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaTablesResult;
import org.apache.ignite.client.proto.query.event.QueryCloseRequest;
import org.apache.ignite.client.proto.query.event.QueryCloseResult;
import org.apache.ignite.client.proto.query.event.QueryExecuteRequest;
@@ -61,4 +69,36 @@
* @return Result.
*/
QueryCloseResult close(QueryCloseRequest req);
+
+ /**
+ * {@link JdbcMetaTablesRequest} command handler.
+ *
+ * @param req Jdbc tables metadata request.
+ * @return Result.
+ */
+ JdbcMetaTablesResult tablesMeta(JdbcMetaTablesRequest req);
+
+ /**
+ * {@link JdbcMetaColumnsRequest} command handler.
+ *
+ * @param req Jdbc columns metadata request.
+ * @return Result.
+ */
+ JdbcMetaColumnsResult columnsMeta(JdbcMetaColumnsRequest req);
+
+ /**
+ * {@link JdbcMetaSchemasRequest} command handler.
+ *
+ * @param req Jdbc schemas metadata request.
+ * @return Result.
+ */
+ JdbcMetaSchemasResult schemasMeta(JdbcMetaSchemasRequest req);
+
+ /**
+ * {@link JdbcMetaPrimaryKeysRequest} command handler.
+ *
+ * @param req Jdbc primary keys metadata request.
+ * @return Result.
+ */
+ JdbcMetaPrimaryKeysResult primaryKeysMeta(JdbcMetaPrimaryKeysRequest req);
}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/ClientMessageUtils.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/ClientMessageUtils.java
new file mode 100644
index 0000000..d836f25
--- /dev/null
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/ClientMessageUtils.java
@@ -0,0 +1,52 @@
+/*
+ * 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.client.proto.query.event;
+
+import org.apache.ignite.internal.client.proto.ClientMessagePacker;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+
+/**
+ * Client message utils class.
+ */
+public class ClientMessageUtils {
+ /**
+ * Packs a string or null if string is null.
+ *
+ * @param packer Message packer.
+ * @param str String to serialize.
+ */
+ public static void writeStringNullable(ClientMessagePacker packer, String str) {
+ if (str == null)
+ packer.packNil();
+ else
+ packer.packString(str);
+ }
+
+ /**
+ * Unpack a string or null if no string is presented.
+ *
+ * @param unpacker Message unpacker.
+ * @return String or null.
+ */
+ public static String readStringNullable(ClientMessageUnpacker unpacker) {
+ if (!unpacker.tryUnpackNil())
+ return unpacker.unpackString();
+
+ return null;
+ }
+}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcColumnMeta.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcColumnMeta.java
new file mode 100644
index 0000000..54ef323
--- /dev/null
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcColumnMeta.java
@@ -0,0 +1,344 @@
+/*
+ * 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.client.proto.query.event;
+
+import java.math.BigDecimal;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.util.Date;
+import java.util.Objects;
+import org.apache.ignite.internal.client.proto.ClientMessagePacker;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+import org.apache.ignite.internal.tostring.S;
+
+import static java.sql.Types.BIGINT;
+import static java.sql.Types.BINARY;
+import static java.sql.Types.BOOLEAN;
+import static java.sql.Types.DATE;
+import static java.sql.Types.DECIMAL;
+import static java.sql.Types.DOUBLE;
+import static java.sql.Types.FLOAT;
+import static java.sql.Types.INTEGER;
+import static java.sql.Types.OTHER;
+import static java.sql.Types.SMALLINT;
+import static java.sql.Types.TIME;
+import static java.sql.Types.TIMESTAMP;
+import static java.sql.Types.TINYINT;
+import static java.sql.Types.VARCHAR;
+
+/**
+ * JDBC column metadata.
+ */
+public class JdbcColumnMeta extends Response {
+ /** Nullable. */
+ private boolean nullable;
+
+ /** Schema name. */
+ private String schemaName;
+
+ /** Table name. */
+ private String tblName;
+
+ /** Column name. */
+ private String colName;
+
+ /** Data type. */
+ private int dataType;
+
+ /** Data type name. */
+ private String dataTypeName;
+
+ /** Precision. */
+ private int precision;
+
+ /** Scale. */
+ private int scale;
+
+ /**
+ * Default constructor is used for serialization.
+ */
+ public JdbcColumnMeta() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param schemaName Schema.
+ * @param tblName Table.
+ * @param colName Column.
+ * @param cls Type.
+ */
+ public JdbcColumnMeta(String schemaName, String tblName, String colName, Class<?> cls) {
+ this(schemaName, tblName, colName, cls, -1, -1, true);
+ }
+
+ /**
+ * Constructor with nullable flag.
+ *
+ * @param schemaName Schema.
+ * @param tblName Table.
+ * @param colName Column.
+ * @param cls Type.
+ * @param nullable Nullable flag.
+ * @param precision Column precision.
+ * @param scale Column scale.
+ */
+ public JdbcColumnMeta(String schemaName, String tblName, String colName, Class<?> cls, int precision, int scale,
+ boolean nullable) {
+ this.schemaName = schemaName;
+ this.tblName = tblName;
+ this.colName = colName;
+ this.nullable = nullable;
+
+ this.dataType = type(cls.getName());
+ this.dataTypeName = typeName(cls.getName());
+ this.precision = precision;
+ this.scale = scale;
+
+ hasResults = true;
+ }
+
+ /**
+ * Gets schema name.
+ *
+ * @return Schema name.
+ */
+ public String schemaName() {
+ return schemaName;
+ }
+
+ /**
+ * Gets table name.
+ *
+ * @return Table name.
+ */
+ public String tableName() {
+ return tblName;
+ }
+
+ /**
+ * Gets column name.
+ *
+ * @return Column name.
+ */
+ public String columnName() {
+ return colName;
+ }
+
+ /**
+ * Gets data type id.
+ *
+ * @return Column's data type.
+ */
+ public int dataType() {
+ return dataType;
+ }
+
+ /**
+ * Gets data type name.
+ *
+ * @return Column's data type name.
+ */
+ public String dataTypeName() {
+ return dataTypeName;
+ }
+
+ /**
+ * Gets default value.
+ *
+ * @return Column's default value.
+ */
+ public String defaultValue() {
+ return null;
+ }
+
+ /**
+ * Gets column precision.
+ *
+ * @return Column's precision.
+ */
+ public int precision() {
+ return precision;
+ }
+
+ /**
+ * Gets column scale.
+ *
+ * @return Column's scale.
+ */
+ public int scale() {
+ return scale;
+ }
+
+ /**
+ * Gets nullable flag.
+ *
+ * @return {@code true} in case the column allows null values. Otherwise returns {@code false}
+ */
+ public boolean isNullable() {
+ return nullable;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeBinary(ClientMessagePacker packer) {
+ super.writeBinary(packer);
+
+ if (!hasResults)
+ return;
+
+ packer.packString(schemaName);
+ packer.packString(tblName);
+ packer.packString(colName);
+
+ packer.packInt(dataType);
+ packer.packString(dataTypeName);
+ packer.packBoolean(nullable);
+ packer.packInt(precision);
+ packer.packInt(scale);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readBinary(ClientMessageUnpacker unpacker) {
+ super.readBinary(unpacker);
+
+ if (!hasResults)
+ return;
+
+ schemaName = unpacker.unpackString();
+ tblName = unpacker.unpackString();
+ colName = unpacker.unpackString();
+
+ dataType = unpacker.unpackInt();
+ dataTypeName = unpacker.unpackString();
+ nullable = unpacker.unpackBoolean();
+ precision = unpacker.unpackInt();
+ scale = unpacker.unpackInt();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ JdbcColumnMeta meta = (JdbcColumnMeta)o;
+ return nullable == meta.nullable
+ && dataType == meta.dataType
+ && precision == meta.precision
+ && scale == meta.scale
+ && Objects.equals(schemaName, meta.schemaName)
+ && Objects.equals(tblName, meta.tblName)
+ && Objects.equals(colName, meta.colName)
+ && Objects.equals(dataTypeName, meta.dataTypeName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ int result = (nullable ? 1 : 0);
+ result = 31 * result + (schemaName != null ? schemaName.hashCode() : 0);
+ result = 31 * result + (tblName != null ? tblName.hashCode() : 0);
+ result = 31 * result + (colName != null ? colName.hashCode() : 0);
+ result = 31 * result + dataType;
+ result = 31 * result + (dataTypeName != null ? dataTypeName.hashCode() : 0);
+ result = 31 * result + precision;
+ result = 31 * result + scale;
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(JdbcColumnMeta.class, this);
+ }
+
+ /**
+ * Converts Java class name to type from {@link Types}.
+ *
+ * @param cls Java class name.
+ * @return Type from {@link Types}.
+ */
+ private static int type(String cls) {
+ if (Boolean.class.getName().equals(cls) || boolean.class.getName().equals(cls))
+ return BOOLEAN;
+ else if (Byte.class.getName().equals(cls) || byte.class.getName().equals(cls))
+ return TINYINT;
+ else if (Short.class.getName().equals(cls) || short.class.getName().equals(cls))
+ return SMALLINT;
+ else if (Integer.class.getName().equals(cls) || int.class.getName().equals(cls))
+ return INTEGER;
+ else if (Long.class.getName().equals(cls) || long.class.getName().equals(cls))
+ return BIGINT;
+ else if (Float.class.getName().equals(cls) || float.class.getName().equals(cls))
+ return FLOAT;
+ else if (Double.class.getName().equals(cls) || double.class.getName().equals(cls))
+ return DOUBLE;
+ else if (String.class.getName().equals(cls))
+ return VARCHAR;
+ else if (byte[].class.getName().equals(cls))
+ return BINARY;
+ else if (Time.class.getName().equals(cls))
+ return TIME;
+ else if (Timestamp.class.getName().equals(cls))
+ return TIMESTAMP;
+ else if (Date.class.getName().equals(cls) || java.sql.Date.class.getName().equals(cls))
+ return DATE;
+ else if (BigDecimal.class.getName().equals(cls))
+ return DECIMAL;
+ else
+ return OTHER;
+ }
+
+ /**
+ * Converts Java class name to SQL type name.
+ *
+ * @param cls Java class name.
+ * @return SQL type name.
+ */
+ private static String typeName(String cls) {
+ if (Boolean.class.getName().equals(cls) || boolean.class.getName().equals(cls))
+ return "BOOLEAN";
+ else if (Byte.class.getName().equals(cls) || byte.class.getName().equals(cls))
+ return "TINYINT";
+ else if (Short.class.getName().equals(cls) || short.class.getName().equals(cls))
+ return "SMALLINT";
+ else if (Integer.class.getName().equals(cls) || int.class.getName().equals(cls))
+ return "INTEGER";
+ else if (Long.class.getName().equals(cls) || long.class.getName().equals(cls))
+ return "BIGINT";
+ else if (Float.class.getName().equals(cls) || float.class.getName().equals(cls))
+ return "FLOAT";
+ else if (Double.class.getName().equals(cls) || double.class.getName().equals(cls))
+ return "DOUBLE";
+ else if (String.class.getName().equals(cls))
+ return "VARCHAR";
+ else if (byte[].class.getName().equals(cls))
+ return "BINARY";
+ else if (Time.class.getName().equals(cls))
+ return "TIME";
+ else if (Timestamp.class.getName().equals(cls))
+ return "TIMESTAMP";
+ else if (Date.class.getName().equals(cls) || java.sql.Date.class.getName().equals(cls))
+ return "DATE";
+ else if (BigDecimal.class.getName().equals(cls))
+ return "DECIMAL";
+ else
+ return "OTHER";
+ }
+}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaColumnsRequest.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaColumnsRequest.java
new file mode 100644
index 0000000..c87f7a6
--- /dev/null
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaColumnsRequest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.client.proto.query.event;
+
+import org.apache.ignite.client.proto.query.ClientMessage;
+import org.apache.ignite.internal.client.proto.ClientMessagePacker;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+import org.apache.ignite.internal.tostring.S;
+
+/**
+ * JDBC get columns metadata request.
+ */
+public class JdbcMetaColumnsRequest implements ClientMessage {
+ /** Schema name pattern. */
+ private String schemaName;
+
+ /** Table name pattern. */
+ private String tblName;
+
+ /** Column name pattern. */
+ private String colName;
+
+ /**
+ * Default constructor is used for deserialization.
+ */
+ public JdbcMetaColumnsRequest() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param schemaName Schema name.
+ * @param tblName Table name.
+ * @param colName Column name.
+ */
+ public JdbcMetaColumnsRequest(String schemaName, String tblName, String colName) {
+ this.schemaName = schemaName;
+ this.tblName = tblName;
+ this.colName = colName;
+ }
+
+ /**
+ * Gets schema name sql pattern.
+ *
+ * @return Schema name pattern.
+ */
+ public String schemaName() {
+ return schemaName;
+ }
+
+ /**
+ * Gets table name sql pattern.
+ *
+ * @return Table name pattern.
+ */
+ public String tableName() {
+ return tblName;
+ }
+
+ /**
+ * Gets column name sql pattern.
+ *
+ * @return Column name pattern.
+ */
+ public String columnName() {
+ return colName;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeBinary(ClientMessagePacker packer) {
+ packer.packString(schemaName);
+ packer.packString(tblName);
+ packer.packString(colName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readBinary(ClientMessageUnpacker unpacker) {
+ schemaName = unpacker.unpackString();
+ tblName = unpacker.unpackString();
+ colName = unpacker.unpackString();
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(JdbcMetaColumnsRequest.class, this);
+ }
+}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaColumnsResult.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaColumnsResult.java
new file mode 100644
index 0000000..fed2195
--- /dev/null
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaColumnsResult.java
@@ -0,0 +1,108 @@
+/*
+ * 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.client.proto.query.event;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.ignite.internal.client.proto.ClientMessagePacker;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+import org.apache.ignite.internal.tostring.S;
+
+/**
+ * JDBC columns metadata result.
+ */
+public class JdbcMetaColumnsResult extends Response {
+ /** Columns metadata. */
+ private List<JdbcColumnMeta> meta;
+
+ /**
+ * Default constructor is used for deserialization.
+ */
+ public JdbcMetaColumnsResult() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param meta Columns metadata.
+ */
+ public JdbcMetaColumnsResult(Collection<JdbcColumnMeta> meta) {
+ Objects.requireNonNull(meta);
+
+ this.meta = new ArrayList<>(meta);
+
+ this.hasResults = true;
+ }
+
+ /**
+ * Gets column metadata.
+ *
+ * @return Columns metadata.
+ */
+ public List<JdbcColumnMeta> meta() {
+ return meta;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeBinary(ClientMessagePacker packer) {
+ super.writeBinary(packer);
+
+ if (!hasResults)
+ return;
+
+ packer.packArrayHeader(meta.size());
+
+ for (JdbcColumnMeta m : meta)
+ m.writeBinary(packer);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readBinary(ClientMessageUnpacker unpacker) {
+ super.readBinary(unpacker);
+
+ if (!hasResults)
+ return;
+
+ int size = unpacker.unpackArrayHeader();
+
+ if (size == 0) {
+ meta = Collections.emptyList();
+
+ return;
+ }
+
+ meta = new ArrayList<>(size);
+
+ for (int i = 0; i < size; ++i) {
+ var m = new JdbcColumnMeta();
+
+ m.readBinary(unpacker);
+
+ meta.add(m);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(JdbcMetaColumnsResult.class, this);
+ }
+}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaPrimaryKeysRequest.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaPrimaryKeysRequest.java
new file mode 100644
index 0000000..3535c96
--- /dev/null
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaPrimaryKeysRequest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.client.proto.query.event;
+
+import org.apache.ignite.client.proto.query.ClientMessage;
+import org.apache.ignite.internal.client.proto.ClientMessagePacker;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+import org.apache.ignite.internal.tostring.S;
+
+/**
+ * JDBC get primary keys metadata request.
+ */
+public class JdbcMetaPrimaryKeysRequest implements ClientMessage {
+ /** Schema name pattern. */
+ private String schemaName;
+
+ /** Table name pattern. */
+ private String tblName;
+
+ /**
+ * Default constructor is used for deserialization.
+ */
+ public JdbcMetaPrimaryKeysRequest() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param schemaName Schema name.
+ * @param tblName Table name.
+ */
+ public JdbcMetaPrimaryKeysRequest(String schemaName, String tblName) {
+ this.schemaName = schemaName;
+ this.tblName = tblName;
+ }
+
+ /**
+ * Gets schema name sql pattern.
+ *
+ * @return Schema name pattern.
+ */
+ public String schemaName() {
+ return schemaName;
+ }
+
+ /**
+ * Gets table name sql pattern.
+ *
+ * @return Table name pattern.
+ */
+ public String tableName() {
+ return tblName;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readBinary(ClientMessageUnpacker unpacker) {
+ schemaName = ClientMessageUtils.readStringNullable(unpacker);
+ tblName = ClientMessageUtils.readStringNullable(unpacker);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeBinary(ClientMessagePacker packer) {
+ ClientMessageUtils.writeStringNullable(packer, schemaName);
+ ClientMessageUtils.writeStringNullable(packer, tblName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(JdbcMetaPrimaryKeysRequest.class, this);
+ }
+}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaPrimaryKeysResult.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaPrimaryKeysResult.java
new file mode 100644
index 0000000..15423b4
--- /dev/null
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaPrimaryKeysResult.java
@@ -0,0 +1,114 @@
+/*
+ * 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.client.proto.query.event;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import org.apache.ignite.internal.client.proto.ClientMessagePacker;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+import org.apache.ignite.internal.tostring.S;
+
+/**
+ * JDBC primary keys metadata result.
+ */
+public class JdbcMetaPrimaryKeysResult extends Response {
+ /** Primary keys meta. */
+ private List<JdbcPrimaryKeyMeta> meta;
+
+ /**
+ * Default constructor is used for deserialization.
+ */
+ public JdbcMetaPrimaryKeysResult() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param meta Column metadata.
+ */
+ public JdbcMetaPrimaryKeysResult(Collection<JdbcPrimaryKeyMeta> meta) {
+ Objects.requireNonNull(meta);
+
+ this.meta = new ArrayList<>(meta);
+
+ this.hasResults = true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeBinary(ClientMessagePacker packer) {
+ super.writeBinary(packer);
+
+ if (!hasResults)
+ return;
+
+ if (meta == null || meta.isEmpty()) {
+ packer.packNil();
+
+ return;
+ }
+
+ packer.packArrayHeader(meta.size());
+
+ for (JdbcPrimaryKeyMeta keyMeta : meta) {
+ keyMeta.writeBinary(packer);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readBinary(ClientMessageUnpacker unpacker) {
+ super.readBinary(unpacker);
+
+ if (!hasResults)
+ return;
+
+ if (unpacker.tryUnpackNil()) {
+ meta = Collections.emptyList();
+
+ return;
+ }
+
+ int size = unpacker.unpackArrayHeader();
+
+ meta = new ArrayList<>(size);
+
+ for (int i = 0; i < size; ++i) {
+ JdbcPrimaryKeyMeta m = new JdbcPrimaryKeyMeta();
+
+ m.readBinary(unpacker);
+
+ meta.add(m);
+ }
+ }
+
+ /**
+ * Gets primary key metadata.
+ *
+ * @return Primary keys metadata.
+ */
+ public List<JdbcPrimaryKeyMeta> meta() {
+ return meta;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(JdbcMetaPrimaryKeysResult.class, this);
+ }
+}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaSchemasRequest.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaSchemasRequest.java
new file mode 100644
index 0000000..de83408
--- /dev/null
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaSchemasRequest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.client.proto.query.event;
+
+import org.apache.ignite.client.proto.query.ClientMessage;
+import org.apache.ignite.internal.client.proto.ClientMessagePacker;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+import org.apache.ignite.internal.tostring.S;
+
+/**
+ * JDBC schemas request.
+ */
+public class JdbcMetaSchemasRequest implements ClientMessage {
+ /** Schema search pattern. */
+ private String schemaName;
+
+ /**
+ * Default constructor is used for deserialization.
+ */
+ public JdbcMetaSchemasRequest() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param schemaName Schema search pattern.
+ */
+ public JdbcMetaSchemasRequest(String schemaName) {
+ this.schemaName = schemaName;
+ }
+
+ /**
+ * Gets schema name sql pattern.
+ *
+ * @return Schema search pattern.
+ */
+ public String schemaName() {
+ return schemaName;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeBinary(ClientMessagePacker packer) {
+ ClientMessageUtils.writeStringNullable(packer, schemaName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readBinary(ClientMessageUnpacker unpacker) {
+ schemaName = ClientMessageUtils.readStringNullable(unpacker);
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(JdbcMetaSchemasRequest.class, this);
+ }
+}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaSchemasResult.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaSchemasResult.java
new file mode 100644
index 0000000..c742462
--- /dev/null
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaSchemasResult.java
@@ -0,0 +1,94 @@
+/*
+ * 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.client.proto.query.event;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Objects;
+import org.apache.ignite.internal.client.proto.ClientMessagePacker;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+import org.apache.ignite.internal.tostring.S;
+
+/**
+ * JDBC schemas result.
+ */
+public class JdbcMetaSchemasResult extends Response {
+ /** Found schemas. */
+ private Collection<String> schemas;
+
+ /**
+ * Default constructor is used for deserialization.
+ */
+ public JdbcMetaSchemasResult() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param schemas Found schemas.
+ */
+ public JdbcMetaSchemasResult(Collection<String> schemas) {
+ Objects.requireNonNull(schemas);
+
+ this.schemas = schemas;
+
+ this.hasResults = true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeBinary(ClientMessagePacker packer) {
+ super.writeBinary(packer);
+
+ if (!hasResults)
+ return;
+
+ packer.packArrayHeader(schemas.size());
+
+ for (String schema : schemas)
+ packer.packString(schema);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readBinary(ClientMessageUnpacker unpacker) {
+ super.readBinary(unpacker);
+
+ if (!hasResults)
+ return;
+
+ int size = unpacker.unpackArrayHeader();
+
+ schemas = new ArrayList<>(size);
+
+ for (int i = 0; i < size; i++)
+ schemas.add(unpacker.unpackString());
+ }
+
+ /**
+ * Gets found table schemas.
+ *
+ * @return Found schemas.
+ */
+ public Collection<String> schemas() {
+ return schemas;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(JdbcMetaSchemasResult.class, this);
+ }
+}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaTablesRequest.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaTablesRequest.java
new file mode 100644
index 0000000..4182954
--- /dev/null
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaTablesRequest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.client.proto.query.event;
+
+import org.apache.ignite.client.proto.query.ClientMessage;
+import org.apache.ignite.internal.client.proto.ClientMessagePacker;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+import org.apache.ignite.internal.tostring.S;
+
+/**
+ * JDBC tables metadata request.
+ */
+public class JdbcMetaTablesRequest implements ClientMessage {
+ /** Schema search pattern. */
+ private String schemaName;
+
+ /** Table search pattern. */
+ private String tblName;
+
+ /** Table types. */
+ private String[] tblTypes;
+
+ /**
+ * Default constructor is used for deserialization.
+ */
+ public JdbcMetaTablesRequest() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param schemaName Schema search pattern.
+ * @param tblName Table search pattern.
+ * @param tblTypes Table types.
+ */
+ public JdbcMetaTablesRequest(String schemaName, String tblName, String[] tblTypes) {
+ this.schemaName = schemaName;
+ this.tblName = tblName;
+ this.tblTypes = tblTypes;
+ }
+
+ /**
+ * Gets schema name pattern.
+ *
+ * @return Schema search pattern.
+ */
+ public String schemaName() {
+ return schemaName;
+ }
+
+ /**
+ * Gets table name pattern.
+ *
+ * @return Table search pattern.
+ */
+ public String tableName() {
+ return tblName;
+ }
+
+ /**
+ * Gets allowed table types.
+ *
+ * @return Table types.
+ */
+ public String[] tableTypes() {
+ return tblTypes;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeBinary(ClientMessagePacker packer) {
+ ClientMessageUtils.writeStringNullable(packer, schemaName);
+ ClientMessageUtils.writeStringNullable(packer, tblName);
+
+ if (tblTypes == null) {
+ packer.packNil();
+
+ return;
+ }
+
+ packer.packArrayHeader(tblTypes.length);
+
+ for (String type : tblTypes)
+ packer.packString(type);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readBinary(ClientMessageUnpacker unpacker) {
+ schemaName = ClientMessageUtils.readStringNullable(unpacker);
+ tblName = ClientMessageUtils.readStringNullable(unpacker);
+
+ if (unpacker.tryUnpackNil())
+ return;
+
+ int size = unpacker.unpackArrayHeader();
+
+ tblTypes = new String[size];
+
+ for (int i = 0; i < size; i++)
+ tblTypes[i] = unpacker.unpackString();
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(JdbcMetaTablesRequest.class, this);
+ }
+}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaTablesResult.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaTablesResult.java
new file mode 100644
index 0000000..2fa57ff
--- /dev/null
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcMetaTablesResult.java
@@ -0,0 +1,99 @@
+/*
+ * 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.client.proto.query.event;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import org.apache.ignite.internal.client.proto.ClientMessagePacker;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+import org.apache.ignite.internal.tostring.S;
+
+/**
+ * JDBC tables metadata result.
+ */
+public class JdbcMetaTablesResult extends Response {
+ /** Tables metadata. */
+ private List<JdbcTableMeta> meta;
+
+ /**
+ * Default constructor is used for deserialization.
+ */
+ public JdbcMetaTablesResult() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param meta Tables metadata.
+ */
+ public JdbcMetaTablesResult(List<JdbcTableMeta> meta) {
+ Objects.requireNonNull(meta);
+
+ this.meta = meta;
+
+ this.hasResults = true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeBinary(ClientMessagePacker packer) {
+ super.writeBinary(packer);
+
+ if (!hasResults)
+ return;
+
+ packer.packArrayHeader(meta.size());
+
+ for (JdbcTableMeta m : meta)
+ m.writeBinary(packer);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readBinary(ClientMessageUnpacker unpacker) {
+ super.readBinary(unpacker);
+
+ if (!hasResults)
+ return;
+
+ int size = unpacker.unpackArrayHeader();
+
+ meta = new ArrayList<>(size);
+
+ for (int i = 0; i < size; ++i) {
+ JdbcTableMeta m = new JdbcTableMeta();
+
+ m.readBinary(unpacker);
+
+ meta.add(m);
+ }
+ }
+
+ /**
+ * Gets table metadata.
+ *
+ * @return Tables metadata.
+ */
+ public List<JdbcTableMeta> meta() {
+ return meta;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(JdbcMetaTablesResult.class, this);
+ }
+}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcPrimaryKeyMeta.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcPrimaryKeyMeta.java
new file mode 100644
index 0000000..2c85c63
--- /dev/null
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcPrimaryKeyMeta.java
@@ -0,0 +1,167 @@
+/*
+ * 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.client.proto.query.event;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import org.apache.ignite.client.proto.query.ClientMessage;
+import org.apache.ignite.internal.client.proto.ClientMessagePacker;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+import org.apache.ignite.internal.tostring.S;
+
+/**
+ * JDBC primary key metadata.
+ */
+public class JdbcPrimaryKeyMeta implements ClientMessage {
+ /** Schema name. */
+ private String schemaName;
+
+ /** Table name. */
+ private String tblName;
+
+ /** Primary key name. */
+ private String name;
+
+ /** Primary key fields. */
+ private List<String> fields;
+
+ /**
+ * Default constructor is used for binary serialization.
+ */
+ public JdbcPrimaryKeyMeta() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param schemaName Schema.
+ * @param tblName Table.
+ * @param name Primary key name.
+ * @param fields Primary key fields.
+ */
+ public JdbcPrimaryKeyMeta(String schemaName, String tblName, String name, List<String> fields) {
+ this.schemaName = schemaName;
+ this.tblName = tblName;
+ this.name = name;
+ this.fields = fields;
+ }
+
+ /**
+ * Gets schema name.
+ *
+ * @return Schema name.
+ */
+ public String schemaName() {
+ return schemaName;
+ }
+
+ /**
+ * Gets table name.
+ *
+ * @return Table name.
+ */
+ public String tableName() {
+ return tblName;
+ }
+
+ /**
+ * Gets primary key name.
+ *
+ * @return Primary key name.
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Gets key field names.
+ *
+ * @return Key fields.
+ */
+ public List<String> fields() {
+ return fields;
+ }
+
+
+ /** {@inheritDoc} */
+ @Override public void writeBinary(ClientMessagePacker packer) {
+ packer.packString(schemaName);
+ packer.packString(tblName);
+ packer.packString(name);
+
+ if (fields == null || fields.isEmpty())
+ packer.packNil();
+
+ packer.packArrayHeader(fields.size());
+
+ for (String field : fields) {
+ packer.packString(field);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readBinary(ClientMessageUnpacker unpacker) {
+ schemaName = unpacker.unpackString();
+ tblName = unpacker.unpackString();
+ name = unpacker.unpackString();
+
+ if (unpacker.tryUnpackNil()) {
+ fields = Collections.emptyList();
+
+ return;
+ }
+
+ int size = unpacker.unpackArrayHeader();
+
+ fields = new ArrayList<>(size);
+
+ for (int i = 0; i < size; i++)
+ fields.add(unpacker.unpackString());
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ JdbcPrimaryKeyMeta meta = (JdbcPrimaryKeyMeta)o;
+
+ return Objects.equals(schemaName, meta.schemaName)
+ && Objects.equals(tblName, meta.tblName)
+ && Objects.equals(name, meta.name)
+ && Objects.equals(fields, meta.fields);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ int result = schemaName.hashCode();
+ result = 31 * result + tblName.hashCode();
+ result = 31 * result + name.hashCode();
+ result = 31 * result + (fields != null ? fields.hashCode() : 0);
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(JdbcPrimaryKeyMeta.class, this);
+ }
+}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcTableMeta.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcTableMeta.java
new file mode 100644
index 0000000..06c7557
--- /dev/null
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/query/event/JdbcTableMeta.java
@@ -0,0 +1,138 @@
+/*
+ * 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.client.proto.query.event;
+
+import java.util.Objects;
+
+import org.apache.ignite.internal.client.proto.ClientMessagePacker;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+import org.apache.ignite.internal.tostring.S;
+
+/**
+ * JDBC table metadata.
+ */
+public class JdbcTableMeta extends Response {
+ /** Schema name. */
+ private String schemaName;
+
+ /** Table name. */
+ private String tblName;
+
+ /** Table type. */
+ private String tblType;
+
+ /**
+ * Default constructor is used for deserialization.
+ */
+ public JdbcTableMeta() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param schemaName Schema name.
+ * @param tblName Table name.
+ * @param tblType Table type.
+ */
+ public JdbcTableMeta(String schemaName, String tblName, String tblType) {
+ this.schemaName = schemaName;
+ this.tblName = tblName;
+ this.tblType = tblType;
+
+ this.hasResults = true;
+ }
+
+ /**
+ * Gets schema name.
+ *
+ * @return Schema name.
+ */
+ public String schemaName() {
+ return schemaName;
+ }
+
+ /**
+ * Gets table name.
+ *
+ * @return Table name.
+ */
+ public String tableName() {
+ return tblName;
+ }
+
+ /**
+ * Gets table type.
+ *
+ * @return Table type.
+ */
+ public String tableType() {
+ return tblType;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeBinary(ClientMessagePacker packer) {
+ super.writeBinary(packer);
+
+ if (!hasResults)
+ return;
+
+ packer.packString(schemaName);
+ packer.packString(tblName);
+ packer.packString(tblType);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readBinary(ClientMessageUnpacker unpacker) {
+ super.readBinary(unpacker);
+
+ if (!hasResults)
+ return;
+
+ schemaName = unpacker.unpackString();
+ tblName = unpacker.unpackString();
+ tblType = unpacker.unpackString();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ JdbcTableMeta meta = (JdbcTableMeta)o;
+
+ return Objects.equals(schemaName, meta.schemaName)
+ && Objects.equals(tblName, meta.tblName)
+ && Objects.equals(tblType, meta.tblType);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ int result = schemaName.hashCode();
+ result = 31 * result + tblName.hashCode();
+ result = 31 * result + tblType.hashCode();
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(JdbcTableMeta.class, this);
+ }
+}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientOp.java b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientOp.java
index fb23495..87b44ad 100644
--- a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientOp.java
+++ b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientOp.java
@@ -119,4 +119,16 @@
/** Close query cursor. */
public static final int SQL_CURSOR_CLOSE = 37;
+
+ /** Get table metadata. */
+ public static final int SQL_TABLE_META = 38;
+
+ /** Get column metadata. */
+ public static final int SQL_COLUMN_META = 39;
+
+ /** Get schemas list. */
+ public static final int SQL_SCHEMAS_META = 40;
+
+ /** Get primary key metadata. */
+ public static final int SQL_PK_META = 41;
}
diff --git a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/QueryEventHandlerTest.java b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/QueryEventHandlerTest.java
deleted file mode 100644
index d74bc1f..0000000
--- a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/QueryEventHandlerTest.java
+++ /dev/null
@@ -1,300 +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.client.handler;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import org.apache.ignite.client.proto.query.JdbcQueryEventHandler;
-import org.apache.ignite.client.proto.query.event.BatchExecuteRequest;
-import org.apache.ignite.client.proto.query.event.BatchExecuteResult;
-import org.apache.ignite.client.proto.query.event.Query;
-import org.apache.ignite.client.proto.query.event.QueryCloseRequest;
-import org.apache.ignite.client.proto.query.event.QueryCloseResult;
-import org.apache.ignite.client.proto.query.event.QueryExecuteRequest;
-import org.apache.ignite.client.proto.query.event.QueryExecuteResult;
-import org.apache.ignite.client.proto.query.event.QueryFetchRequest;
-import org.apache.ignite.client.proto.query.event.QueryFetchResult;
-import org.apache.ignite.client.proto.query.event.QuerySingleResult;
-import org.apache.ignite.internal.processors.query.calcite.QueryProcessor;
-import org.apache.ignite.internal.processors.query.calcite.SqlCursor;
-import org.apache.ignite.internal.processors.query.calcite.SqlQueryType;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-
-import static org.apache.ignite.client.proto.query.IgniteQueryErrorCode.UNSUPPORTED_OPERATION;
-import static org.apache.ignite.client.proto.query.event.Response.STATUS_FAILED;
-import static org.apache.ignite.client.proto.query.event.Response.STATUS_SUCCESS;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.when;
-
-/**
- * Test QueryEventHandler implementation with various request types.
- * */
-@ExtendWith(MockitoExtension.class)
-public class QueryEventHandlerTest {
- /**
- * Mocked query processor.
- */
- @Mock
- private QueryProcessor processor;
-
- /**
- * Mocked sql cursor.
- */
- @Mock
- private SqlCursor<List<?>> cursor;
-
- /**
- * Test multiple select query request.
- */
- @Test
- public void testSelectQueryRequestMultipleStatements() {
- int cursorSize = 10;
-
- JdbcQueryEventHandler hnd = prepareHandlerForMultiState(cursorSize);
-
- QueryExecuteRequest qryReq = getJdbcQueryExecuteRequest(10);
-
- QueryExecuteResult res = hnd.query(qryReq);
-
- assertEquals(res.status(), STATUS_SUCCESS);
- assertNull(res.err());
-
- assertEquals(res.results().size(), cursorSize);
-
- for (int i = 0; i < res.results().size(); i++) {
- QuerySingleResult singleRes = res.results().get(i);
- assertTrue(singleRes.isQuery());
- assertFalse(singleRes.last());
- assertEquals(singleRes.cursorId(), i);
-
- assertEquals(singleRes.items().size(), 10);
- assertEquals(singleRes.items().get(0).size(), 1);
-
- assertEquals(singleRes.items().get(0).get(0), "42");
- }
- }
-
- /**
- * Prepare cursors and processor for multiple select query request.
- *
- * @param cursorSize Size of the cursors array.
- * @return Query event handler.
- */
- private JdbcQueryEventHandler prepareHandlerForMultiState(int cursorSize) {
- when(cursor.getQueryType()).thenReturn(SqlQueryType.QUERY);
- when(cursor.hasNext()).thenReturn(true);
-
- doReturn(Collections.singletonList("42")).when(cursor).next();
-
- List<SqlCursor<List<?>>> cursors = new ArrayList<>(cursorSize);
-
- for (int i = 0; i < cursorSize; i++)
- cursors.add(cursor);
-
- when(processor.query(anyString(), anyString(), any())).thenReturn(cursors);
-
- return new JdbcQueryEventHandlerImpl(processor);
- }
-
- /**
- * Test fetch query request.
- */
- @Test
- public void testFetchQueryRequest() {
- JdbcQueryEventHandler hnd = getHandler(SqlQueryType.QUERY, "42");
-
- QueryExecuteRequest qryReq = getJdbcQueryExecuteRequest(10);
-
- QueryExecuteResult qryRes = hnd.query(qryReq);
-
- var fetchReq = new QueryFetchRequest(qryRes.results().get(0).cursorId(), 10);
-
- QueryFetchResult fetchRes = hnd.fetch(fetchReq);
-
- assertEquals(fetchRes.status(), STATUS_SUCCESS);
- assertNull(fetchRes.err());
-
- assertEquals(fetchRes.items().size(), 10);
-
- assertEquals(fetchRes.items().get(0).get(0), "42");
- }
-
- /**
- * Test dml query request.
- */
- @Test
- public void testDMLQuery() {
- JdbcQueryEventHandler hnd = getHandler(SqlQueryType.DML, 1L);
-
- when(cursor.hasNext()).thenReturn(true).thenReturn(false);
-
- QueryExecuteRequest qryReq = getJdbcQueryExecuteRequest(10);
-
- QueryExecuteResult res = hnd.query(qryReq);
-
- assertEquals(res.status(), STATUS_SUCCESS);
- assertNull(res.err());
-
- assertEquals(res.results().size(), 1);
-
- QuerySingleResult singleRes = res.results().get(0);
-
- assertEquals(singleRes.updateCount(), 1L);
- assertFalse(singleRes.isQuery());
- assertTrue(singleRes.last());
- }
-
- /**
- * Test batch query request.
- */
- @Test
- public void testBatchQuery() {
- JdbcQueryEventHandler hnd = new JdbcQueryEventHandlerImpl(processor);
-
- var req = new BatchExecuteRequest(
- "PUBLIC",
- Collections.singletonList(new Query("INSERT INTRO test VALUES (1);", null)),
- false);
-
- BatchExecuteResult batch = hnd.batch(req);
-
- assertEquals(batch.status(), UNSUPPORTED_OPERATION);
- assertNotNull(batch.err());
- }
-
- /**
- * Test error cases for select query request.
- */
- @Test
- public void testSelectQueryBadRequest() {
- JdbcQueryEventHandler hnd = new JdbcQueryEventHandlerImpl(processor);
-
- QueryExecuteRequest qryReq = getJdbcQueryExecuteRequest(10);
-
- QueryExecuteResult res1 = hnd.query(qryReq);
-
- assertEquals(res1.status(), STATUS_FAILED);
- assertNotNull(res1.err());
-
- QueryExecuteRequest req2 = getJdbcQueryExecuteRequest(10);
-
- when(processor.query(anyString(), anyString(), any())).thenReturn(Collections.emptyList());
-
- QueryExecuteResult res2 = hnd.query(req2);
-
- assertEquals(res2.status(), STATUS_FAILED);
- assertNotNull(res2.err());
-
- when(cursor.hasNext()).thenReturn(true);
- when(cursor.next()).thenThrow(RuntimeException.class);
- when(processor.query(anyString(), anyString(), any())).thenReturn(Collections.singletonList(cursor));
-
- QueryExecuteResult res3 = hnd.query(req2);
-
- assertEquals(res3.status(), STATUS_FAILED);
- assertNotNull(res3.err());
- }
-
- /**
- * Test error cases for fetch query request.
- */
- @Test
- public void testFetchQueryBadRequests() {
- JdbcQueryEventHandler hnd = getHandler(SqlQueryType.QUERY, "42");
-
- QueryExecuteRequest qryReq = getJdbcQueryExecuteRequest(1);
-
- QueryExecuteResult qryRes = hnd.query(qryReq);
-
- var fetchReq = new QueryFetchRequest(qryRes.results().get(0).cursorId(), -1);
-
- QueryFetchResult fetchRes = hnd.fetch(fetchReq);
-
- assertEquals(fetchRes.status(), STATUS_FAILED);
- assertNotNull(fetchRes.err());
-
- fetchReq = new QueryFetchRequest(Integer.MAX_VALUE, 1);
-
- fetchRes = hnd.fetch(fetchReq);
-
- assertEquals(fetchRes.status(), STATUS_FAILED);
- assertNotNull(fetchRes.err());
- }
-
- /**
- * Test close cursor request.
- */
- @Test
- public void testCloseRequest() {
- JdbcQueryEventHandler hnd = getHandler(SqlQueryType.QUERY, "42");
-
- QueryExecuteRequest qryReq = getJdbcQueryExecuteRequest(1);
-
- QueryExecuteResult qryRes = hnd.query(qryReq);
-
- var closeReq = new QueryCloseRequest(qryRes.results().get(0).cursorId());
-
- QueryCloseResult closeRes = hnd.close(closeReq);
-
- assertEquals(closeRes.status(), STATUS_SUCCESS);
-
- closeRes = hnd.close(closeReq);
-
- assertEquals(closeRes.status(), STATUS_FAILED);
- assertNotNull(closeRes.err());
- }
-
- /**
- * Prepare getJdbcQueryExecuteRequest.
- *
- * @param pageSize Size of result set in response.
- * @return JdbcQueryExecuteRequest.
- */
- private QueryExecuteRequest getJdbcQueryExecuteRequest(int pageSize) {
- return new QueryExecuteRequest("PUBLIC", pageSize, 3, "SELECT * FROM Test;", null);
- }
-
- /**
- * Prepare cursor and processor for multiple select query request.
- *
- * @param type Expected sql query type.
- * @param val Value in result set.
- * @return Query event handler.
- */
- private JdbcQueryEventHandler getHandler(SqlQueryType type, Object val) {
- when(cursor.getQueryType()).thenReturn(type);
- when(cursor.hasNext()).thenReturn(true);
-
- doReturn(Collections.singletonList(val)).when(cursor).next();
-
- when(processor.query(anyString(), anyString(), any())).thenReturn(Collections.singletonList(cursor));
-
- return new JdbcQueryEventHandlerImpl(processor);
- }
-}
diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
index d6d22e3..47173c8 100644
--- a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
+++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
@@ -25,9 +25,14 @@
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.apache.ignite.client.handler.requests.sql.ClientSqlCloseRequest;
+import org.apache.ignite.client.handler.requests.sql.ClientSqlColumnMetadataRequest;
import org.apache.ignite.client.handler.requests.sql.ClientSqlExecuteBatchRequest;
import org.apache.ignite.client.handler.requests.sql.ClientSqlExecuteRequest;
import org.apache.ignite.client.handler.requests.sql.ClientSqlFetchRequest;
+import org.apache.ignite.client.handler.requests.sql.ClientSqlPrimaryKeyMetadataRequest;
+import org.apache.ignite.client.handler.requests.sql.ClientSqlSchemasMetadataRequest;
+import org.apache.ignite.client.handler.requests.sql.ClientSqlTableMetadataRequest;
+import org.apache.ignite.client.handler.requests.sql.JdbcMetadataCatalog;
import org.apache.ignite.client.handler.requests.table.ClientSchemasGetRequest;
import org.apache.ignite.client.handler.requests.table.ClientTableDropRequest;
import org.apache.ignite.client.handler.requests.table.ClientTableGetRequest;
@@ -98,7 +103,7 @@
this.igniteTables = igniteTables;
- this.handler = new JdbcQueryEventHandlerImpl(processor);
+ this.handler = new JdbcQueryEventHandlerImpl(processor, new JdbcMetadataCatalog(igniteTables));
}
/** {@inheritDoc} */
@@ -351,6 +356,18 @@
case ClientOp.SQL_CURSOR_CLOSE:
return ClientSqlCloseRequest.process(in, out, handler);
+ case ClientOp.SQL_TABLE_META:
+ return ClientSqlTableMetadataRequest.process(in, out, handler);
+
+ case ClientOp.SQL_COLUMN_META:
+ return ClientSqlColumnMetadataRequest.process(in, out, handler);
+
+ case ClientOp.SQL_SCHEMAS_META:
+ return ClientSqlSchemasMetadataRequest.process(in, out, handler);
+
+ case ClientOp.SQL_PK_META:
+ return ClientSqlPrimaryKeyMetadataRequest.process(in, out, handler);
+
default:
throw new IgniteException("Unexpected operation code: " + opCode);
}
diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImpl.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImpl.java
index 782d385..c6001ea 100644
--- a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImpl.java
+++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImpl.java
@@ -20,12 +20,25 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
+import org.apache.ignite.client.handler.requests.sql.JdbcMetadataCatalog;
import org.apache.ignite.client.proto.query.JdbcQueryEventHandler;
import org.apache.ignite.client.proto.query.event.BatchExecuteRequest;
import org.apache.ignite.client.proto.query.event.BatchExecuteResult;
+import org.apache.ignite.client.proto.query.event.JdbcColumnMeta;
+import org.apache.ignite.client.proto.query.event.JdbcMetaColumnsRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaColumnsResult;
+import org.apache.ignite.client.proto.query.event.JdbcMetaPrimaryKeysRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaPrimaryKeysResult;
+import org.apache.ignite.client.proto.query.event.JdbcMetaSchemasRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaSchemasResult;
+import org.apache.ignite.client.proto.query.event.JdbcMetaTablesRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaTablesResult;
+import org.apache.ignite.client.proto.query.event.JdbcPrimaryKeyMeta;
+import org.apache.ignite.client.proto.query.event.JdbcTableMeta;
import org.apache.ignite.client.proto.query.event.QueryCloseRequest;
import org.apache.ignite.client.proto.query.event.QueryCloseResult;
import org.apache.ignite.client.proto.query.event.QueryExecuteRequest;
@@ -53,13 +66,18 @@
/** Sql query processor. */
private final QueryProcessor processor;
+ /** Jdbc metadata info. */
+ private final JdbcMetadataCatalog meta;
+
/**
* Constructor.
*
* @param processor Processor.
+ * @param meta JdbcMetadataInfo.
*/
- public JdbcQueryEventHandlerImpl(QueryProcessor processor) {
+ public JdbcQueryEventHandlerImpl(QueryProcessor processor, JdbcMetadataCatalog meta) {
this.processor = processor;
+ this.meta = meta;
}
/** {@inheritDoc} */
@@ -154,6 +172,34 @@
return new QueryCloseResult();
}
+ /** {@inheritDoc} */
+ @Override public JdbcMetaTablesResult tablesMeta(JdbcMetaTablesRequest req) {
+ List<JdbcTableMeta> tblsMeta = meta.getTablesMeta(req.schemaName(), req.tableName(), req.tableTypes());
+
+ return new JdbcMetaTablesResult(tblsMeta);
+ }
+
+ /** {@inheritDoc} */
+ @Override public JdbcMetaColumnsResult columnsMeta(JdbcMetaColumnsRequest req) {
+ Collection<JdbcColumnMeta> tblsMeta = meta.getColumnsMeta(req.schemaName(), req.tableName(), req.columnName());
+
+ return new JdbcMetaColumnsResult(tblsMeta);
+ }
+
+ /** {@inheritDoc} */
+ @Override public JdbcMetaSchemasResult schemasMeta(JdbcMetaSchemasRequest req) {
+ Collection<String> tblsMeta = meta.getSchemasMeta(req.schemaName());
+
+ return new JdbcMetaSchemasResult(tblsMeta);
+ }
+
+ /** {@inheritDoc} */
+ @Override public JdbcMetaPrimaryKeysResult primaryKeysMeta(JdbcMetaPrimaryKeysRequest req) {
+ Collection<JdbcPrimaryKeyMeta> tblsMeta = meta.getPrimaryKeys(req.schemaName(), req.tableName());
+
+ return new JdbcMetaPrimaryKeysResult(tblsMeta);
+ }
+
/**
* Serializes the stack trace of given exception for further sending to the client.
*
diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlColumnMetadataRequest.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlColumnMetadataRequest.java
new file mode 100644
index 0000000..a245162
--- /dev/null
+++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlColumnMetadataRequest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.client.handler.requests.sql;
+
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.client.proto.query.JdbcQueryEventHandler;
+import org.apache.ignite.client.proto.query.event.JdbcMetaColumnsRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaColumnsResult;
+import org.apache.ignite.internal.client.proto.ClientMessagePacker;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+
+/**
+ * Client sql column metadata request handler.
+ */
+public class ClientSqlColumnMetadataRequest {
+ /**
+ * Processes remote {@code JdbcMetaColumnsRequest}.
+ *
+ * @param in Client message unpacker.
+ * @param out Client message packer.
+ * @param handler Query event handler.
+ * @return null value indicates synchronous operation.
+ */
+ public static CompletableFuture<Void> process(
+ ClientMessageUnpacker in,
+ ClientMessagePacker out,
+ JdbcQueryEventHandler handler
+ ) {
+ var req = new JdbcMetaColumnsRequest();
+
+ req.readBinary(in);
+
+ JdbcMetaColumnsResult res = handler.columnsMeta(req);
+
+ res.writeBinary(out);
+
+ return null;
+ }
+}
diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlPrimaryKeyMetadataRequest.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlPrimaryKeyMetadataRequest.java
new file mode 100644
index 0000000..d2774bc
--- /dev/null
+++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlPrimaryKeyMetadataRequest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.client.handler.requests.sql;
+
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.client.proto.query.JdbcQueryEventHandler;
+import org.apache.ignite.client.proto.query.event.JdbcMetaPrimaryKeysRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaPrimaryKeysResult;
+import org.apache.ignite.internal.client.proto.ClientMessagePacker;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+
+/**
+ * Client sql primary key metadata request handler.
+ */
+public class ClientSqlPrimaryKeyMetadataRequest {
+ /**
+ * Processes remote {@code JdbcMetaPrimaryKeysRequest}.
+ *
+ * @param in Client message unpacker.
+ * @param out Client message packer.
+ * @param handler Query event handler.
+ * @return null value indicates synchronous operation.
+ */
+ public static CompletableFuture<Void> process(
+ ClientMessageUnpacker in,
+ ClientMessagePacker out,
+ JdbcQueryEventHandler handler
+ ) {
+ var req = new JdbcMetaPrimaryKeysRequest();
+
+ req.readBinary(in);
+
+ JdbcMetaPrimaryKeysResult res = handler.primaryKeysMeta(req);
+
+ res.writeBinary(out);
+
+ return null;
+ }
+}
diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlSchemasMetadataRequest.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlSchemasMetadataRequest.java
new file mode 100644
index 0000000..12090c4
--- /dev/null
+++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlSchemasMetadataRequest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.client.handler.requests.sql;
+
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.client.proto.query.JdbcQueryEventHandler;
+import org.apache.ignite.client.proto.query.event.JdbcMetaSchemasRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaSchemasResult;
+import org.apache.ignite.internal.client.proto.ClientMessagePacker;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+
+/**
+ * Client sql schema metadata request handler.
+ */
+public class ClientSqlSchemasMetadataRequest {
+ /**
+ * Processes remote {@code JdbcMetaSchemasRequest}.
+ *
+ * @param in Client message unpacker.
+ * @param out Client message packer.
+ * @param handler Query event handler.
+ * @return null value indicates synchronous operation.
+ */
+ public static CompletableFuture<Void> process(
+ ClientMessageUnpacker in,
+ ClientMessagePacker out,
+ JdbcQueryEventHandler handler
+ ) {
+ var req = new JdbcMetaSchemasRequest();
+
+ req.readBinary(in);
+
+ JdbcMetaSchemasResult res = handler.schemasMeta(req);
+
+ res.writeBinary(out);
+
+ return null;
+ }
+}
diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlTableMetadataRequest.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlTableMetadataRequest.java
new file mode 100644
index 0000000..c072889
--- /dev/null
+++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlTableMetadataRequest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.client.handler.requests.sql;
+
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.client.proto.query.JdbcQueryEventHandler;
+import org.apache.ignite.client.proto.query.event.JdbcMetaTablesRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaTablesResult;
+import org.apache.ignite.internal.client.proto.ClientMessagePacker;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+
+/**
+ * Client sql table metadata request handler.
+ */
+public class ClientSqlTableMetadataRequest {
+ /**
+ * Processes remote {@code JdbcMetaTablesRequest}.
+ *
+ * @param in Client message unpacker.
+ * @param out Client message packer.
+ * @param handler Query event handler.
+ * @return null value indicates synchronous operation.
+ */
+ public static CompletableFuture<Void> process(
+ ClientMessageUnpacker in,
+ ClientMessagePacker out,
+ JdbcQueryEventHandler handler
+ ) {
+ var req = new JdbcMetaTablesRequest();
+
+ req.readBinary(in);
+
+ JdbcMetaTablesResult res = handler.tablesMeta(req);
+
+ res.writeBinary(out);
+
+ return null;
+ }
+}
diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/JdbcMetadataCatalog.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/JdbcMetadataCatalog.java
new file mode 100644
index 0000000..f66f3fb
--- /dev/null
+++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/JdbcMetadataCatalog.java
@@ -0,0 +1,335 @@
+/*
+ * 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.client.handler.requests.sql;
+
+import java.sql.DatabaseMetaData;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.ignite.client.proto.query.event.JdbcColumnMeta;
+import org.apache.ignite.client.proto.query.event.JdbcPrimaryKeyMeta;
+import org.apache.ignite.client.proto.query.event.JdbcTableMeta;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.DecimalNativeType;
+import org.apache.ignite.internal.schema.NativeType;
+import org.apache.ignite.internal.schema.NativeTypeSpec;
+import org.apache.ignite.internal.schema.NumberNativeType;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.SchemaRegistry;
+import org.apache.ignite.internal.table.TableImpl;
+import org.apache.ignite.internal.util.Pair;
+import org.apache.ignite.table.Table;
+import org.apache.ignite.table.manager.IgniteTables;
+
+//TODO IGNITE-15525 Filter by table type must be added after 'view' type will appear.
+/**
+ * Facade over {@link IgniteTables} to get information about database entities in terms of JDBC.
+ */
+public class JdbcMetadataCatalog {
+ /** Table name separator. */
+ private static final String TABLE_NAME_SEPARATOR = "\\.";
+
+ /** Table schema. */
+ private static final int TABLE_SCHEMA = 0;
+
+ /** Table name. */
+ private static final int TABLE_NAME = 1;
+
+ /** Primary key identifier. */
+ private static final String PK = "PK_";
+
+ /** Table type. */
+ private static final String TBL_TYPE = "TABLE";
+
+ /** Default schema name. */
+ private static final String DEFAULT_SCHEMA_NAME = "PUBLIC";
+
+ /** Ignite tables interface. Used to get all the database metadata. */
+ private final IgniteTables tables;
+
+ /** Comparator for {@link Column} by schema then table name then column order. */
+ private static final Comparator<Pair<String, Column>> bySchemaThenTabNameThenColOrder
+ = Comparator.comparing((Function<Pair<String, Column>, String>)Pair::getFirst)
+ .thenComparingInt(o -> o.getSecond().schemaIndex());
+
+ /** Comparator for {@link JdbcTableMeta} by table type then schema then table name. */
+ private static final Comparator<Table> byTblTypeThenSchemaThenTblName = Comparator.comparing(Table::tableName);
+
+ /**
+ * Initializes info.
+ *
+ * @param tables IgniteTables.
+ */
+ public JdbcMetadataCatalog(IgniteTables tables) {
+ this.tables = tables;
+ }
+
+ /**
+ * See {@link DatabaseMetaData#getPrimaryKeys(String, String, String)} for details.
+ *
+ * Ignite has only one possible CATALOG_NAME, it is handled on the client (driver) side.
+ *
+ * @param schemaNamePtrn Sql pattern for schema name.
+ * @param tblNamePtrn Sql pattern for table name.
+ * @return Collection of primary keys information for tables that matches specified schema and table name patterns.
+ */
+ public Collection<JdbcPrimaryKeyMeta> getPrimaryKeys(String schemaNamePtrn, String tblNamePtrn) {
+ Collection<JdbcPrimaryKeyMeta> metaSet = new HashSet<>();
+
+ String schemaNameRegex = translateSqlWildcardsToRegex(schemaNamePtrn);
+ String tlbNameRegex = translateSqlWildcardsToRegex(tblNamePtrn);
+
+ tables.tables().stream()
+ .filter(t -> matches(getTblSchema(t.tableName()), schemaNameRegex))
+ .filter(t -> matches(getTblName(t.tableName()), tlbNameRegex))
+ .forEach(tbl -> {
+ JdbcPrimaryKeyMeta meta = createPrimaryKeyMeta(tbl);
+
+ metaSet.add(meta);
+ });
+
+ return metaSet;
+ }
+
+ /**
+ * See {@link DatabaseMetaData#getTables(String, String, String, String[])} for details.
+ *
+ * Ignite has only one possible value for CATALOG_NAME and has only one table type so these parameters are handled
+ * on the client (driver) side.
+ *
+ * Result is ordered by (schema name, table name).
+ *
+ * @param schemaNamePtrn Sql pattern for schema name.
+ * @param tblNamePtrn Sql pattern for table name.
+ * @param tblTypes Requested table types.
+ * @return List of metadatas of tables that matches.
+ */
+ public List<JdbcTableMeta> getTablesMeta(String schemaNamePtrn, String tblNamePtrn, String[] tblTypes) {
+ String schemaNameRegex = translateSqlWildcardsToRegex(schemaNamePtrn);
+ String tlbNameRegex = translateSqlWildcardsToRegex(tblNamePtrn);
+
+ List<Table> tblsMeta = tables.tables().stream()
+ .filter(t -> matches(getTblSchema(t.tableName()), schemaNameRegex))
+ .filter(t -> matches(getTblName(t.tableName()), tlbNameRegex))
+ .collect(Collectors.toList());
+
+ return tblsMeta.stream()
+ .sorted(byTblTypeThenSchemaThenTblName)
+ .map(t -> new JdbcTableMeta(getTblSchema(t.tableName()), getTblName(t.tableName()), TBL_TYPE))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * See {@link DatabaseMetaData#getColumns(String, String, String, String)} for details.
+ *
+ * Ignite has only one possible CATALOG_NAME, it is handled on the client (driver) side.
+ *
+ * @param schemaNamePtrn Schema name java regex pattern.
+ * @param tblNamePtrn Table name java regex pattern.
+ * @param colNamePtrn Column name java regex pattern.
+ * @return List of metadatas about columns that match specified schema/tablename/columnname criterias.
+ */
+ public Collection<JdbcColumnMeta> getColumnsMeta(String schemaNamePtrn, String tblNamePtrn, String colNamePtrn) {
+ Collection<JdbcColumnMeta> metas = new LinkedHashSet<>();
+
+ String schemaNameRegex = translateSqlWildcardsToRegex(schemaNamePtrn);
+ String tlbNameRegex = translateSqlWildcardsToRegex(tblNamePtrn);
+ String colNameRegex = translateSqlWildcardsToRegex(colNamePtrn);
+
+ tables.tables().stream()
+ .filter(t -> matches(getTblSchema(t.tableName()), schemaNameRegex))
+ .filter(t -> matches(getTblName(t.tableName()), tlbNameRegex))
+ .flatMap(
+ tbl -> {
+ SchemaDescriptor schema = ((TableImpl)tbl).schemaView().schema();
+
+ List<Pair<String, Column>> tblColPairs = new ArrayList<>();
+
+ for (Column column : schema.keyColumns().columns())
+ tblColPairs.add(new Pair<>(tbl.tableName(), column));
+
+ for (Column column : schema.valueColumns().columns())
+ tblColPairs.add(new Pair<>(tbl.tableName(), column));
+
+ return tblColPairs.stream();
+ })
+ .filter(e -> matches(e.getSecond().name(), colNameRegex))
+ .sorted(bySchemaThenTabNameThenColOrder)
+ .forEachOrdered(pair -> {
+ JdbcColumnMeta colMeta = createColumnMeta(pair.getFirst(), pair.getSecond());
+
+ if (!metas.contains(colMeta))
+ metas.add(colMeta);
+ });
+
+ return metas;
+ }
+
+ /**
+ * See {@link DatabaseMetaData#getSchemas(String, String)} for details.
+ *
+ * Ignite has only one possible CATALOG_NAME, it is handled on the client (driver) side.
+ *
+ * @param schemaNamePtrn Sql pattern for schema name filter.
+ * @return schema names that matches provided pattern.
+ */
+ public Collection<String> getSchemasMeta(String schemaNamePtrn) {
+ SortedSet<String> schemas = new TreeSet<>(); // to have values sorted.
+
+ String schemaNameRegex = translateSqlWildcardsToRegex(schemaNamePtrn);
+
+ if (matches(DEFAULT_SCHEMA_NAME, schemaNameRegex))
+ schemas.add(DEFAULT_SCHEMA_NAME);
+
+ tables.tables().stream()
+ .map(tbl -> getTblSchema(tbl.tableName()))
+ .filter(schema -> matches(schema, schemaNameRegex))
+ .forEach(schemas::add);
+
+ return schemas;
+ }
+
+ /**
+ * Creates primary key metadata from table object.
+ *
+ * @param tbl Table.
+ * @return Jdbc primary key metadata.
+ */
+ private JdbcPrimaryKeyMeta createPrimaryKeyMeta(Table tbl) {
+ String schemaName = getTblSchema(tbl.tableName());
+ String tblName = getTblName(tbl.tableName());
+
+ final String keyName = PK + tblName;
+
+ SchemaRegistry registry = ((TableImpl)tbl).schemaView();
+
+ List<String> keyColNames = Arrays.stream(registry.schema().keyColumns().columns())
+ .map(Column::name)
+ .collect(Collectors.toList());
+
+ return new JdbcPrimaryKeyMeta(schemaName, tblName, keyName, keyColNames);
+ }
+
+ /**
+ * Creates column metadata from column and table name.
+ *
+ * @param tblName Table name.
+ * @param col Column.
+ * @return Column metadata.
+ */
+ private JdbcColumnMeta createColumnMeta(String tblName, Column col) {
+ NativeType type = col.type();
+
+ int precision = -1;
+ int scale = -1;
+
+ if (type.spec() == NativeTypeSpec.NUMBER)
+ precision = ((NumberNativeType)type).precision();
+ else if (type.spec() == NativeTypeSpec.DECIMAL) {
+ precision = ((DecimalNativeType)type).precision();
+ scale = ((DecimalNativeType)type).scale();
+ }
+
+ return new JdbcColumnMeta(
+ getTblSchema(tblName),
+ getTblName(tblName),
+ col.name(),
+ Commons.nativeTypeToClass(col.type()),
+ precision,
+ scale,
+ col.nullable()
+ );
+ }
+
+ /**
+ * Splits the tableName into schema and table name and returns the table name.
+ *
+ * @param tblName Table name.
+ * @return Table name string.
+ */
+ private String getTblName(String tblName) {
+ return tblName.split(TABLE_NAME_SEPARATOR)[TABLE_NAME];
+ }
+
+ /**
+ * Splits the tableName into schema and table name and returns the table schema.
+ *
+ * @param tblName Table name.
+ * @return Table schema string.
+ */
+ private String getTblSchema(String tblName) {
+ return tblName.split(TABLE_NAME_SEPARATOR)[TABLE_SCHEMA];
+ }
+
+ /**
+ * Checks whether string matches SQL pattern.
+ *
+ * @param str String.
+ * @param sqlPtrn Pattern.
+ * @return Whether string matches pattern.
+ */
+ private static boolean matches(String str, String sqlPtrn) {
+ if (str == null)
+ return false;
+
+ if (sqlPtrn == null)
+ return true;
+
+ return str.matches(sqlPtrn);
+ }
+
+ /**
+ * <p>Converts sql pattern wildcards into java regex wildcards.</p>
+ * <p>Translates "_" to "." and "%" to ".*" if those are not escaped with "\" ("\_" or "\%").</p>
+ * <p>All other characters are considered normal and will be escaped if necessary.</p>
+ * <pre>
+ * Example:
+ * som_ --> som.
+ * so% --> so.*
+ * s[om]e --> so\[om\]e
+ * so\_me --> so_me
+ * some? --> some\?
+ * som\e --> som\\e
+ * </pre>
+ *
+ * @param sqlPtrn Sql pattern.
+ * @return Java regex pattern.
+ */
+ private static String translateSqlWildcardsToRegex(String sqlPtrn) {
+ if (sqlPtrn == null || sqlPtrn.isEmpty())
+ return sqlPtrn;
+
+ String toRegex = ' ' + sqlPtrn;
+
+ toRegex = toRegex.replaceAll("([\\[\\]{}()*+?.\\\\\\\\^$|])", "\\\\$1");
+ toRegex = toRegex.replaceAll("([^\\\\\\\\])((?:\\\\\\\\\\\\\\\\)*)%", "$1$2.*");
+ toRegex = toRegex.replaceAll("([^\\\\\\\\])((?:\\\\\\\\\\\\\\\\)*)_", "$1$2.");
+ toRegex = toRegex.replaceAll("([^\\\\\\\\])(\\\\\\\\(?>\\\\\\\\\\\\\\\\)*\\\\\\\\)*\\\\\\\\([_|%])", "$1$2$3");
+
+ return toRegex.substring(1);
+ }
+}
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/TcpIgniteClient.java b/modules/client/src/main/java/org/apache/ignite/internal/client/TcpIgniteClient.java
index 88680be7..0ffb12b 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/TcpIgniteClient.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/TcpIgniteClient.java
@@ -117,11 +117,11 @@
}
/**
- * Send JdbcClientMessage request to server size and reads JdbcClientMessage result.
+ * Send ClientMessage request to server size and reads ClientMessage result.
*
* @param opCode Operation code.
- * @param req JdbcClientMessage request.
- * @param res JdbcClientMessage result.
+ * @param req ClientMessage request.
+ * @param res ClientMessage result.
*/
public void sendRequest(int opCode, ClientMessage req, ClientMessage res) {
ch.serviceAsync(opCode, w -> req.writeBinary(w.out()), p -> {
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/query/JdbcClientQueryEventHandler.java b/modules/client/src/main/java/org/apache/ignite/internal/client/query/JdbcClientQueryEventHandler.java
index a02b766..441776c 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/query/JdbcClientQueryEventHandler.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/query/JdbcClientQueryEventHandler.java
@@ -20,6 +20,14 @@
import org.apache.ignite.client.proto.query.JdbcQueryEventHandler;
import org.apache.ignite.client.proto.query.event.BatchExecuteRequest;
import org.apache.ignite.client.proto.query.event.BatchExecuteResult;
+import org.apache.ignite.client.proto.query.event.JdbcMetaColumnsRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaColumnsResult;
+import org.apache.ignite.client.proto.query.event.JdbcMetaPrimaryKeysRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaPrimaryKeysResult;
+import org.apache.ignite.client.proto.query.event.JdbcMetaSchemasRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaSchemasResult;
+import org.apache.ignite.client.proto.query.event.JdbcMetaTablesRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaTablesResult;
import org.apache.ignite.client.proto.query.event.QueryCloseRequest;
import org.apache.ignite.client.proto.query.event.QueryCloseResult;
import org.apache.ignite.client.proto.query.event.QueryExecuteRequest;
@@ -78,4 +86,40 @@
return res;
}
+
+ /** {@inheritDoc} */
+ @Override public JdbcMetaTablesResult tablesMeta(JdbcMetaTablesRequest req) {
+ JdbcMetaTablesResult res = new JdbcMetaTablesResult();
+
+ client.sendRequest(ClientOp.SQL_TABLE_META, req, res);
+
+ return res;
+ }
+
+ /** {@inheritDoc} */
+ @Override public JdbcMetaColumnsResult columnsMeta(JdbcMetaColumnsRequest req) {
+ JdbcMetaColumnsResult res = new JdbcMetaColumnsResult();
+
+ client.sendRequest(ClientOp.SQL_COLUMN_META, req, res);
+
+ return res;
+ }
+
+ /** {@inheritDoc} */
+ @Override public JdbcMetaSchemasResult schemasMeta(JdbcMetaSchemasRequest req) {
+ JdbcMetaSchemasResult res = new JdbcMetaSchemasResult();
+
+ client.sendRequest(ClientOp.SQL_SCHEMAS_META, req, res);
+
+ return res;
+ }
+
+ /** {@inheritDoc} */
+ @Override public JdbcMetaPrimaryKeysResult primaryKeysMeta(JdbcMetaPrimaryKeysRequest req) {
+ JdbcMetaPrimaryKeysResult res = new JdbcMetaPrimaryKeysResult();
+
+ client.sendRequest(ClientOp.SQL_PK_META, req, res);
+
+ return res;
+ }
}
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java b/modules/client/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
index f8effd4..fc48d0c 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
@@ -104,6 +104,9 @@
/** Ignite remote client. */
private final TcpIgniteClient client;
+ /** Jdbc metadata. Cache the JDBC object on the first access */
+ private JdbcDatabaseMetadata metadata;
+
/**
* Constructor.
*
@@ -255,7 +258,7 @@
if (autoCommit)
throw new SQLException("Transaction cannot be committed explicitly in auto-commit mode.");
- doCommit();
+ doCommit();
}
/** {@inheritDoc} */
@@ -311,7 +314,10 @@
@Override public DatabaseMetaData getMetaData() throws SQLException {
ensureNotClosed();
- throw new SQLFeatureNotSupportedException("DatabaseMetaData is not supported.");
+ if (metadata == null)
+ metadata = new JdbcDatabaseMetadata(this);
+
+ return metadata;
}
/** {@inheritDoc} */
@@ -774,4 +780,13 @@
public ConnectionProperties connectionProperties() {
return connProps;
}
+
+ /**
+ * Gets connection url.
+ *
+ * @return Connection URL.
+ */
+ public String url() {
+ return connProps.getUrl();
+ }
}
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/jdbc/JdbcDatabaseMetadata.java b/modules/client/src/main/java/org/apache/ignite/internal/jdbc/JdbcDatabaseMetadata.java
new file mode 100644
index 0000000..27ee6b2
--- /dev/null
+++ b/modules/client/src/main/java/org/apache/ignite/internal/jdbc/JdbcDatabaseMetadata.java
@@ -0,0 +1,1531 @@
+/*
+ * 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;
+
+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.client.proto.query.IgniteQueryErrorCode;
+import org.apache.ignite.client.proto.query.event.JdbcColumnMeta;
+import org.apache.ignite.client.proto.query.event.JdbcMetaColumnsRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaColumnsResult;
+import org.apache.ignite.client.proto.query.event.JdbcMetaPrimaryKeysRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaPrimaryKeysResult;
+import org.apache.ignite.client.proto.query.event.JdbcMetaSchemasRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaSchemasResult;
+import org.apache.ignite.client.proto.query.event.JdbcMetaTablesRequest;
+import org.apache.ignite.client.proto.query.event.JdbcMetaTablesResult;
+import org.apache.ignite.client.proto.query.event.JdbcPrimaryKeyMeta;
+import org.apache.ignite.client.proto.query.event.JdbcTableMeta;
+import org.apache.ignite.internal.client.proto.ProtocolVersion;
+
+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;
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+
+/**
+ * JDBC database metadata implementation.
+ */
+public class JdbcDatabaseMetadata implements DatabaseMetaData {
+ /** Driver name. */
+ public static final String DRIVER_NAME = "Apache Ignite JDBC Driver";
+
+ /** The only possible name for catalog. */
+ public static final String CATALOG_NAME = "IGNITE";
+
+ /** Name of TABLE type. */
+ public static final String TYPE_TABLE = "TABLE";
+
+ /** Connection. */
+ private final JdbcConnection conn;
+
+ /**
+ * Constructor.
+ *
+ * @param conn Connection.
+ */
+ JdbcDatabaseMetadata(JdbcConnection conn) {
+ this.conn = conn;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean allProceduresAreCallable() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean allTablesAreSelectable() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getURL() {
+ return conn.url();
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getUserName() {
+ return "";
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean isReadOnly() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean nullsAreSortedHigh() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean nullsAreSortedLow() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean nullsAreSortedAtStart() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean nullsAreSortedAtEnd() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getDatabaseProductName() {
+ return "Apache Ignite";
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getDatabaseProductVersion() {
+ return ProtocolVersion.LATEST_VER.toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getDriverName() {
+ return DRIVER_NAME;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getDriverVersion() {
+ return ProtocolVersion.LATEST_VER.toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getDriverMajorVersion() {
+ return ProtocolVersion.LATEST_VER.major();
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getDriverMinorVersion() {
+ return ProtocolVersion.LATEST_VER.minor();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean usesLocalFiles() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean usesLocalFilePerTable() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsMixedCaseIdentifiers() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean storesUpperCaseIdentifiers() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean storesLowerCaseIdentifiers() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean storesMixedCaseIdentifiers() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsMixedCaseQuotedIdentifiers() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean storesUpperCaseQuotedIdentifiers() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean storesLowerCaseQuotedIdentifiers() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean storesMixedCaseQuotedIdentifiers() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getIdentifierQuoteString() {
+ return "\"";
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getSQLKeywords() {
+ return "";
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getNumericFunctions() {
+ return "";
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getStringFunctions() {
+ return "";
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getSystemFunctions() {
+ return "";
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getTimeDateFunctions() {
+ return "";
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getSearchStringEscape() {
+ return "\\";
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getExtraNameCharacters() {
+ return "";
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsAlterTableWithAddColumn() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsAlterTableWithDropColumn() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsColumnAliasing() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean nullPlusNonNullIsNull() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsConvert() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsConvert(int fromType, int toType) {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsTableCorrelationNames() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsDifferentTableCorrelationNames() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsExpressionsInOrderBy() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsOrderByUnrelated() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsGroupBy() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsGroupByUnrelated() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsGroupByBeyondSelect() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsLikeEscapeClause() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsMultipleResultSets() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsMultipleTransactions() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsNonNullableColumns() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsMinimumSQLGrammar() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsCoreSQLGrammar() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsExtendedSQLGrammar() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsANSI92EntryLevelSQL() {
+ //TODO IGNITE-15527
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsANSI92IntermediateSQL() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsANSI92FullSQL() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsIntegrityEnhancementFacility() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsOuterJoins() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsFullOuterJoins() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsLimitedOuterJoins() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getSchemaTerm() {
+ return "";
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getProcedureTerm() {
+ return "";
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getCatalogTerm() {
+ return "";
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean isCatalogAtStart() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getCatalogSeparator() {
+ return "";
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsSchemasInDataManipulation() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsSchemasInProcedureCalls() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsSchemasInTableDefinitions() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsSchemasInIndexDefinitions() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsSchemasInPrivilegeDefinitions() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsCatalogsInDataManipulation() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsCatalogsInProcedureCalls() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsCatalogsInTableDefinitions() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsCatalogsInIndexDefinitions() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsCatalogsInPrivilegeDefinitions() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsPositionedDelete() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsPositionedUpdate() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsSelectForUpdate() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsStoredProcedures() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsSubqueriesInComparisons() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsSubqueriesInExists() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsSubqueriesInIns() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsSubqueriesInQuantifieds() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsCorrelatedSubqueries() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsUnion() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsUnionAll() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsOpenCursorsAcrossCommit() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsOpenCursorsAcrossRollback() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsOpenStatementsAcrossCommit() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsOpenStatementsAcrossRollback() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxBinaryLiteralLength() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxCharLiteralLength() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxColumnNameLength() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxColumnsInGroupBy() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxColumnsInIndex() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxColumnsInOrderBy() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxColumnsInSelect() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxColumnsInTable() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxConnections() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxCursorNameLength() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxIndexLength() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxSchemaNameLength() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxProcedureNameLength() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxCatalogNameLength() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxRowSize() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean doesMaxRowSizeIncludeBlobs() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxStatementLength() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxStatements() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxTableNameLength() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxTablesInSelect() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getMaxUserNameLength() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getDefaultTransactionIsolation() {
+ return TRANSACTION_NONE;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsTransactions() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsTransactionIsolationLevel(int level) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsDataDefinitionAndDataManipulationTransactions() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsDataManipulationTransactionsOnly() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean dataDefinitionCausesTransactionCommit() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean dataDefinitionIgnoredInTransactions() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public ResultSet getProcedures(String catalog, String schemaPtrn,
+ String procedureNamePtrn) {
+ return new JdbcResultSet(Collections.emptyList(), 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) {
+ return new JdbcResultSet(Collections.emptyList(), 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 {
+ conn.ensureNotClosed();
+
+ final List<JdbcColumnMeta> meta = 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 (TYPE_TABLE.equals(type)) {
+ tblTypeMatch = true;
+
+ break;
+ }
+ }
+ }
+
+ if (!isValidCatalog(catalog) || !tblTypeMatch)
+ return new JdbcResultSet(Collections.emptyList(), meta);
+
+ JdbcMetaTablesResult res
+ = conn.handler().tablesMeta(new JdbcMetaTablesRequest(schemaPtrn, tblNamePtrn, tblTypes));
+
+ if (!res.hasResults())
+ throw IgniteQueryErrorCode.createJdbcSqlException(res.err(), res.status());
+
+ List<List<Object>> rows = new LinkedList<>();
+
+ for (JdbcTableMeta tblMeta : res.meta())
+ rows.add(tableRow(tblMeta));
+
+ return new JdbcResultSet(rows, meta);
+ }
+
+ /** {@inheritDoc} */
+ @Override public ResultSet getSchemas() throws SQLException {
+ return getSchemas(null, "%");
+ }
+
+ /** {@inheritDoc} */
+ @SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
+ @Override public ResultSet getCatalogs() {
+ return new JdbcResultSet(singletonList(singletonList(CATALOG_NAME)),
+ asList(new JdbcColumnMeta(null, null, "TABLE_CAT", String.class)));
+ }
+
+ /** {@inheritDoc} */
+ @SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
+ @Override public ResultSet getTableTypes() {
+ return new JdbcResultSet(
+ asList(singletonList(TYPE_TABLE)),
+ asList(new JdbcColumnMeta(null, null, "TABLE_TYPE", String.class)));
+ }
+
+ /** {@inheritDoc} */
+ @Override public ResultSet getColumns(String catalog, String schemaPtrn, String tblNamePtrn, String colNamePtrn) throws SQLException {
+ conn.ensureNotClosed();
+
+ final List<JdbcColumnMeta> meta = asList(
+ new JdbcColumnMeta(null, null, "TABLE_CAT", String.class), // 1
+ new JdbcColumnMeta(null, null, "TABLE_SCHEM", String.class), // 2
+ new JdbcColumnMeta(null, null, "TABLE_NAME", String.class), // 3
+ new JdbcColumnMeta(null, null, "COLUMN_NAME", String.class), // 4
+ new JdbcColumnMeta(null, null, "DATA_TYPE", Short.class), // 5
+ new JdbcColumnMeta(null, null, "TYPE_NAME", String.class), // 6
+ new JdbcColumnMeta(null, null, "COLUMN_SIZE", Integer.class), // 7
+ new JdbcColumnMeta(null, null, "BUFFER_LENGTH", Integer.class), // 8
+ new JdbcColumnMeta(null, null, "DECIMAL_DIGITS", Integer.class), // 9
+ new JdbcColumnMeta(null, null, "NUM_PREC_RADIX", Short.class), // 10
+ new JdbcColumnMeta(null, null, "NULLABLE", Short.class), // 11
+ new JdbcColumnMeta(null, null, "REMARKS", String.class), // 12
+ new JdbcColumnMeta(null, null, "COLUMN_DEF", String.class), // 13
+ new JdbcColumnMeta(null, null, "SQL_DATA_TYPE", Integer.class), // 14
+ new JdbcColumnMeta(null, null, "SQL_DATETIME_SUB", Integer.class), // 15
+ new JdbcColumnMeta(null, null, "CHAR_OCTET_LENGTH", Integer.class), // 16
+ new JdbcColumnMeta(null, null, "ORDINAL_POSITION", Integer.class), // 17
+ new JdbcColumnMeta(null, null, "IS_NULLABLE", String.class), // 18
+ new JdbcColumnMeta(null, null, "SCOPE_CATLOG", String.class), // 19
+ new JdbcColumnMeta(null, null, "SCOPE_SCHEMA", String.class), // 20
+ new JdbcColumnMeta(null, null, "SCOPE_TABLE", String.class), // 21
+ new JdbcColumnMeta(null, null, "SOURCE_DATA_TYPE", Short.class), // 22
+ new JdbcColumnMeta(null, null, "IS_AUTOINCREMENT", String.class), // 23
+ new JdbcColumnMeta(null, null, "IS_GENERATEDCOLUMN", String.class) // 24
+ );
+
+ if (!isValidCatalog(catalog))
+ return new JdbcResultSet(Collections.emptyList(), meta);
+
+ JdbcMetaColumnsResult res = conn.handler().columnsMeta(new JdbcMetaColumnsRequest(schemaPtrn, tblNamePtrn, colNamePtrn));
+
+ if (!res.hasResults())
+ throw IgniteQueryErrorCode.createJdbcSqlException(res.err(), res.status());
+
+ 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 JdbcResultSet(rows, meta);
+ }
+
+ /** {@inheritDoc} */
+ @Override public ResultSet getColumnPrivileges(String catalog, String schema, String tbl,
+ String colNamePtrn) {
+ return new JdbcResultSet(Collections.emptyList(), 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) {
+ return new JdbcResultSet(Collections.emptyList(), 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) {
+ return new JdbcResultSet(Collections.emptyList(), 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) {
+ return new JdbcResultSet(Collections.emptyList(), 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 {
+ conn.ensureNotClosed();
+
+ final List<JdbcColumnMeta> meta = 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 (!isValidCatalog(catalog))
+ return new JdbcResultSet(Collections.emptyList(), meta);
+
+ JdbcMetaPrimaryKeysResult res = conn.handler().primaryKeysMeta(new JdbcMetaPrimaryKeysRequest(schema, tbl));
+
+ if (!res.hasResults())
+ throw IgniteQueryErrorCode.createJdbcSqlException(res.err(), res.status());
+
+ List<List<Object>> rows = new LinkedList<>();
+
+ for (JdbcPrimaryKeyMeta pkMeta : res.meta())
+ rows.addAll(primaryKeyRows(pkMeta));
+
+ return new JdbcResultSet(rows, meta);
+ }
+
+ /** {@inheritDoc} */
+ @Override public ResultSet getImportedKeys(String catalog, String schema, String tbl) {
+ return new JdbcResultSet(Collections.emptyList(), 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) {
+ return new JdbcResultSet(Collections.emptyList(), 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) {
+ return new JdbcResultSet(Collections.emptyList(), 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() {
+ List<List<Object>> types = new ArrayList<>(21);
+
+ types.add(Arrays.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.asList("ARRAY", Types.ARRAY, 0, "(", "')", null,
+ (short)typeNullable, false, (short)typeSearchable, false, false, false, "ARRAY", 0, 0,
+ Types.ARRAY, 0, null));
+
+ return new JdbcResultSet(types, 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 {
+ conn.ensureNotClosed();
+
+ final List<JdbcColumnMeta> meta = 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 (!isValidCatalog(catalog))
+ return new JdbcResultSet(Collections.emptyList(), meta);
+
+ throw new UnsupportedOperationException("Index info is not supported yet.");
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsResultSetType(int type) {
+ return type == TYPE_FORWARD_ONLY;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsResultSetConcurrency(int type, int concurrency) {
+ return supportsResultSetType(type) && concurrency == CONCUR_READ_ONLY;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean ownUpdatesAreVisible(int type) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean ownDeletesAreVisible(int type) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean ownInsertsAreVisible(int type) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean othersUpdatesAreVisible(int type) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean othersDeletesAreVisible(int type) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean othersInsertsAreVisible(int type) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean updatesAreDetected(int type) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean deletesAreDetected(int type) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean insertsAreDetected(int type) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsBatchUpdates() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public ResultSet getUDTs(String catalog, String schemaPtrn, String typeNamePtrn,
+ int[] types) {
+ return new JdbcResultSet(Collections.emptyList(), 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() {
+ return conn;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsSavepoints() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsNamedParameters() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsMultipleOpenResults() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsGetGeneratedKeys() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public ResultSet getSuperTypes(String catalog, String schemaPtrn,
+ String typeNamePtrn) {
+ return new JdbcResultSet(Collections.emptyList(), 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) {
+ return new JdbcResultSet(Collections.emptyList(), 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) {
+ return new JdbcResultSet(Collections.emptyList(), 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) {
+ return holdability == HOLD_CURSORS_OVER_COMMIT;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getResultSetHoldability() {
+ return HOLD_CURSORS_OVER_COMMIT;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getDatabaseMajorVersion() {
+ return ProtocolVersion.LATEST_VER.major();
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getDatabaseMinorVersion() {
+ return ProtocolVersion.LATEST_VER.minor();
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getJDBCMajorVersion() {
+ return 4;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getJDBCMinorVersion() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getSQLStateType() {
+ return DatabaseMetaData.sqlStateSQL99;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean locatorsUpdateCopy() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsStatementPooling() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public RowIdLifetime getRowIdLifetime() {
+ return ROWID_UNSUPPORTED;
+ }
+
+ /** {@inheritDoc} */
+ @Override public ResultSet getSchemas(String catalog, String schemaPtrn) throws SQLException {
+ conn.ensureNotClosed();
+
+ final List<JdbcColumnMeta> meta = asList(
+ new JdbcColumnMeta(null, null, "TABLE_SCHEM", String.class),
+ new JdbcColumnMeta(null, null, "TABLE_CATALOG", String.class)
+ );
+
+ if (!isValidCatalog(catalog))
+ return new JdbcResultSet(Collections.emptyList(), meta);
+
+ JdbcMetaSchemasResult res = conn.handler().schemasMeta(new JdbcMetaSchemasRequest(schemaPtrn));
+
+ if (!res.hasResults())
+ throw IgniteQueryErrorCode.createJdbcSqlException(res.err(), res.status());
+
+ List<List<Object>> rows = new LinkedList<>();
+
+ for (String schema : res.schemas()) {
+ List<Object> row = new ArrayList<>(2);
+
+ row.add(schema);
+ row.add(CATALOG_NAME);
+
+ rows.add(row);
+ }
+
+ return new JdbcResultSet(rows, meta);
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean supportsStoredFunctionsUsingCallSyntax() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean autoCommitFailureClosesAllResultSets() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public ResultSet getClientInfoProperties() {
+ return new JdbcResultSet(Collections.emptyList(), 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)
+ ));
+ }
+
+ //TODO IGNITE-15529 List all supported functions
+ /** {@inheritDoc} */
+ @Override public ResultSet getFunctions(String catalog, String schemaPtrn,
+ String functionNamePtrn) {
+ return new JdbcResultSet(Collections.emptyList(), 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) {
+ return new JdbcResultSet(Collections.emptyList(), 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} */
+ @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) {
+ return iface != null && iface.isAssignableFrom(JdbcDatabaseMetadata.class);
+ }
+
+ /** {@inheritDoc} */
+ @Override public ResultSet getPseudoColumns(String catalog, String schemaPtrn, String tblNamePtrn,
+ String colNamePtrn) {
+ return new JdbcResultSet(Collections.emptyList(), 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)
+ ));
+ }
+
+ /**
+ * Checks if specified catalog matches the only possible catalog value. See {@link JdbcDatabaseMetadata#CATALOG_NAME}.
+ *
+ * @param catalog Catalog name or {@code null}.
+ * @return {@code true} If catalog equal ignoring case to {@link JdbcDatabaseMetadata#CATALOG_NAME}
+ * or null (which means any catalog).
+ * Otherwise returns {@code false}.
+ */
+ private static boolean isValidCatalog(String catalog) {
+ return catalog == null || catalog.equalsIgnoreCase(CATALOG_NAME);
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean generatedKeyAlwaysReturned() {
+ return false;
+ }
+
+ /**
+ * Constructs a list of rows in jdbc format for a given table metadata.
+ *
+ * @param tblMeta Table metadata.
+ * @return Table metadata row.
+ */
+ private List<Object> tableRow(JdbcTableMeta tblMeta) {
+ List<Object> row = new ArrayList<>(10);
+
+ row.add(CATALOG_NAME);
+ row.add(tblMeta.schemaName());
+ row.add(tblMeta.tableName());
+ row.add(tblMeta.tableType());
+ row.add(null);
+ row.add(null);
+ row.add(null);
+ row.add(null);
+ row.add(null);
+ row.add(null);
+
+ return row;
+ }
+
+ /**
+ * Constructs a list of rows in jdbc format for a given column metadata.
+ *
+ * @param colMeta Column metadata.
+ * @param pos Ordinal position.
+ * @return Column metadata row.
+ */
+ public static List<Object> columnRow(JdbcColumnMeta colMeta, int pos) {
+ List<Object> row = new ArrayList<>(24);
+
+ row.add(CATALOG_NAME); // 1. TABLE_CAT
+ row.add(colMeta.schemaName()); // 2. TABLE_SCHEM
+ row.add(colMeta.tableName()); // 3. TABLE_NAME
+ row.add(colMeta.columnName()); // 4. COLUMN_NAME
+ row.add(colMeta.dataType()); // 5. DATA_TYPE
+ row.add(colMeta.dataTypeName()); // 6. TYPE_NAME
+ row.add(colMeta.precision() == -1 ? null : colMeta.precision()); // 7. COLUMN_SIZE
+ row.add((Integer)null); // 8. BUFFER_LENGTH
+ row.add(colMeta.scale() == -1 ? null : colMeta.scale()); // 9. DECIMAL_DIGITS
+ row.add(10); // 10. NUM_PREC_RADIX
+ row.add(colMeta.isNullable() ? columnNullable : columnNoNulls); // 11. NULLABLE
+ row.add((String)null); // 12. REMARKS
+ row.add(colMeta.defaultValue()); // 13. COLUMN_DEF
+ row.add(colMeta.dataType()); // 14. SQL_DATA_TYPE
+ row.add((Integer)null); // 15. SQL_DATETIME_SUB
+ row.add(Integer.MAX_VALUE); // 16. CHAR_OCTET_LENGTH
+ row.add(pos); // 17. ORDINAL_POSITION
+ row.add(colMeta.isNullable() ? "YES" : "NO"); // 18. IS_NULLABLE
+ row.add((String)null); // 19. SCOPE_CATALOG
+ row.add((String)null); // 20. SCOPE_SCHEMA
+ row.add((String)null); // 21. SCOPE_TABLE
+ row.add((Short)null); // 22. SOURCE_DATA_TYPE
+ row.add("NO"); // 23. IS_AUTOINCREMENT
+ row.add("NO"); // 23. IS_GENERATEDCOLUMN
+
+ return row;
+ }
+
+ /**
+ * Constructs a list of rows in jdbc format for a given primary key metadata.
+ *
+ * @param pkMeta Primary key metadata.
+ * @return Result set rows for primary key.
+ */
+ private static 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(CATALOG_NAME); // table catalog
+ row.add(pkMeta.schemaName());
+ row.add(pkMeta.tableName());
+ row.add(pkMeta.fields().get(i));
+ row.add(i + 1); // sequence number
+ row.add(pkMeta.name());
+
+ rows.add(row);
+ }
+
+ return rows;
+ }
+}
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java b/modules/client/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
index eeab095..8d4a61a 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
@@ -47,6 +47,7 @@
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Calendar;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -55,6 +56,7 @@
import org.apache.ignite.client.proto.query.IgniteQueryErrorCode;
import org.apache.ignite.client.proto.query.JdbcQueryEventHandler;
import org.apache.ignite.client.proto.query.SqlStateCode;
+import org.apache.ignite.client.proto.query.event.JdbcColumnMeta;
import org.apache.ignite.client.proto.query.event.QueryCloseRequest;
import org.apache.ignite.client.proto.query.event.QueryCloseResult;
import org.apache.ignite.client.proto.query.event.QueryFetchRequest;
@@ -87,6 +89,15 @@
/** Cursor ID. */
private final Long cursorId;
+ /** Jdbc column metadata. */
+ private List<JdbcColumnMeta> meta;
+
+ /** Metadata initialization flag. */
+ private boolean metaInit;
+
+ /** Column order map. */
+ private Map<String, Integer> colOrder;
+
/** Rows. */
private List<List<Object>> rows;
@@ -163,6 +174,27 @@
this.updCnt = updCnt;
}
+ /**
+ * Creates new result set.
+ *
+ * @param rows Rows.
+ * @param meta Column metadata.
+ */
+ public JdbcResultSet(List<List<Object>> rows, List<JdbcColumnMeta> meta) {
+ stmt = null;
+ cursorId = null;
+
+ finished = true;
+ isQuery = true;
+
+ this.rows = rows;
+ this.rowsIter = rows.iterator();
+ this.meta = meta;
+ this.metaInit = true;
+
+ initColumnOrder();
+ }
+
/** {@inheritDoc} */
@Override public boolean next() throws SQLException {
ensureNotClosed();
@@ -690,7 +722,6 @@
ensureNotClosed();
throw new SQLFeatureNotSupportedException("ResultSetMetaData are not supported.");
-
}
/** {@inheritDoc} */
@@ -699,8 +730,17 @@
Objects.requireNonNull(colLb);
- throw new SQLFeatureNotSupportedException("FindColumn by column label are not supported.");
+ if (!metaInit)
+ throw new SQLFeatureNotSupportedException("FindColumn by column label are not supported.");
+ Integer order = columnOrder().get(colLb.toUpperCase());
+
+ if (order == null)
+ throw new SQLException("Column not found: " + colLb, SqlStateCode.PARSING_EXCEPTION);
+
+ assert order >= 0;
+
+ return order + 1;
}
/** {@inheritDoc} */
@@ -1888,4 +1928,49 @@
SqlStateCode.CONVERSION_FAILED);
}
}
+
+ /**
+ * Init if needed and return column order.
+ *
+ * @return Column order map.
+ * @throws SQLException On error.
+ */
+ private Map<String, Integer> columnOrder() throws SQLException {
+ if (colOrder != null)
+ return colOrder;
+
+ if (!metaInit)
+ meta();
+
+ initColumnOrder();
+
+ return colOrder;
+ }
+
+ /**
+ * Init column order map.
+ */
+ private void initColumnOrder() {
+ colOrder = new HashMap<>(meta.size());
+
+ for (int i = 0; i < meta.size(); ++i) {
+ String colName = meta.get(i).columnName().toUpperCase();
+
+ if (!colOrder.containsKey(colName))
+ colOrder.put(colName, i);
+ }
+ }
+
+ /**
+ * Returns columns metadata list.
+ *
+ * @return Results metadata.
+ * @throws SQLException On error.
+ */
+ private List<JdbcColumnMeta> meta() throws SQLException {
+ if (finished && (!isQuery || autoClose))
+ throw new SQLException("Server cursor is already closed.", SqlStateCode.INVALID_CURSOR_STATE);
+
+ throw new SQLFeatureNotSupportedException("ResultSetMetaData are not supported.");
+ }
}
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/jdbc/JdbcConnectionSelfTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/jdbc/JdbcConnectionSelfTest.java
index 8427299..afe3bf2 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/jdbc/JdbcConnectionSelfTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/jdbc/JdbcConnectionSelfTest.java
@@ -82,7 +82,7 @@
*/
@SuppressWarnings({"EmptyTryBlock", "unused"})
@Test
- @Disabled
+ @Disabled("ITDS-1887")
public void testDefaultsIPv6() throws Exception {
var url = "jdbc:ignite:thin://[::1]:10800";
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/jdbc/JdbcMetadataSelfTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/jdbc/JdbcMetadataSelfTest.java
new file mode 100644
index 0000000..7818ec7
--- /dev/null
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/jdbc/JdbcMetadataSelfTest.java
@@ -0,0 +1,642 @@
+/*
+ * 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.runner.app.jdbc;
+
+import java.math.BigDecimal;
+import java.nio.file.Path;
+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.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.ignite.app.Ignite;
+import org.apache.ignite.app.IgnitionManager;
+import org.apache.ignite.internal.client.proto.ProtocolVersion;
+import org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter;
+import org.apache.ignite.jdbc.IgniteJdbcDriver;
+import org.apache.ignite.schema.ColumnType;
+import org.apache.ignite.schema.SchemaBuilders;
+import org.apache.ignite.schema.SchemaTable;
+import org.apache.ignite.table.Table;
+import org.apache.ignite.table.Tuple;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static java.sql.Types.DATE;
+import static java.sql.Types.DECIMAL;
+import static java.sql.Types.INTEGER;
+import static java.sql.Types.VARCHAR;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Metadata tests.
+ */
+public class JdbcMetadataSelfTest {
+ /** URL. */
+ protected static final String URL = "jdbc:ignite:thin://127.0.1.1:10800";
+
+ /** Nodes bootstrap configuration. */
+ private static final Map<String, String> nodesBootstrapCfg = new LinkedHashMap<>() {{
+ put("node2", "{\n" +
+ " \"node\": {\n" +
+ " \"metastorageNodes\":[ \"node2\" ]\n" +
+ " }\n" +
+ "}");
+ }};
+
+ /** Cluster nodes. */
+ protected static final List<Ignite> clusterNodes = new ArrayList<>();
+
+ /**
+ * Creates a cluster of three nodes.
+ *
+ * @param temp Temporal directory.
+ */
+ @BeforeAll
+ public static void beforeAll(@TempDir Path temp) {
+ IgniteJdbcDriver.register();
+
+ nodesBootstrapCfg.forEach((nodeName, configStr) ->
+ clusterNodes.add(IgnitionManager.start(nodeName, configStr, temp.resolve(nodeName)))
+ );
+ }
+
+ /**
+ * Close all cluster nodes.
+ *
+ * @throws Exception if failed.
+ */
+ @AfterAll
+ public static void afterAll() throws Exception {
+ for (Ignite clusterNode : clusterNodes) {
+ clusterNode.close();
+ }
+ }
+
+ /**
+ * Create the connection ant statement.
+ *
+ * @throws Exception if failed.
+ */
+ @BeforeEach
+ public void beforeTest() {
+ // Create table on node 0.
+ SchemaTable perTbl = SchemaBuilders.tableBuilder("PUBLIC", "PERSON").columns(
+ SchemaBuilders.column("NAME", ColumnType.string()).asNullable().build(),
+ SchemaBuilders.column("AGE", ColumnType.INT32).asNullable().build(),
+ SchemaBuilders.column("ORGID", ColumnType.INT32).asNonNull().build()
+ ).withPrimaryKey("ORGID").build();
+
+ SchemaTable orgTbl = SchemaBuilders.tableBuilder("PUBLIC", "ORGANIZATION").columns(
+ SchemaBuilders.column("ID", ColumnType.INT32).asNonNull().build(),
+ SchemaBuilders.column("NAME", ColumnType.string()).asNullable().build(),
+ SchemaBuilders.column("BIGDATA", ColumnType.decimalOf(20, 10)).asNullable().build()
+ ).withPrimaryKey("ID").build();
+
+ if (clusterNodes.get(0).tables().table(perTbl.canonicalName()) != null)
+ return;
+
+ clusterNodes.get(0).tables().createTable(perTbl.canonicalName(), tblCh ->
+ SchemaConfigurationConverter.convert(perTbl, tblCh)
+ .changeReplicas(1)
+ .changePartitions(10)
+ );
+
+ clusterNodes.get(0).tables().createTable(orgTbl.canonicalName(), tblCh ->
+ SchemaConfigurationConverter.convert(orgTbl, tblCh)
+ .changeReplicas(1)
+ .changePartitions(10)
+ );
+
+ Table tbl1 = clusterNodes.get(0).tables().table(perTbl.canonicalName());
+ Table tbl2 = clusterNodes.get(0).tables().table(orgTbl.canonicalName());
+
+ tbl1.insert(Tuple.create().set("ORGID", 1).set("NAME", "111").set("AGE", 111));
+ tbl2.insert(Tuple.create().set("ID", 1).set("NAME", "AAA").set("BIGDATA", BigDecimal.valueOf(10)));
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ @Disabled("IGNITE-15187")
+ public void testResultSetMetaData() throws Exception {
+ Statement stmt = DriverManager.getConnection(URL).createStatement();
+
+ ResultSet rs = stmt.executeQuery(
+ "select p.name, o.id as orgId, p.age from PERSON p, ORGANIZATION o where p.orgId = o.id");
+
+ assertNotNull(rs);
+
+ ResultSetMetaData meta = rs.getMetaData();
+
+ assertNotNull(meta);
+
+ assertEquals(3, meta.getColumnCount());
+
+ assertEquals("Person".toUpperCase(), meta.getTableName(1).toUpperCase());
+ assertEquals("name".toUpperCase(), meta.getColumnName(1).toUpperCase());
+ assertEquals("name".toUpperCase(), meta.getColumnLabel(1).toUpperCase());
+ assertEquals(VARCHAR, meta.getColumnType(1));
+ assertEquals(meta.getColumnTypeName(1), "VARCHAR");
+ assertEquals(meta.getColumnClassName(1), "java.lang.String");
+
+ assertEquals("Organization".toUpperCase(), meta.getTableName(2).toUpperCase());
+ assertEquals("orgId".toUpperCase(), meta.getColumnName(2).toUpperCase());
+ assertEquals("orgId".toUpperCase(), meta.getColumnLabel(2).toUpperCase());
+ assertEquals(INTEGER, meta.getColumnType(2));
+ assertEquals(meta.getColumnTypeName(2), "INTEGER");
+ assertEquals(meta.getColumnClassName(2), "java.lang.Integer");
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ @Disabled
+ public void testDecimalAndDateTypeMetaData() throws Exception {
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ Statement stmt = conn.createStatement();
+
+ ResultSet rs = stmt.executeQuery(
+ "select t.decimal, t.date from \"metaTest\".MetaTest as t");
+
+ assertNotNull(rs);
+
+ ResultSetMetaData meta = rs.getMetaData();
+
+ assertNotNull(meta);
+
+ assertEquals(2, meta.getColumnCount());
+
+ assertEquals("METATEST", meta.getTableName(1).toUpperCase());
+ assertEquals("DECIMAL", meta.getColumnName(1).toUpperCase());
+ assertEquals("DECIMAL", meta.getColumnLabel(1).toUpperCase());
+ assertEquals(DECIMAL, meta.getColumnType(1));
+ assertEquals(meta.getColumnTypeName(1), "DECIMAL");
+ assertEquals(meta.getColumnClassName(1), "java.math.BigDecimal");
+
+ assertEquals("METATEST", meta.getTableName(2).toUpperCase());
+ assertEquals("DATE", meta.getColumnName(2).toUpperCase());
+ assertEquals("DATE", meta.getColumnLabel(2).toUpperCase());
+ assertEquals(DATE, meta.getColumnType(2));
+ assertEquals(meta.getColumnTypeName(2), "DATE");
+ assertEquals(meta.getColumnClassName(2), "java.sql.Date");
+ }
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testGetTables() throws Exception {
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ DatabaseMetaData meta = conn.getMetaData();
+
+ ResultSet rs = meta.getTables("IGNITE", "PUBLIC", "%", new String[]{"TABLE"});
+ assertNotNull(rs);
+ assertTrue(rs.next());
+ assertEquals("TABLE", rs.getString("TABLE_TYPE"));
+ assertEquals("ORGANIZATION", rs.getString("TABLE_NAME"));
+ assertTrue(rs.next());
+ assertEquals("TABLE", rs.getString("TABLE_TYPE"));
+ assertEquals("PERSON", rs.getString("TABLE_NAME"));
+
+ rs = meta.getTables("IGNITE", "PUBLIC", "%", null);
+ assertNotNull(rs);
+ assertTrue(rs.next());
+ assertEquals("TABLE", rs.getString("TABLE_TYPE"));
+ assertEquals("ORGANIZATION", rs.getString("TABLE_NAME"));
+
+ rs = meta.getTables("IGNITE", "PUBLIC", "", new String[]{"WRONG"});
+ assertFalse(rs.next());
+ }
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testGetColumns() throws Exception {
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ DatabaseMetaData meta = conn.getMetaData();
+
+ ResultSet rs = meta.getColumns("IGNITE", "PUBLIC", "PERSON", "%");
+
+ assertNotNull(rs);
+
+ Collection<String> names = new ArrayList<>(2);
+
+ names.add("NAME");
+ names.add("AGE");
+ names.add("ORGID");
+
+ int cnt = 0;
+
+ while (rs.next()) {
+ String name = rs.getString("COLUMN_NAME");
+
+ assertTrue(names.remove(name));
+
+ if ("NAME".equals(name)) {
+ assertEquals(VARCHAR, rs.getInt("DATA_TYPE"));
+ assertEquals(rs.getString("TYPE_NAME"), "VARCHAR");
+ assertEquals(1, rs.getInt("NULLABLE"));
+ } else if ("AGE".equals(name)) {
+ assertEquals(INTEGER, rs.getInt("DATA_TYPE"));
+ assertEquals(rs.getString("TYPE_NAME"), "INTEGER");
+ assertEquals(1, rs.getInt("NULLABLE"));
+ } else if ("ORGID".equals(name)) {
+ assertEquals(INTEGER, rs.getInt("DATA_TYPE"));
+ assertEquals(rs.getString("TYPE_NAME"), "INTEGER");
+ assertEquals(0, rs.getInt("NULLABLE"));
+
+ }
+ cnt++;
+ }
+
+ assertTrue(names.isEmpty());
+ assertEquals(3, cnt);
+
+ rs = meta.getColumns("IGNITE", "PUBLIC", "ORGANIZATION", "%");
+
+ assertNotNull(rs);
+
+ names.add("ID");
+ names.add("NAME");
+ names.add("BIGDATA");
+
+ cnt = 0;
+
+ while (rs.next()) {
+ String name = rs.getString("COLUMN_NAME");
+
+ assertTrue(names.remove(name));
+
+ if ("ID".equals(name)) {
+ assertEquals(INTEGER, rs.getInt("DATA_TYPE"));
+ assertEquals(rs.getString("TYPE_NAME"), "INTEGER");
+ assertEquals(0, rs.getInt("NULLABLE"));
+ } else if ("NAME".equals(name)) {
+ assertEquals(VARCHAR, rs.getInt("DATA_TYPE"));
+ assertEquals(rs.getString("TYPE_NAME"), "VARCHAR");
+ assertEquals(1, rs.getInt("NULLABLE"));
+ } else if ("BIGDATA".equals(name)) {
+ assertEquals(DECIMAL, rs.getInt("DATA_TYPE"));
+ assertEquals(rs.getString("TYPE_NAME"), "DECIMAL");
+ assertEquals(1, rs.getInt("NULLABLE"));
+ assertEquals(10, rs.getInt("DECIMAL_DIGITS"));
+ assertEquals(20, rs.getInt("COLUMN_SIZE"));
+ }
+
+ cnt++;
+ }
+
+ assertTrue(names.isEmpty());
+ assertEquals(3, cnt);
+ }
+ }
+
+ /**
+ * Check JDBC support flags.
+ */
+ @Test
+ public void testCheckSupports() throws SQLException {
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ DatabaseMetaData meta = conn.getMetaData();
+
+ assertTrue(meta.supportsANSI92EntryLevelSQL());
+ assertTrue(meta.supportsAlterTableWithAddColumn());
+ assertTrue(meta.supportsAlterTableWithDropColumn());
+ assertTrue(meta.nullPlusNonNullIsNull());
+ }
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testVersions() throws Exception {
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ assertEquals(conn.getMetaData().getDatabaseProductVersion(), ProtocolVersion.LATEST_VER.toString(),
+ "Unexpected ignite database product version.");
+ assertEquals(conn.getMetaData().getDriverVersion(), ProtocolVersion.LATEST_VER.toString(),
+ "Unexpected ignite driver version.");
+ }
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testSchemasMetadata() throws Exception {
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ ResultSet rs = conn.getMetaData().getSchemas();
+
+ Set<String> expectedSchemas = new HashSet<>(Arrays.asList("PUBLIC", "PUBLIC"));
+
+ Set<String> schemas = new HashSet<>();
+
+ while (rs.next())
+ schemas.add(rs.getString(1));
+
+ assertEquals(schemas, expectedSchemas);
+ }
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testEmptySchemasMetadata() throws Exception {
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ ResultSet rs = conn.getMetaData().getSchemas(null, "qqq");
+
+ assertFalse(rs.next(), "Empty result set is expected");
+ }
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testPrimaryKeyMetadata() throws Exception {
+ try (Connection conn = DriverManager.getConnection(URL);
+ ResultSet rs = conn.getMetaData().getPrimaryKeys(null, "PUBLIC", "PERSON")) {
+
+ int cnt = 0;
+
+ while (rs.next()) {
+ assertEquals(rs.getString("COLUMN_NAME"), "ORGID");
+
+ cnt++;
+ }
+
+ assertEquals(1, cnt);
+ }
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ 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(
+ "PUBLIC.ORGANIZATION.PK_ORGANIZATION.ID",
+ "PUBLIC.PERSON.PK_PERSON.ORGID"));
+
+ 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"));
+ }
+
+ assertEquals(expectedPks, actualPks, "Metadata contains unexpected primary keys info.");
+ }
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testInvalidCatalog() throws Exception {
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ DatabaseMetaData meta = conn.getMetaData();
+
+ ResultSet rs = meta.getSchemas("q", null);
+
+ assertFalse(rs.next(), "Results must be empty");
+
+ rs = meta.getTables("q", null, null, null);
+
+ assertFalse(rs.next(), "Results must be empty");
+
+ rs = meta.getColumns("q", null, null, null);
+
+ assertFalse(rs.next(), "Results must be empty");
+
+ rs = meta.getIndexInfo("q", null, null, false, false);
+
+ assertFalse(rs.next(), "Results must be empty");
+
+ rs = meta.getPrimaryKeys("q", null, null);
+
+ assertFalse(rs.next(), "Results must be empty");
+ }
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testGetTableTypes() throws Exception {
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ DatabaseMetaData meta = conn.getMetaData();
+
+ ResultSet rs = meta.getTableTypes();
+
+ assertTrue(rs.next());
+
+ assertEquals("TABLE", rs.getString("TABLE_TYPE"));
+
+ assertFalse(rs.next());
+ }
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ @Disabled
+ public void testParametersMetadata() throws Exception {
+ // Perform checks few times due to query/plan caching.
+ for (int i = 0; i < 3; i++) {
+ // No parameters statement.
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ conn.setSchema("\"pers\"");
+
+ PreparedStatement noParams = conn.prepareStatement("select * from Person;");
+ ParameterMetaData params = noParams.getParameterMetaData();
+
+ assertEquals(0, params.getParameterCount(), "Parameters should be empty.");
+ }
+
+ // Selects.
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ conn.setSchema("\"pers\"");
+
+ PreparedStatement selectStmt = conn.prepareStatement("select orgId from Person p where p.name > ? and p.orgId > ?");
+
+ ParameterMetaData meta = selectStmt.getParameterMetaData();
+
+ assertNotNull(meta);
+
+ assertEquals(2, meta.getParameterCount());
+
+ assertEquals(VARCHAR, meta.getParameterType(1));
+ assertEquals(ParameterMetaData.parameterNullableUnknown, meta.isNullable(1));
+ assertEquals(Integer.MAX_VALUE, meta.getPrecision(1));
+
+ assertEquals(INTEGER, meta.getParameterType(2));
+ assertEquals(ParameterMetaData.parameterNullableUnknown, meta.isNullable(2));
+ }
+
+ // Updates.
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ conn.setSchema("\"pers\"");
+
+ PreparedStatement updateStmt = conn.prepareStatement("update Person p set orgId = 42 where p.name > ? and p.orgId > ?");
+
+ ParameterMetaData meta = updateStmt.getParameterMetaData();
+
+ assertNotNull(meta);
+
+ assertEquals(2, meta.getParameterCount());
+
+ assertEquals(VARCHAR, meta.getParameterType(1));
+ assertEquals(ParameterMetaData.parameterNullableUnknown, meta.isNullable(1));
+ assertEquals(Integer.MAX_VALUE, meta.getPrecision(1));
+
+ assertEquals(INTEGER, meta.getParameterType(2));
+ assertEquals(ParameterMetaData.parameterNullableUnknown, meta.isNullable(2));
+ }
+
+ // Multistatement
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ conn.setSchema("\"pers\"");
+
+ PreparedStatement updateStmt = conn.prepareStatement(
+ "update Person p set orgId = 42 where p.name > ? and p.orgId > ?;" +
+ "select orgId from Person p where p.name > ? and p.orgId > ?");
+
+ ParameterMetaData meta = updateStmt.getParameterMetaData();
+
+ assertNotNull(meta);
+
+ assertEquals(4, meta.getParameterCount());
+
+ assertEquals(VARCHAR, meta.getParameterType(1));
+ assertEquals(ParameterMetaData.parameterNullableUnknown, meta.isNullable(1));
+ assertEquals(Integer.MAX_VALUE, meta.getPrecision(1));
+
+ assertEquals(INTEGER, meta.getParameterType(2));
+ assertEquals(ParameterMetaData.parameterNullableUnknown, meta.isNullable(2));
+
+ assertEquals(VARCHAR, meta.getParameterType(3));
+ assertEquals(ParameterMetaData.parameterNullableUnknown, meta.isNullable(3));
+ assertEquals(Integer.MAX_VALUE, meta.getPrecision(3));
+
+ assertEquals(INTEGER, meta.getParameterType(4));
+ assertEquals(ParameterMetaData.parameterNullableUnknown, meta.isNullable(4));
+ }
+ }
+ }
+
+ /**
+ * Check that parameters metadata throws correct exception on non-parsable statement.
+ */
+ @Test
+ public void testParametersMetadataNegative() throws Exception {
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ conn.setSchema("\"pers\"");
+
+ PreparedStatement notCorrect = conn.prepareStatement("select * from NotExistingTable;");
+
+ assertThrows(SQLException.class, notCorrect::getParameterMetaData, "Table \"NOTEXISTINGTABLE\" not found");
+ }
+ }
+
+ /**
+ * Negative scenarios for catalog name.
+ * Perform metadata lookups, that use incorrect catalog names.
+ */
+ @Test
+ public void testCatalogWithNotExistingName() throws SQLException {
+ checkNoEntitiesFoundForCatalog("");
+ checkNoEntitiesFoundForCatalog("NOT_EXISTING_CATALOG");
+ }
+
+ /**
+ * Check that lookup in the metadata have been performed using specified catalog name (that is neither {@code null}
+ * nor correct catalog name), empty result set is returned.
+ *
+ * @param invalidCat catalog name that is not either
+ */
+ private void checkNoEntitiesFoundForCatalog(String invalidCat) throws SQLException {
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ DatabaseMetaData meta = conn.getMetaData();
+
+ // Intention: we set the other arguments that way, the values to have as many results as possible.
+ assertIsEmpty(meta.getTables(invalidCat, null, "%", new String[] {"TABLE"}));
+ assertIsEmpty(meta.getColumns(invalidCat, null, "%", "%"));
+ assertIsEmpty(meta.getColumnPrivileges(invalidCat, "pers", "PERSON", "%"));
+ assertIsEmpty(meta.getTablePrivileges(invalidCat, null, "%"));
+ assertIsEmpty(meta.getPrimaryKeys(invalidCat, "pers", "PERSON"));
+ assertIsEmpty(meta.getImportedKeys(invalidCat, "pers", "PERSON"));
+ assertIsEmpty(meta.getExportedKeys(invalidCat, "pers", "PERSON"));
+ // meta.getCrossReference(...) doesn't make sense because we don't have FK constraint.
+ assertIsEmpty(meta.getIndexInfo(invalidCat, null, "%", false, true));
+ assertIsEmpty(meta.getSuperTables(invalidCat, "%", "%"));
+ assertIsEmpty(meta.getSchemas(invalidCat, null));
+ assertIsEmpty(meta.getPseudoColumns(invalidCat, null, "%", ""));
+ }
+ }
+
+ /**
+ * Assert that specified ResultSet contains no rows.
+ *
+ * @param rs result set to check.
+ * @throws SQLException on error.
+ */
+ private static void assertIsEmpty(ResultSet rs) throws SQLException {
+ try {
+ boolean empty = !rs.next();
+
+ assertTrue(empty, "Result should be empty because invalid catalog is specified.");
+ }
+ finally {
+ rs.close();
+ }
+ }
+}