This is an automated email from the ASF dual-hosted git repository.

jooger pushed a commit to branch jdbc_over_thin_sql
in repository https://gitbox.apache.org/repos/asf/ignite-3.git

commit d7e92ce689d5c7e1e4c67b625378151533b139c4
Author: Max Zhuravkov <[email protected]>
AuthorDate: Thu Aug 28 07:36:36 2025 +0300

    IGNITE-26276 Sql. Jdbc. Add new implementation for ResultSetMetadata (#6488)
---
 .../internal/jdbc/proto/event/JdbcColumnMeta.java  |  10 +-
 .../ignite/client/handler/ItClientHandlerTest.java |   1 +
 .../internal/jdbc/JdbcResultSetMetadata.java       |  37 ++-
 .../internal/jdbc2/JdbcResultSetMetadata.java      | 223 ++++++++++++++
 .../ignite/internal/jdbc/ColumnDefinition.java     |  67 +++++
 .../jdbc/JdbcResultSetMetadataBaseSelfTest.java    | 320 +++++++++++++++++++++
 .../jdbc/JdbcResultSetMetadataSelfTest.java        |  68 +++++
 .../jdbc2/JdbcResultSetMetadata2SelfTest.java      |  84 ++++++
 8 files changed, 798 insertions(+), 12 deletions(-)

diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcColumnMeta.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcColumnMeta.java
index 5ae1473852b..7fa56b49802 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcColumnMeta.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/jdbc/proto/event/JdbcColumnMeta.java
@@ -303,7 +303,7 @@ public class JdbcColumnMeta extends Response {
      * @param columnType Column type.
      * @return SQL type name.
      */
-    private static String typeName(ColumnType columnType) {
+    public static String typeName(ColumnType columnType) {
         switch (columnType) {
             case BOOLEAN: return "BOOLEAN";
             case INT8: return "TINYINT";
@@ -331,7 +331,13 @@ public class JdbcColumnMeta extends Response {
         }
     }
 
-    private static int typeId(ColumnType columnType) {
+    /**
+     * Converts column type to SQL type id.
+     *
+     * @param columnType Column type.
+     * @return SQL type id.
+     */
+    public static int typeId(ColumnType columnType) {
         switch (columnType) {
             case BOOLEAN: return BOOLEAN;
             case INT8: return TINYINT;
diff --git 
a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java
 
b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java
index 883e0687ea8..6358a799ce3 100644
--- 
a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java
+++ 
b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java
@@ -546,6 +546,7 @@ public class ItClientHandlerTest extends 
BaseIgniteAbstractTest {
             expected.set(9);
             expected.set(10);
             expected.set(11);
+            expected.set(12);
             assertEquals(expected, supportedFeatures);
 
             var extensionsLen = unpacker.unpackInt();
diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSetMetadata.java
 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSetMetadata.java
index 0cc9b79d324..09abc1f185b 100644
--- 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSetMetadata.java
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSetMetadata.java
@@ -52,121 +52,131 @@ public class JdbcResultSetMetadata implements 
ResultSetMetaData {
     /** {@inheritDoc} */
     @Override
     public boolean isAutoIncrement(int col) throws SQLException {
+        getColumn(col);
         return false;
     }
 
     /** {@inheritDoc} */
     @Override
     public boolean isCaseSensitive(int col) throws SQLException {
+        getColumn(col);
         return false;
     }
 
     /** {@inheritDoc} */
     @Override
     public boolean isSearchable(int col) throws SQLException {
+        getColumn(col);
         return false;
     }
 
     /** {@inheritDoc} */
     @Override
     public boolean isCurrency(int col) throws SQLException {
+        getColumn(col);
         return false;
     }
 
     /** {@inheritDoc} */
     @Override
     public int isNullable(int col) throws SQLException {
-        return columnNullable;
+        return getColumn(col).isNullable() ? columnNullable : columnNoNulls;
     }
 
     /** {@inheritDoc} */
     @Override
     public boolean isSigned(int col) throws SQLException {
+        getColumn(col);
         return true;
     }
 
     /** {@inheritDoc} */
     @Override
     public int getColumnDisplaySize(int col) throws SQLException {
+        getColumn(col);
         return COL_WIDTH;
     }
 
     /** {@inheritDoc} */
     @Override
     public String getColumnLabel(int col) throws SQLException {
-        return meta.get(col - 1).columnLabel();
+        return getColumn(col).columnLabel();
     }
 
     /** {@inheritDoc} */
     @Override
     public String getColumnName(int col) throws SQLException {
-        return meta.get(col - 1).columnName();
+        return getColumn(col).columnName();
     }
 
     /** {@inheritDoc} */
     @Override
     public String getSchemaName(int col) throws SQLException {
-        return meta.get(col - 1).schemaName();
+        return getColumn(col).schemaName();
     }
 
     /** {@inheritDoc} */
     @Override
     public int getPrecision(int col) throws SQLException {
-        return meta.get(col - 1).precision();
+        return getColumn(col).precision();
     }
 
     /** {@inheritDoc} */
     @Override
     public int getScale(int col) throws SQLException {
-        return meta.get(col - 1).scale();
+        return getColumn(col).scale();
     }
 
     /** {@inheritDoc} */
     @Override
     public String getTableName(int col) throws SQLException {
-        return meta.get(col - 1).tableName();
+        return getColumn(col).tableName();
     }
 
     /** {@inheritDoc} */
     @Override
     public String getCatalogName(int col) throws SQLException {
+        getColumn(col);
         return "";
     }
 
     /** {@inheritDoc} */
     @Override
     public int getColumnType(int col) throws SQLException {
-        return meta.get(col - 1).dataType();
+        return getColumn(col).dataType();
     }
 
     /** {@inheritDoc} */
     @Override
     public String getColumnTypeName(int col) throws SQLException {
-        return meta.get(col - 1).dataTypeName();
+        return getColumn(col).dataTypeName();
     }
 
     /** {@inheritDoc} */
     @Override
     public boolean isReadOnly(int col) throws SQLException {
+        getColumn(col);
         return true;
     }
 
     /** {@inheritDoc} */
     @Override
     public boolean isWritable(int col) throws SQLException {
+        getColumn(col);
         return false;
     }
 
     /** {@inheritDoc} */
     @Override
     public boolean isDefinitelyWritable(int col) throws SQLException {
+        getColumn(col);
         return false;
     }
 
     /** {@inheritDoc} */
     @Override
     public String getColumnClassName(int col) throws SQLException {
-        return meta.get(col - 1).dataTypeClass();
+        return getColumn(col).dataTypeClass();
     }
 
     /** {@inheritDoc} */
@@ -184,4 +194,11 @@ public class JdbcResultSetMetadata implements 
ResultSetMetaData {
     public boolean isWrapperFor(Class<?> iface) throws SQLException {
         return iface != null && 
iface.isAssignableFrom(JdbcResultSetMetadata.class);
     }
+
+    private JdbcColumnMeta getColumn(int col) throws SQLException {
+        if (col < 1 || col > meta.size()) {
+            throw new SQLException("Invalid column index: " + col);
+        }
+        return meta.get(col - 1);
+    }
 }
diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSetMetadata.java
 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSetMetadata.java
new file mode 100644
index 00000000000..6eba7145a3a
--- /dev/null
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc2/JdbcResultSetMetadata.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.jdbc2;
+
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.List;
+import org.apache.ignite.internal.jdbc.JdbcConverterUtils;
+import org.apache.ignite.internal.jdbc.proto.event.JdbcColumnMeta;
+import org.apache.ignite.sql.ColumnMetadata;
+import org.apache.ignite.sql.ColumnMetadata.ColumnOrigin;
+import org.apache.ignite.sql.ColumnType;
+import org.apache.ignite.sql.ResultSetMetadata;
+
+/**
+ * JDBC result set metadata implementation.
+ */
+public class JdbcResultSetMetadata implements ResultSetMetaData {
+    private static final int COLUMN_DISPLAY_SIZE = 30;
+
+    private final List<ColumnMetadata> cols;
+
+    /**
+     * Constructor.
+     *
+     * @param metadata Metadata.
+     */
+    public JdbcResultSetMetadata(ResultSetMetadata metadata) {
+        if (metadata == null) {
+            throw new IllegalArgumentException("metadata");
+        }
+        this.cols = metadata.columns();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getColumnCount() throws SQLException {
+        return cols.size();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isAutoIncrement(int column) throws SQLException {
+        getColumn(column);
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCaseSensitive(int column) throws SQLException {
+        getColumn(column);
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isSearchable(int column) throws SQLException {
+        getColumn(column);
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCurrency(int column) throws SQLException {
+        getColumn(column);
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int isNullable(int column) throws SQLException {
+        return getColumn(column).nullable() ? columnNullable : columnNoNulls;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isSigned(int column) throws SQLException {
+        getColumn(column);
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getColumnDisplaySize(int column) throws SQLException {
+        getColumn(column);
+        return COLUMN_DISPLAY_SIZE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getColumnLabel(int column) throws SQLException {
+        ColumnMetadata metadata = getColumn(column);
+        return metadata.name();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getColumnName(int column) throws SQLException {
+        ColumnMetadata metadata = getColumn(column);
+        ColumnOrigin origin = metadata.origin();
+        // Compatibility with the existing driver
+        if (origin != null && origin.columnName() != null) {
+            return origin.columnName();
+        } else {
+            return metadata.name();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getSchemaName(int column) throws SQLException {
+        ColumnMetadata.ColumnOrigin origin = getColumn(column).origin();
+        // Compatibility with the existing driver
+        return origin != null ? origin.schemaName() : null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getPrecision(int column) throws SQLException {
+        return getColumn(column).precision();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getScale(int column) throws SQLException {
+        return getColumn(column).scale();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getTableName(int column) throws SQLException {
+        ColumnMetadata.ColumnOrigin origin = getColumn(column).origin();
+        // Compatibility with the existing driver
+        return origin != null ? origin.tableName() : null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getCatalogName(int column) throws SQLException {
+        getColumn(column);
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getColumnType(int column) throws SQLException {
+        ColumnType columnType = getColumn(column).type();
+        return JdbcColumnMeta.typeId(columnType);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getColumnTypeName(int column) throws SQLException {
+        ColumnType columnType = getColumn(column).type();
+        return JdbcColumnMeta.typeName(columnType);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isReadOnly(int column) throws SQLException {
+        getColumn(column);
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isWritable(int column) throws SQLException {
+        getColumn(column);
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isDefinitelyWritable(int column) throws SQLException {
+        getColumn(column);
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getColumnClassName(int column) throws SQLException {
+        ColumnType columnType = getColumn(column).type();
+        return JdbcConverterUtils.columnTypeToJdbcClass(columnType).getName();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public <T> T unwrap(Class<T> iface) throws SQLException {
+        if (!isWrapperFor(iface)) {
+            throw new SQLException("Result set meta data is not a wrapper for 
" + iface.getName());
+        }
+        return iface.cast(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isWrapperFor(Class<?> iface) throws SQLException {
+        return iface != null && 
iface.isAssignableFrom(JdbcResultSetMetadata.class);
+    }
+
+    private ColumnMetadata getColumn(int idx) throws SQLException {
+        // JDBC columns are 1-based
+        if (idx < 1 || idx > cols.size()) {
+            throw new SQLException("Invalid column index: " + idx);
+        }
+        return cols.get(idx - 1);
+    }
+}
diff --git 
a/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/ColumnDefinition.java
 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/ColumnDefinition.java
new file mode 100644
index 00000000000..77d09118a96
--- /dev/null
+++ 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/ColumnDefinition.java
@@ -0,0 +1,67 @@
+/*
+ * 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 org.apache.ignite.sql.ColumnType;
+import org.jetbrains.annotations.Nullable;
+
+/** Column definition. */
+public final class ColumnDefinition {
+    public final String label;
+    public final String schema;
+    public final String table;
+    public final String column;
+    public final ColumnType type;
+    public final int precision;
+    public final int scale;
+    public final boolean nullable;
+
+    ColumnDefinition(
+            String label,
+            ColumnType type,
+            int precision,
+            int scale,
+            boolean nullable
+    ) {
+        this(label, null, null, null, type, precision, scale, nullable);
+    }
+
+    private ColumnDefinition(
+            String label,
+            @Nullable String schema,
+            @Nullable String table,
+            @Nullable String column,
+            ColumnType type,
+            int precision,
+            int scale,
+            boolean nullable
+    ) {
+        this.label = label;
+        this.schema = schema;
+        this.table = table;
+        this.column = column;
+        this.type = type;
+        this.precision = precision;
+        this.scale = scale;
+        this.nullable = nullable;
+    }
+
+    ColumnDefinition withOrigin(@Nullable String schema, @Nullable String 
table, @Nullable String column) {
+        return new ColumnDefinition(label, schema, table, column, type, 
precision, scale, nullable);
+    }
+}
diff --git 
a/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/JdbcResultSetMetadataBaseSelfTest.java
 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/JdbcResultSetMetadataBaseSelfTest.java
new file mode 100644
index 00000000000..8fa2106e349
--- /dev/null
+++ 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/JdbcResultSetMetadataBaseSelfTest.java
@@ -0,0 +1,320 @@
+/*
+ * 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 static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.List;
+import org.apache.ignite.internal.jdbc.proto.event.JdbcColumnMeta;
+import org.apache.ignite.sql.ColumnType;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * Tests for {@link ResultSetMetaData} implementations.
+ */
+public abstract class JdbcResultSetMetadataBaseSelfTest {
+
+    protected static final ColumnDefinition COLUMN = new ColumnDefinition("L", 
ColumnType.DECIMAL, 10, 5, true);
+
+    private static final int COLUMN_DISPLAY_SIZE = 30;
+
+    protected abstract ResultSetMetaData createMeta(List<ColumnDefinition> 
columns);
+
+    @Test
+    public void columnCount() throws SQLException {
+        {
+            ResultSetMetaData md = createMeta(List.of(COLUMN));
+            assertEquals(1, md.getColumnCount());
+        }
+        {
+            ResultSetMetaData md = createMeta(List.of(COLUMN, COLUMN));
+            assertEquals(2, md.getColumnCount());
+        }
+    }
+
+    @ParameterizedTest
+    @EnumSource(ColumnType.class)
+    public void isAutoIncrement(ColumnType columnType) throws SQLException {
+        ColumnDefinition column = new ColumnDefinition("C", columnType, 0, 0, 
true);
+        ResultSetMetaData md = createMeta(List.of(column));
+        assertFalse(md.isAutoIncrement(1));
+    }
+
+    @ParameterizedTest
+    @EnumSource(ColumnType.class)
+    public void isCaseSensitive(ColumnType columnType) throws SQLException {
+        ColumnDefinition column = new ColumnDefinition("C", columnType, 0, 0, 
true);
+        ResultSetMetaData md = createMeta(List.of(column));
+        assertFalse(md.isCaseSensitive(1));
+    }
+
+    @ParameterizedTest
+    @EnumSource(ColumnType.class)
+    public void isSearchable(ColumnType columnType) throws SQLException {
+        ColumnDefinition column = new ColumnDefinition("C", columnType, 0, 0, 
true);
+        ResultSetMetaData md = createMeta(List.of(column));
+        assertFalse(md.isSearchable(1));
+    }
+
+    @ParameterizedTest
+    @EnumSource(ColumnType.class)
+    public void isCurrency(ColumnType columnType) throws SQLException {
+        ColumnDefinition column = new ColumnDefinition("C", columnType, 0, 0, 
true);
+        ResultSetMetaData md = createMeta(List.of(column));
+        assertFalse(md.isCurrency(1));
+    }
+
+    @ParameterizedTest
+    @EnumSource(ColumnType.class)
+    public void getColumnDisplaySize(ColumnType columnType) throws 
SQLException {
+        ColumnDefinition column = new ColumnDefinition("C", columnType, 0, 0, 
true);
+        ResultSetMetaData md = createMeta(List.of(column));
+        assertEquals(COLUMN_DISPLAY_SIZE, md.getColumnDisplaySize(1));
+    }
+
+    @Test
+    public void getColumnLabel() throws SQLException {
+        ResultSetMetaData md = createMeta(List.of(
+                new ColumnDefinition("LABEL1", ColumnType.INT8, 0, 0, true),
+                new ColumnDefinition("label2", ColumnType.INT8, 0, 0, 
true).withOrigin("S", "T", null),
+                new ColumnDefinition("Label3", ColumnType.INT8, 0, 0, 
true).withOrigin("S", "T", "COL")
+        ));
+
+        assertEquals("LABEL1", md.getColumnLabel(1));
+        assertEquals("label2", md.getColumnLabel(2));
+        assertEquals("Label3", md.getColumnLabel(3));
+    }
+
+    @Test
+    public void getColumnName() throws SQLException {
+        ResultSetMetaData md = createMeta(List.of(
+                new ColumnDefinition("COLUMN1", ColumnType.INT8, 0, 0, true),
+                new ColumnDefinition("Column2", ColumnType.INT8, 0, 0, 
true).withOrigin("S", "T", null),
+                new ColumnDefinition("C3", ColumnType.INT8, 0, 0, 
true).withOrigin("S", "T", "Column")
+        ));
+
+        assertEquals("COLUMN1", md.getColumnName(1));
+        assertEquals("Column2", md.getColumnName(2));
+        assertEquals("Column", md.getColumnName(3));
+    }
+
+    @Test
+    public void getSchemaName() throws SQLException {
+        ResultSetMetaData md = createMeta(List.of(
+                new ColumnDefinition("C1", ColumnType.INT8, 0, 0, true),
+                new ColumnDefinition("C2", ColumnType.INT8, 0, 0, 
true).withOrigin("S", "T", null),
+                new ColumnDefinition("Schema", ColumnType.INT8, 0, 0, 
true).withOrigin("Schema", "T", null)
+        ));
+
+        assertNull(md.getSchemaName(1));
+        assertEquals("S", md.getSchemaName(2));
+        assertEquals("Schema", md.getSchemaName(3));
+    }
+
+    @Test
+    public void getTableName() throws SQLException {
+        ResultSetMetaData md = createMeta(List.of(
+                new ColumnDefinition("C1", ColumnType.INT8, 0, 0, true),
+                new ColumnDefinition("C2", ColumnType.INT8, 0, 0, 
true).withOrigin("S", "T", null),
+                new ColumnDefinition("C3", ColumnType.INT8, 0, 0, 
true).withOrigin("S", "Table", null)
+        ));
+
+        assertNull(md.getTableName(1));
+        assertEquals("T", md.getTableName(2));
+        assertEquals("Table", md.getTableName(3));
+    }
+
+    @Test
+    public void getCatalogName() throws SQLException {
+        ResultSetMetaData md = createMeta(List.of(COLUMN));
+        assertEquals("", md.getCatalogName(1));
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {true, false})
+    public void isNullable(boolean nullable) throws SQLException {
+        ColumnDefinition column = new ColumnDefinition("C", ColumnType.INT8, 
0, 0, nullable);
+        ResultSetMetaData md = createMeta(List.of(column));
+
+        int indicator = nullable ? ResultSetMetaData.columnNullable : 
ResultSetMetaData.columnNoNulls;
+        assertEquals(indicator, md.isNullable(1));
+    }
+
+    @ParameterizedTest
+    @EnumSource(ColumnType.class)
+    public void isSigned(ColumnType columnType) throws SQLException {
+        ColumnDefinition column = new ColumnDefinition("C", columnType, 0, 0, 
true);
+        ResultSetMetaData md = createMeta(List.of(column));
+        assertTrue(md.isSigned(1));
+    }
+
+    @ParameterizedTest
+    @ValueSource(ints = {-1, 0, 1, 10})
+    public void getPrecision(int precision) throws SQLException {
+        ColumnDefinition column = new ColumnDefinition("C", 
ColumnType.DECIMAL, precision, 0, true);
+        ResultSetMetaData md = createMeta(List.of(column));
+
+        assertEquals(column.precision, md.getPrecision(1));
+    }
+
+    @ParameterizedTest
+    @EnumSource(ColumnType.class)
+    public void getColumnType(ColumnType columnType) throws SQLException {
+        ColumnDefinition column = new ColumnDefinition("C", columnType, 10, 5, 
true);
+
+        ResultSetMetaData md = createMeta(List.of(column));
+        assertEquals(JdbcColumnMeta.typeId(column.type), md.getColumnType(1));
+    }
+
+    @ParameterizedTest
+    @ValueSource(ints = {-1, 0, 1, 10})
+    public void getScale(int scale) throws SQLException {
+        ColumnDefinition column = new ColumnDefinition("C", 
ColumnType.DECIMAL, 0, scale, true);
+        ResultSetMetaData md = createMeta(List.of(column));
+
+        assertEquals(column.scale, md.getScale(1));
+    }
+
+    @ParameterizedTest
+    @EnumSource(ColumnType.class)
+    public void getColumnTypeName(ColumnType columnType) throws SQLException {
+        ColumnDefinition column = new ColumnDefinition("C", columnType, 0, 0, 
true);
+
+        ResultSetMetaData md = createMeta(List.of(column));
+        assertEquals(JdbcColumnMeta.typeName(column.type), 
md.getColumnTypeName(1));
+    }
+
+    @Test
+    public void isReadOnly() throws SQLException {
+        ResultSetMetaData md = createMeta(List.of(COLUMN));
+        assertTrue(md.isReadOnly(1));
+    }
+
+    @Test
+    public void isWritable() throws SQLException {
+        ResultSetMetaData md = createMeta(List.of(COLUMN));
+        assertFalse(md.isWritable(1));
+    }
+
+    @Test
+    public void isDefinitelyWritable() throws SQLException {
+        ResultSetMetaData md = createMeta(List.of(COLUMN));
+        assertFalse(md.isDefinitelyWritable(1));
+    }
+
+    @ParameterizedTest
+    @EnumSource(ColumnType.class)
+    public void getColumnClassName(ColumnType columnType) throws SQLException {
+        ColumnDefinition column = new ColumnDefinition("C", columnType, 0, 0, 
true);
+        ResultSetMetaData md = createMeta(List.of(column));
+
+        String typeClassName = 
JdbcConverterUtils.columnTypeToJdbcClass(column.type).getName();
+        assertEquals(typeClassName, md.getColumnClassName(1));
+    }
+
+    @Test
+    public void unwrapAndIsWrapperFor() throws SQLException {
+        ResultSetMetaData md = createMeta(List.of(COLUMN));
+        assertTrue(md.isWrapperFor(ResultSetMetaData.class));
+        assertDoesNotThrow(() -> md.unwrap(ResultSetMetaData.class));
+    }
+
+    @Test
+    public void methodsExpectValidColumn() {
+        ResultSetMetaData md = createMeta(List.of(COLUMN));
+
+        expectInvalidColumnException(md::isAutoIncrement, 0);
+        expectInvalidColumnException(md::isAutoIncrement, 2);
+
+        expectInvalidColumnException(md::isCaseSensitive, 0);
+        expectInvalidColumnException(md::isCaseSensitive, 2);
+
+        expectInvalidColumnException(md::isSearchable, 0);
+        expectInvalidColumnException(md::isSearchable, 2);
+
+        expectInvalidColumnException(md::isCurrency, 0);
+        expectInvalidColumnException(md::isCurrency, 2);
+
+        expectInvalidColumnException(md::isNullable, 0);
+        expectInvalidColumnException(md::isNullable, 2);
+
+        expectInvalidColumnException(md::isSigned, 0);
+        expectInvalidColumnException(md::isSigned, 2);
+
+        expectInvalidColumnException(md::getColumnDisplaySize, 0);
+        expectInvalidColumnException(md::getColumnDisplaySize, 2);
+
+        expectInvalidColumnException(md::getColumnName, 0);
+        expectInvalidColumnException(md::getColumnName, 2);
+
+        expectInvalidColumnException(md::getSchemaName, 0);
+        expectInvalidColumnException(md::getSchemaName, 2);
+
+        expectInvalidColumnException(md::getPrecision, 0);
+        expectInvalidColumnException(md::getPrecision, 2);
+
+        expectInvalidColumnException(md::getScale, 0);
+        expectInvalidColumnException(md::getScale, 2);
+
+        expectInvalidColumnException(md::getTableName, 0);
+        expectInvalidColumnException(md::getTableName, 2);
+
+        expectInvalidColumnException(md::getCatalogName, 0);
+        expectInvalidColumnException(md::getCatalogName, 2);
+
+        expectInvalidColumnException(md::getColumnType, 0);
+        expectInvalidColumnException(md::getColumnType, 2);
+
+        expectInvalidColumnException(md::getColumnTypeName, 0);
+        expectInvalidColumnException(md::getColumnTypeName, 2);
+
+        expectInvalidColumnException(md::isReadOnly, 0);
+        expectInvalidColumnException(md::isReadOnly, 2);
+
+        expectInvalidColumnException(md::isWritable, 0);
+        expectInvalidColumnException(md::isWritable, 2);
+
+        expectInvalidColumnException(md::isDefinitelyWritable, 0);
+        expectInvalidColumnException(md::isDefinitelyWritable, 2);
+
+        expectInvalidColumnException(md::getColumnClassName, 0);
+        expectInvalidColumnException(md::getColumnClassName, 2);
+    }
+
+    private static void expectInvalidColumnException(ColumnMetadataMethod m, 
int column) {
+        SQLException err = assertThrows(SQLException.class, () -> 
m.call(column));
+        assertThat(err.getMessage(), containsString("Invalid column index: " + 
column));
+    }
+
+    @FunctionalInterface
+    private interface ColumnMetadataMethod {
+        void call(int column) throws SQLException;
+    }
+}
diff --git 
a/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/JdbcResultSetMetadataSelfTest.java
 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/JdbcResultSetMetadataSelfTest.java
new file mode 100644
index 00000000000..b5cda963204
--- /dev/null
+++ 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/JdbcResultSetMetadataSelfTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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 static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.ignite.internal.jdbc.proto.event.JdbcColumnMeta;
+import org.apache.ignite.sql.ResultSet;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link JdbcResultSetMetadata}.
+ */
+public class JdbcResultSetMetadataSelfTest extends 
JdbcResultSetMetadataBaseSelfTest {
+    @Override
+    protected ResultSetMetaData createMeta(List<ColumnDefinition> columns) {
+        List<JdbcColumnMeta> jdbcColumns = new ArrayList<>();
+        for (ColumnDefinition s : columns) {
+            jdbcColumns.add(new JdbcColumnMeta(s.label, s.schema, s.table, 
s.column, s.type, s.precision, s.scale, s.nullable));
+        }
+        return new JdbcResultSetMetadata(jdbcColumns);
+    }
+
+    @Test
+    @Override
+    public void unwrapAndIsWrapperFor() throws SQLException {
+        ResultSetMetaData md = createMeta(List.of(COLUMN));
+        {
+            assertTrue(md.isWrapperFor(ResultSetMetaData.class));
+            assertDoesNotThrow(() -> md.unwrap(ResultSetMetaData.class));
+        }
+
+        {
+            assertTrue(md.isWrapperFor(JdbcResultSetMetadata.class));
+            assertDoesNotThrow(() -> md.unwrap(JdbcResultSetMetadata.class));
+        }
+
+        {
+            assertFalse(md.isWrapperFor(ResultSet.class));
+            SQLException err = assertThrows(SQLException.class, () -> 
md.unwrap(ResultSet.class));
+            assertThat(err.getMessage(), containsString("Result set meta data 
is not a wrapper for " + ResultSet.class.getName()));
+        }
+    }
+}
diff --git 
a/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc2/JdbcResultSetMetadata2SelfTest.java
 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc2/JdbcResultSetMetadata2SelfTest.java
new file mode 100644
index 00000000000..775e3d594f1
--- /dev/null
+++ 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc2/JdbcResultSetMetadata2SelfTest.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.jdbc2;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.ignite.internal.jdbc.ColumnDefinition;
+import org.apache.ignite.internal.jdbc.JdbcResultSetMetadataBaseSelfTest;
+import org.apache.ignite.internal.sql.ColumnMetadataImpl;
+import org.apache.ignite.internal.sql.ColumnMetadataImpl.ColumnOriginImpl;
+import org.apache.ignite.internal.sql.ResultSetMetadataImpl;
+import org.apache.ignite.sql.ColumnMetadata;
+import org.apache.ignite.sql.ResultSet;
+import org.apache.ignite.sql.ResultSetMetadata;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link JdbcResultSetMetadata}.
+ */
+public class JdbcResultSetMetadata2SelfTest extends 
JdbcResultSetMetadataBaseSelfTest {
+
+    @Test
+    @Override
+    public void unwrapAndIsWrapperFor() throws SQLException {
+        ResultSetMetaData md = createMeta(List.of(COLUMN));
+        {
+            assertTrue(md.isWrapperFor(ResultSetMetaData.class));
+            assertDoesNotThrow(() -> md.unwrap(ResultSetMetaData.class));
+        }
+
+        {
+            assertTrue(md.isWrapperFor(JdbcResultSetMetadata.class));
+            assertDoesNotThrow(() -> md.unwrap(JdbcResultSetMetadata.class));
+        }
+
+        {
+            assertFalse(md.isWrapperFor(ResultSet.class));
+            SQLException err = assertThrows(SQLException.class, () -> 
md.unwrap(ResultSet.class));
+            assertThat(err.getMessage(), containsString("Result set meta data 
is not a wrapper for " + ResultSet.class.getName()));
+        }
+    }
+
+    @Override
+    protected ResultSetMetaData createMeta(List<ColumnDefinition> columns) {
+        List<ColumnMetadata> columnsMeta = new ArrayList<>();
+
+        for (ColumnDefinition s : columns) {
+            ColumnOriginImpl origin;
+            if (s.schema != null) {
+                origin = new ColumnOriginImpl(s.schema, s.table, s.column);
+            } else {
+                origin = null;
+            }
+
+            columnsMeta.add(new ColumnMetadataImpl(s.label, s.type, 
s.precision, s.scale, s.nullable, origin));
+        }
+        ResultSetMetadata apiMeta = new ResultSetMetadataImpl(columnsMeta);
+        return new JdbcResultSetMetadata(apiMeta);
+    }
+}


Reply via email to