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


The following commit(s) were added to refs/heads/jdbc_over_thin_sql by this 
push:
     new 5b3f1d8a743 IGNITE-26351 Jdbc. Add additional tests for existing 
ResultSet (#6518)
5b3f1d8a743 is described below

commit 5b3f1d8a743af2558ba9c0e09060b14a9734ef74
Author: Max Zhuravkov <[email protected]>
AuthorDate: Fri Sep 5 10:37:45 2025 +0300

    IGNITE-26351 Jdbc. Add additional tests for existing ResultSet (#6518)
---
 .../apache/ignite/internal/jdbc/JdbcResultSet.java |   40 +-
 .../internal/jdbc/JdbcResultSetBaseSelfTest.java   | 1102 ++++++++++++++++++++
 .../internal/jdbc/JdbcResultSetSelfTest.java       |   64 ++
 3 files changed, 1205 insertions(+), 1 deletion(-)

diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
index 053c169a4aa..25efcc7592f 100644
--- 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcResultSet.java
@@ -79,6 +79,7 @@ import org.apache.ignite.internal.util.StringUtils;
 import org.apache.ignite.internal.util.TransformingIterator;
 import org.apache.ignite.sql.ColumnType;
 import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
 
 /**
  * Jdbc result set implementation.
@@ -236,6 +237,27 @@ public class JdbcResultSet implements ResultSet {
         initColumnOrder(jdbcMeta);
     }
 
+    /**
+     * Creates new result set.
+     *
+     * @exception SQLException if a database access error occurs
+     */
+    @TestOnly
+    JdbcResultSet(List<List<Object>> rows, List<JdbcColumnMeta> meta, 
JdbcStatement stmt) throws SQLException {
+        this.stmt = stmt;
+        cursorId = null;
+
+        finished = true;
+        hasResultSet = true;
+        hasNextResult = false;
+        holdsResource = false;
+
+        this.rowsIter = rows.iterator();
+        this.jdbcMeta = new JdbcResultSetMetadata(meta);
+
+        initColumnOrder(jdbcMeta);
+    }
+
     boolean holdResults() {
         return rows != null;
     }
@@ -287,6 +309,7 @@ public class JdbcResultSet implements ResultSet {
     @Override
     public boolean next() throws SQLException {
         ensureNotClosed();
+
         if ((rowsIter == null || !rowsIter.hasNext()) && !finished) {
             try {
                 JdbcQueryFetchResult res = cursorHandler.fetchAsync(new 
JdbcFetchQueryResultsRequest(cursorId, fetchSize)).get();
@@ -383,7 +406,6 @@ public class JdbcResultSet implements ResultSet {
     @Override
     public boolean wasNull() throws SQLException {
         ensureNotClosed();
-        ensureHasCurrentRow();
 
         return wasNull;
     }
@@ -1807,24 +1829,32 @@ public class JdbcResultSet implements ResultSet {
     /** {@inheritDoc} */
     @Override
     public void updateRef(int colIdx, Ref x) throws SQLException {
+        ensureNotClosed();
+
         throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
     }
 
     /** {@inheritDoc} */
     @Override
     public void updateRef(String colLb, Ref x) throws SQLException {
+        ensureNotClosed();
+
         throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
     }
 
     /** {@inheritDoc} */
     @Override
     public void updateBlob(int colIdx, Blob x) throws SQLException {
+        ensureNotClosed();
+
         throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
     }
 
     /** {@inheritDoc} */
     @Override
     public void updateBlob(String colLb, Blob x) throws SQLException {
+        ensureNotClosed();
+
         throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
     }
 
@@ -1863,12 +1893,16 @@ public class JdbcResultSet implements ResultSet {
     /** {@inheritDoc} */
     @Override
     public void updateClob(int colIdx, Clob x) throws SQLException {
+        ensureNotClosed();
+
         throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
     }
 
     /** {@inheritDoc} */
     @Override
     public void updateClob(String colLb, Clob x) throws SQLException {
+        ensureNotClosed();
+
         throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
     }
 
@@ -1907,12 +1941,16 @@ public class JdbcResultSet implements ResultSet {
     /** {@inheritDoc} */
     @Override
     public void updateArray(int colIdx, Array x) throws SQLException {
+        ensureNotClosed();
+
         throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
     }
 
     /** {@inheritDoc} */
     @Override
     public void updateArray(String colLb, Array x) throws SQLException {
+        ensureNotClosed();
+
         throw new SQLFeatureNotSupportedException("Updates are not 
supported.");
     }
 
diff --git 
a/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/JdbcResultSetBaseSelfTest.java
 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/JdbcResultSetBaseSelfTest.java
new file mode 100644
index 00000000000..0b20789a76c
--- /dev/null
+++ 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/JdbcResultSetBaseSelfTest.java
@@ -0,0 +1,1102 @@
+/*
+ * 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.any;
+import static org.hamcrest.Matchers.containsString;
+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.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Date;
+import java.sql.NClob;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+import org.apache.ignite.sql.ColumnType;
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+/**
+ * Compatibility tests to ensure that both JDBC result set adapters behave 
identically for core java.sql.ResultSet methods (excluding
+ * metadata checks).
+ */
+public abstract class JdbcResultSetBaseSelfTest extends BaseIgniteAbstractTest 
{
+
+    protected abstract ResultSet createResultSet(
+            @Nullable ZoneId zoneId,
+            List<ColumnDefinition> cols,
+            List<List<Object>> rows
+    ) throws SQLException;
+
+    private ResultSet createSingleRow(ColumnDefinition col, @Nullable Object 
value) throws SQLException {
+        return createMultiRow(new ColumnDefinition[]{col}, new 
Object[]{value});
+    }
+
+    private ResultSet createMultiRow(@Nullable ZoneId zoneId, 
ColumnDefinition[] columns, Object[] values) throws SQLException {
+        if (columns.length != values.length) {
+            throw new IllegalArgumentException("cols, values must have the 
same length");
+        }
+
+        List<ColumnDefinition> cols = Arrays.asList(columns);
+        List<List<Object>> rows = new ArrayList<>();
+        rows.add(Arrays.asList(values));
+
+        return createResultSet(zoneId, cols, rows);
+    }
+
+    private ResultSet createMultiRow(ColumnDefinition[] columns, Object[] 
values) throws SQLException {
+        return createMultiRow(null, columns, values);
+    }
+
+    @Test
+    public void getNotSupportedTypes() throws SQLException {
+        try (ResultSet rs = createSingleRow(new ColumnDefinition("C", 
ColumnType.STRING, 3, 0, false), "ABC")) {
+            assertTrue(rs.next());
+
+            expectNotSupported(() -> rs.getArray(1));
+            expectNotSupported(() -> rs.getArray("C"));
+
+            expectNotSupported(() -> rs.getAsciiStream(1));
+            expectNotSupported(() -> rs.getAsciiStream("C"));
+
+            expectNotSupported(() -> rs.getBinaryStream(1));
+            expectNotSupported(() -> rs.getBinaryStream("C"));
+
+            expectNotSupported(() -> rs.getBlob(1));
+            expectNotSupported(() -> rs.getBlob("C"));
+
+            expectNotSupported(() -> rs.getClob(1));
+            expectNotSupported(() -> rs.getClob("C"));
+
+            expectNotSupported(() -> rs.getCharacterStream(1));
+            expectNotSupported(() -> rs.getCharacterStream("C"));
+
+            expectNotSupported(() -> rs.getNCharacterStream(1));
+            expectNotSupported(() -> rs.getNCharacterStream("C"));
+
+            expectNotSupported(() -> rs.getNClob(1));
+            expectNotSupported(() -> rs.getNClob("C"));
+
+            expectNotSupported(() -> rs.getRef(1));
+            expectNotSupported(() -> rs.getRef("C"));
+
+            expectNotSupported(() -> rs.getRowId(1));
+            expectNotSupported(() -> rs.getRowId("C"));
+
+            expectNotSupported(() -> rs.getSQLXML(1));
+            expectNotSupported(() -> rs.getSQLXML("C"));
+
+            expectNotSupported(() -> rs.getObject(1, Map.of()));
+            expectNotSupported(() -> rs.getObject("C", Map.of()));
+        }
+    }
+
+    @Test
+    public void getNotImplemented() throws SQLException {
+        try (ResultSet rs = createSingleRow(new ColumnDefinition("C", 
ColumnType.STRING, 3, 0, false), "ABC")) {
+
+            expectNotSupported(() -> rs.getAsciiStream(1));
+            expectNotSupported(() -> rs.getAsciiStream("C"));
+
+            expectNotSupported(() -> rs.getBinaryStream(1));
+            expectNotSupported(() -> rs.getBinaryStream("C"));
+
+            expectNotSupported(() -> rs.getCharacterStream(1));
+            expectNotSupported(() -> rs.getCharacterStream("C"));
+        }
+    }
+
+    @Test
+    public void updateMethodsAreNotSupported() throws Exception {
+        try (ResultSet rs = createSingleRow(new ColumnDefinition("C", 
ColumnType.STRING, 3, 0, false), "ABC")) {
+            assertTrue(rs.next());
+
+            expectNotSupported(() -> rs.updateBoolean(1, true));
+            expectNotSupported(() -> rs.updateBoolean("C", true));
+
+            expectNotSupported(() -> rs.updateByte(1, (byte) 0));
+            expectNotSupported(() -> rs.updateByte("C", (byte) 0));
+
+            expectNotSupported(() -> rs.updateShort(1, (short) 0));
+            expectNotSupported(() -> rs.updateShort("C", (short) 0));
+
+            expectNotSupported(() -> rs.updateInt(1, 0));
+            expectNotSupported(() -> rs.updateInt("C", 0));
+
+            expectNotSupported(() -> rs.updateLong(1, 0));
+            expectNotSupported(() -> rs.updateLong("C", 0));
+
+            expectNotSupported(() -> rs.updateFloat(1, 0.0f));
+            expectNotSupported(() -> rs.updateFloat("C", 0.0f));
+
+            expectNotSupported(() -> rs.updateDouble(1, 0.0));
+            expectNotSupported(() -> rs.updateDouble("C", 0.0));
+
+            expectNotSupported(() -> rs.updateString(1, ""));
+            expectNotSupported(() -> rs.updateString("C", ""));
+
+            expectNotSupported(() -> rs.updateTime(1, new Time(0)));
+            expectNotSupported(() -> rs.updateTime("C", new Time(0)));
+
+            expectNotSupported(() -> rs.updateDate(1, new Date(0)));
+            expectNotSupported(() -> rs.updateDate("C", new Date(0)));
+
+            expectNotSupported(() -> rs.updateTimestamp(1, new Timestamp(0)));
+            expectNotSupported(() -> rs.updateTimestamp("C", new 
Timestamp(0)));
+
+            expectNotSupported(() -> rs.updateBytes(1, new byte[]{1}));
+            expectNotSupported(() -> rs.updateBytes("C", new byte[]{1}));
+
+            expectNotSupported(() -> rs.updateArray(1, null));
+            expectNotSupported(() -> rs.updateArray("C", null));
+
+            expectNotSupported(() -> rs.updateBlob(1, (Blob) null));
+            expectNotSupported(() -> rs.updateBlob(1, (InputStream) null));
+            expectNotSupported(() -> rs.updateBlob(1, null, 0L));
+            expectNotSupported(() -> rs.updateBlob("C", (Blob) null));
+            expectNotSupported(() -> rs.updateBlob("C", (InputStream) null));
+            expectNotSupported(() -> rs.updateBlob("C", null, 0L));
+
+            expectNotSupported(() -> rs.updateClob(1, (Clob) null));
+            expectNotSupported(() -> rs.updateClob(1, (Reader) null));
+            expectNotSupported(() -> rs.updateClob(1, null, 0L));
+            expectNotSupported(() -> rs.updateClob("C", (Clob) null));
+            expectNotSupported(() -> rs.updateClob("C", (Reader) null));
+            expectNotSupported(() -> rs.updateClob("C", null, 0L));
+
+            expectNotSupported(() -> rs.updateNClob(1, (NClob) null));
+            expectNotSupported(() -> rs.updateNClob(1, (Reader) null));
+            expectNotSupported(() -> rs.updateNClob(1, null, 0L));
+            expectNotSupported(() -> rs.updateNClob("C", (NClob) null));
+            expectNotSupported(() -> rs.updateNClob("C", (Reader) null));
+            expectNotSupported(() -> rs.updateNClob("C", null, 0L));
+
+            expectNotSupported(() -> rs.updateAsciiStream(1, null));
+            expectNotSupported(() -> rs.updateAsciiStream(1, null, 0));
+            expectNotSupported(() -> rs.updateAsciiStream(1, null, 0L));
+            expectNotSupported(() -> rs.updateAsciiStream("C", null));
+            expectNotSupported(() -> rs.updateAsciiStream("C", null, 0));
+            expectNotSupported(() -> rs.updateAsciiStream("C", null, 0L));
+
+            expectNotSupported(() -> rs.updateBinaryStream(1, null));
+            expectNotSupported(() -> rs.updateBinaryStream(1, null, 0));
+            expectNotSupported(() -> rs.updateBinaryStream(1, null, 0L));
+            expectNotSupported(() -> rs.updateBinaryStream("C", null));
+            expectNotSupported(() -> rs.updateBinaryStream("C", null, 0));
+            expectNotSupported(() -> rs.updateBinaryStream("C", null, 0L));
+
+            expectNotSupported(() -> rs.updateCharacterStream(1, null));
+            expectNotSupported(() -> rs.updateCharacterStream(1, null, 0));
+            expectNotSupported(() -> rs.updateCharacterStream(1, null, 0L));
+            expectNotSupported(() -> rs.updateCharacterStream("C", null));
+            expectNotSupported(() -> rs.updateCharacterStream("C", null, 0));
+            expectNotSupported(() -> rs.updateCharacterStream("C", null, 0L));
+
+            expectNotSupported(() -> rs.updateNCharacterStream(1, null));
+            expectNotSupported(() -> rs.updateNCharacterStream(1, null, 0));
+            expectNotSupported(() -> rs.updateNCharacterStream(1, null, 0L));
+            expectNotSupported(() -> rs.updateNCharacterStream("C", null));
+            expectNotSupported(() -> rs.updateNCharacterStream("C", null, 0));
+            expectNotSupported(() -> rs.updateNCharacterStream("C", null, 0L));
+
+            expectNotSupported(() -> rs.updateRef(1, null));
+            expectNotSupported(() -> rs.updateRef("C", null));
+
+            expectNotSupported(() -> rs.updateRowId(1, null));
+            expectNotSupported(() -> rs.updateRowId("C", null));
+
+            expectNotSupported(() -> rs.updateNString(1, null));
+            expectNotSupported(() -> rs.updateNString("C", null));
+
+            expectNotSupported(() -> rs.updateSQLXML(1, null));
+            expectNotSupported(() -> rs.updateSQLXML("C", null));
+
+            expectNotSupported(() -> rs.updateObject(1, null));
+            expectNotSupported(() -> rs.updateObject(1, null, 0));
+            expectNotSupported(() -> rs.updateObject("C", null));
+            expectNotSupported(() -> rs.updateObject("C", null, 0));
+
+            expectNotSupported(() -> rs.updateBigDecimal(1, null));
+            expectNotSupported(() -> rs.updateBigDecimal("C", null));
+
+            expectNotSupported(() -> rs.updateNull(1));
+            expectNotSupported(() -> rs.updateNull("C"));
+
+            expectNotSupported(rs::cancelRowUpdates);
+
+            expectNotSupported(rs::updateRow);
+
+            expectNotSupported(rs::deleteRow);
+
+            expectNotSupported(rs::insertRow);
+
+            expectNotSupported(rs::moveToInsertRow);
+        }
+    }
+
+    @Test
+    public void navigationMethods() throws SQLException {
+        try (ResultSet rs = createResultSet(null,
+                List.of(new ColumnDefinition("C", ColumnType.STRING, 3, 0, 
false)),
+                List.of(List.of("A"), List.of("B")))
+        ) {
+            assertTrue(rs.isBeforeFirst());
+            assertFalse(rs.isAfterLast());
+            assertFalse(rs.isFirst());
+            assertFalse(rs.isLast());
+            assertEquals(0, rs.getRow());
+
+            assertTrue(rs.next());
+
+            assertFalse(rs.isBeforeFirst());
+            assertFalse(rs.isAfterLast());
+            assertTrue(rs.isFirst());
+            assertFalse(rs.isLast());
+            assertEquals(1, rs.getRow());
+
+            assertTrue(rs.next());
+
+            assertFalse(rs.isBeforeFirst());
+            assertFalse(rs.isAfterLast());
+            assertFalse(rs.isFirst());
+            assertTrue(rs.isLast());
+            assertEquals(2, rs.getRow());
+
+            assertFalse(rs.next());
+
+            assertFalse(rs.isBeforeFirst());
+            assertTrue(rs.isAfterLast());
+            assertFalse(rs.isFirst());
+            assertFalse(rs.isLast());
+            assertEquals(0, rs.getRow());
+        }
+
+        try (ResultSet rs = createResultSet(null,
+                List.of(new ColumnDefinition("C", ColumnType.STRING, 3, 0, 
false)),
+                List.of(List.of("A"), List.of("B")))
+        ) {
+            String error = "Result set is forward-only.";
+
+            expectSqlException(rs::first, error);
+            expectSqlException(rs::afterLast, error);
+            expectSqlException(rs::last, error);
+
+            assertTrue(rs.next());
+
+            expectSqlException(rs::first, error);
+            expectSqlException(rs::afterLast, error);
+            expectSqlException(rs::last, error);
+
+            expectSqlException(() -> rs.absolute(0), error);
+            expectSqlException(() -> rs.relative(0), error);
+            expectSqlException(rs::previous, error);
+
+            assertEquals(ResultSet.FETCH_FORWARD, rs.getFetchDirection());
+            rs.setFetchDirection(ResultSet.FETCH_FORWARD);
+            assertEquals(ResultSet.FETCH_FORWARD, rs.getFetchDirection());
+
+            assertEquals(ResultSet.CONCUR_READ_ONLY, rs.getConcurrency());
+            assertEquals(ResultSet.TYPE_FORWARD_ONLY, rs.getType());
+            assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, 
rs.getHoldability());
+
+            expectSqlException(rs::moveToCurrentRow, "The result set 
concurrency is CONCUR_READ_ONLY");
+        }
+    }
+
+    @ParameterizedTest
+    @EnumSource(names = {"PERIOD", "DURATION"}, mode = EnumSource.Mode.EXCLUDE)
+    public void wasNull(ColumnType columnType) throws SQLException {
+        Object value;
+        switch (columnType) {
+            case NULL:
+                value = null;
+                break;
+            case BOOLEAN:
+                value = true;
+                break;
+            case INT8:
+                value = (byte) 1;
+                break;
+            case INT16:
+                value = (short) 1;
+                break;
+            case INT32:
+                value = 1;
+                break;
+            case INT64:
+                value = 1L;
+                break;
+            case FLOAT:
+                value = 0.0f;
+                break;
+            case DOUBLE:
+                value = 0.0d;
+                break;
+            case DECIMAL:
+                value = BigDecimal.ZERO;
+                break;
+            case DATE:
+                value = LocalDate.now();
+                break;
+            case TIME:
+                value = LocalTime.now();
+                break;
+            case DATETIME:
+                value = LocalDateTime.now();
+                break;
+            case TIMESTAMP:
+                value = Instant.now();
+                break;
+            case UUID:
+                value = new UUID(1, 1);
+                break;
+            case STRING:
+                value = "";
+                break;
+            case BYTE_ARRAY:
+                value = new byte[0];
+                break;
+            case PERIOD:
+            case DURATION:
+            default:
+                throw new IllegalArgumentException("Unexpected type: " + 
columnType);
+        }
+
+        try (ResultSet rs = createResultSet(null,
+                List.of(new ColumnDefinition("C", columnType, 0, 0, true)),
+                List.of(Collections.singletonList(value),
+                        Collections.singletonList(null),
+                        Collections.singletonList(value)
+                )
+        )) {
+            // Allow to access wasNull for non-positioned result set.
+            assertFalse(rs.wasNull());
+
+            // First row
+            assertTrue(rs.next());
+            // Allow to access wasNull, if no getter was set as well.
+            assertFalse(rs.wasNull());
+
+            // Read a column, so we can call wasNull
+            Object val1 = rs.getObject(1);
+            assertEquals(val1 == null, rs.wasNull());
+
+            // Second row
+            assertTrue(rs.next());
+            // Result should not change
+            assertEquals(val1 == null, rs.wasNull());
+
+            Object val2 = rs.getObject(1);
+            assertNull(val2);
+            assertTrue(rs.wasNull());
+
+            // Third row
+            assertTrue(rs.next());
+            // Result should not change
+            assertTrue(rs.wasNull());
+
+            Object val3 = rs.getObject(1);
+            assertEquals(val3 == null, rs.wasNull());
+
+            // Move past the last row
+            assertFalse(rs.next());
+            // Result should not change
+            assertEquals(val3 == null, rs.wasNull());
+        }
+    }
+
+    @ParameterizedTest
+    @EnumSource(names = {"PERIOD", "DURATION"}, mode = EnumSource.Mode.EXCLUDE)
+    public void wasNullPositional(ColumnType columnType) throws SQLException {
+        try (ResultSet rs = createSingleRow(new ColumnDefinition("C", 
columnType, 0, 0, false), null)) {
+            assertTrue(rs.next());
+
+            switch (columnType) {
+                case NULL:
+                    assertNull(rs.getObject(1));
+                    assertTrue(rs.wasNull());
+                    break;
+                case BOOLEAN:
+                    assertFalse(rs.getBoolean(1));
+                    assertTrue(rs.wasNull());
+                    break;
+                case INT8:
+                    assertEquals(0, rs.getByte(1));
+                    assertTrue(rs.wasNull());
+                    break;
+                case INT16:
+                    assertEquals(0, rs.getShort(1));
+                    assertTrue(rs.wasNull());
+                    break;
+                case INT32:
+                    assertEquals(0, rs.getInt(1));
+                    assertTrue(rs.wasNull());
+                    break;
+                case INT64:
+                    assertEquals(0, rs.getLong(1));
+                    assertTrue(rs.wasNull());
+                    break;
+                case FLOAT:
+                    assertEquals(0, rs.getFloat(1));
+                    assertTrue(rs.wasNull());
+                    break;
+                case DOUBLE:
+                    assertEquals(0, rs.getDouble(1));
+                    assertTrue(rs.wasNull());
+                    break;
+                case DECIMAL:
+                    assertNull(rs.getBigDecimal(1));
+                    assertTrue(rs.wasNull());
+                    break;
+                case DATE:
+                    assertNull(rs.getDate(1));
+                    assertTrue(rs.wasNull());
+                    break;
+                case TIME:
+                    assertNull(rs.getTime(1));
+                    assertTrue(rs.wasNull());
+                    break;
+                case DATETIME:
+                    assertNull(rs.getTimestamp(1));
+                    assertTrue(rs.wasNull());
+                    break;
+                case TIMESTAMP:
+                    assertNull(rs.getTimestamp(1));
+                    assertTrue(rs.wasNull());
+                    break;
+                case UUID:
+                    assertNull(rs.getObject(1, UUID.class));
+                    assertTrue(rs.wasNull());
+                    break;
+                case STRING:
+                    assertNull(rs.getString(1));
+                    assertTrue(rs.wasNull());
+                    break;
+                case BYTE_ARRAY:
+                    assertNull(rs.getBytes(1));
+                    assertTrue(rs.wasNull());
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unexpected type: " + 
columnType);
+            }
+
+            assertFalse(rs.next());
+            assertTrue(rs.wasNull());
+        }
+    }
+
+    @ParameterizedTest
+    @EnumSource(names = {"PERIOD", "DURATION"}, mode = EnumSource.Mode.EXCLUDE)
+    public void wasNullNamed(ColumnType columnType) throws SQLException {
+        try (ResultSet rs = createSingleRow(new ColumnDefinition("C", 
columnType, 0, 0, false), null)) {
+            assertTrue(rs.next());
+
+            switch (columnType) {
+                case NULL:
+                    assertNull(rs.getObject("C"));
+                    assertTrue(rs.wasNull());
+                    break;
+                case BOOLEAN:
+                    assertFalse(rs.getBoolean("C"));
+                    assertTrue(rs.wasNull());
+                    break;
+                case INT8:
+                    assertEquals(0, rs.getByte("C"));
+                    assertTrue(rs.wasNull());
+                    break;
+                case INT16:
+                    assertEquals(0, rs.getShort("C"));
+                    assertTrue(rs.wasNull());
+                    break;
+                case INT32:
+                    assertEquals(0, rs.getInt("C"));
+                    assertTrue(rs.wasNull());
+                    break;
+                case INT64:
+                    assertEquals(0, rs.getLong("C"));
+                    assertTrue(rs.wasNull());
+                    break;
+                case FLOAT:
+                    assertEquals(0, rs.getFloat("C"));
+                    assertTrue(rs.wasNull());
+                    break;
+                case DOUBLE:
+                    assertEquals(0, rs.getDouble("C"));
+                    assertTrue(rs.wasNull());
+                    break;
+                case DECIMAL:
+                    assertNull(rs.getBigDecimal("C"));
+                    assertTrue(rs.wasNull());
+                    break;
+                case DATE:
+                    assertNull(rs.getDate("C"));
+                    assertTrue(rs.wasNull());
+                    break;
+                case TIME:
+                    assertNull(rs.getTime("C"));
+                    assertTrue(rs.wasNull());
+                    break;
+                case DATETIME:
+                    assertNull(rs.getTimestamp("C"));
+                    assertTrue(rs.wasNull());
+                    break;
+                case TIMESTAMP:
+                    assertNull(rs.getTimestamp("C"));
+                    assertTrue(rs.wasNull());
+                    break;
+                case UUID:
+                    assertNull(rs.getObject(1, UUID.class));
+                    assertTrue(rs.wasNull());
+                    break;
+                case STRING:
+                    assertNull(rs.getString("C"));
+                    assertTrue(rs.wasNull());
+                    break;
+                case BYTE_ARRAY:
+                    assertNull(rs.getBytes("C"));
+                    assertTrue(rs.wasNull());
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unexpected type: " + 
columnType);
+            }
+
+            assertFalse(rs.next());
+            assertTrue(rs.wasNull());
+        }
+    }
+
+    @Test
+    public void getUnknownColumn() throws SQLException {
+        try (ResultSet rs = createSingleRow(new ColumnDefinition("C", 
ColumnType.BOOLEAN, 0, 0, false), 1)) {
+            assertTrue(rs.next());
+
+            expectInvalidColumn(rs::getBoolean, -1);
+            expectInvalidColumn(rs::getBoolean, 2);
+            expectInvalidColumn(rs::getBoolean, "X");
+
+            expectInvalidColumn(rs::getByte, -1);
+            expectInvalidColumn(rs::getByte, 2);
+            expectInvalidColumn(rs::getByte, "X");
+
+            expectInvalidColumn(rs::getShort, -1);
+            expectInvalidColumn(rs::getShort, 2);
+            expectInvalidColumn(rs::getShort, "X");
+
+            expectInvalidColumn(rs::getInt, -1);
+            expectInvalidColumn(rs::getInt, 2);
+            expectInvalidColumn(rs::getInt, "X");
+
+            expectInvalidColumn(rs::getLong, -1);
+            expectInvalidColumn(rs::getLong, 2);
+            expectInvalidColumn(rs::getLong, "X");
+
+            expectInvalidColumn(rs::getFloat, -1);
+            expectInvalidColumn(rs::getFloat, 2);
+            expectInvalidColumn(rs::getFloat, "X");
+
+            expectInvalidColumn(rs::getDouble, -1);
+            expectInvalidColumn(rs::getDouble, 2);
+            expectInvalidColumn(rs::getDouble, "X");
+
+            expectInvalidColumn(rs::getBigDecimal, -1);
+            expectInvalidColumn(rs::getBigDecimal, 2);
+            expectInvalidColumn(rs::getBigDecimal, "X");
+
+            expectInvalidColumn(rs::getDate, -1);
+            expectInvalidColumn(rs::getDate, 2);
+            expectInvalidColumn(rs::getDate, "X");
+
+            expectInvalidColumn(rs::getTime, -1);
+            expectInvalidColumn(rs::getTime, 2);
+            expectInvalidColumn(rs::getTime, "X");
+
+            expectInvalidColumn(rs::getTimestamp, -1);
+            expectInvalidColumn(rs::getTimestamp, 2);
+            expectInvalidColumn(rs::getTimestamp, "X");
+
+            expectInvalidColumn(rs::getString, -1);
+            expectInvalidColumn(rs::getString, 2);
+            expectInvalidColumn(rs::getString, "X");
+
+            expectInvalidColumn(rs::getNString, -1);
+            expectInvalidColumn(rs::getNString, 2);
+            expectInvalidColumn(rs::getNString, "X");
+
+            expectInvalidColumn(rs::getObject, -1);
+            expectInvalidColumn(rs::getObject, 2);
+            expectInvalidColumn(rs::getObject, "X");
+
+            expectSqlException(() -> rs.getObject(-1, Integer.class), "Invalid 
column index: -1");
+            expectSqlException(() -> rs.getObject(2, Integer.class), "Invalid 
column index: 2");
+            expectSqlException(() -> rs.getObject("X", Integer.class), "Column 
not found: X");
+
+            expectSqlException(() -> rs.getObject(-1, Map.of()), "SQL 
structured type are not supported");
+            expectSqlException(() -> rs.getObject(2, Map.of()), "SQL 
structured type are not supported");
+            expectSqlException(() -> rs.getObject("X", Map.of()), "SQL 
structured type are not supported");
+        }
+    }
+
+    @Test
+    public void methodsArePositioned() throws SQLException {
+        try (ResultSet rs = createResultSet(null,
+                List.of(new ColumnDefinition("C", ColumnType.BOOLEAN, 0, 0, 
false)),
+                List.of())
+        ) {
+            expectPositioned(() -> rs.getBoolean(1));
+            expectPositioned(() -> rs.getBoolean("C"));
+
+            expectPositioned(() -> rs.getByte(1));
+            expectPositioned(() -> rs.getByte("C"));
+
+            expectPositioned(() -> rs.getShort(1));
+            expectPositioned(() -> rs.getShort("C"));
+
+            expectPositioned(() -> rs.getInt(1));
+            expectPositioned(() -> rs.getInt("C"));
+
+            expectPositioned(() -> rs.getLong(1));
+            expectPositioned(() -> rs.getLong("C"));
+
+            expectPositioned(() -> rs.getFloat(1));
+            expectPositioned(() -> rs.getFloat("C"));
+
+            expectPositioned(() -> rs.getDouble(1));
+            expectPositioned(() -> rs.getDouble("C"));
+
+            expectPositioned(() -> rs.getBigDecimal(1));
+            expectPositioned(() -> rs.getBigDecimal("C"));
+
+            //noinspection deprecation
+            expectPositioned(() -> rs.getBigDecimal(1, 2));
+            //noinspection deprecation
+            expectPositioned(() -> rs.getBigDecimal("C", 3));
+
+            expectPositioned(() -> rs.getDate(1));
+            expectPositioned(() -> rs.getDate("C"));
+
+            expectPositioned(() -> rs.getTime(1));
+            expectPositioned(() -> rs.getTime("C"));
+
+            expectPositioned(() -> rs.getTimestamp(1));
+            expectPositioned(() -> rs.getTimestamp("C"));
+
+            expectPositioned(() -> rs.getString(1));
+            expectPositioned(() -> rs.getString("C"));
+
+            expectPositioned(() -> rs.getNString(1));
+            expectPositioned(() -> rs.getNString("C"));
+
+            expectPositioned(() -> rs.getObject(1));
+            expectPositioned(() -> rs.getObject("C"));
+
+            expectPositioned(() -> rs.getObject(1, Boolean.class));
+            expectPositioned(() -> rs.getObject("C", Boolean.class));
+
+            expectPositioned(() -> rs.getBytes(1));
+            expectPositioned(() -> rs.getBytes("C"));
+
+            expectPositioned(() -> rs.getURL(1));
+            expectPositioned(() -> rs.getURL("C"));
+
+            // Do not require positioning
+            assertThat(rs.getType(), any(Integer.class));
+
+            assertThat(rs.getConcurrency(), any(Integer.class));
+
+            assertNull(rs.getCursorName());
+
+            assertThat(rs.getFetchDirection(), any(Integer.class));
+
+            assertThat(rs.getFetchSize(), any(Integer.class));
+
+            assertThat(rs.getRow(), any(Integer.class));
+
+            assertThat(rs.isClosed(), any(Boolean.class));
+
+            assertThat(rs.getHoldability(), any(Integer.class));
+        }
+    }
+
+    @Test
+    public void warnings() throws SQLException {
+        try (ResultSet rs = createSingleRow(new ColumnDefinition("C", 
ColumnType.BOOLEAN, 0, 0, false), true)) {
+            assertNull(rs.getWarnings());
+
+            rs.clearWarnings();
+            assertNull(rs.getWarnings());
+        }
+    }
+
+    @Test
+    public void getMetadata() throws SQLException {
+        try (ResultSet rs = createSingleRow(new ColumnDefinition("C", 
ColumnType.BOOLEAN, 0, 0, false), true)) {
+            ResultSetMetaData metaData = rs.getMetaData();
+            assertEquals(1, metaData.getColumnCount());
+        }
+    }
+
+    @Test
+    public void close() throws SQLException {
+        try (ResultSet rs = createResultSet(null,
+                List.of(new ColumnDefinition("C", ColumnType.BOOLEAN, 0, 0, 
false)),
+                List.of(List.of())
+        )) {
+            assertFalse(rs.isClosed());
+
+            rs.close();
+
+            assertTrue(rs.isClosed());
+
+            // Navigation
+            expectClosed(rs::next);
+            expectClosed(rs::previous);
+            expectClosed(rs::first);
+            expectClosed(rs::last);
+            expectClosed(rs::beforeFirst);
+            expectClosed(rs::afterLast);
+            expectClosed(() -> rs.absolute(1));
+            expectClosed(() -> rs.relative(1));
+
+            // Position/state queries and settings
+            expectClosed(rs::isBeforeFirst);
+            expectClosed(rs::isAfterLast);
+            expectClosed(rs::isFirst);
+            expectClosed(rs::isLast);
+            expectClosed(rs::getRow);
+            expectClosed(() -> rs.setFetchDirection(ResultSet.FETCH_FORWARD));
+            expectClosed(rs::getFetchDirection);
+            expectClosed(() -> rs.setFetchSize(1));
+            expectClosed(rs::getFetchSize);
+            expectClosed(rs::getType);
+            expectClosed(rs::getConcurrency);
+            expectClosed(rs::rowUpdated);
+            expectClosed(rs::rowInserted);
+            expectClosed(rs::rowDeleted);
+            expectClosed(rs::getHoldability);
+
+            // Metadata and lookup
+            expectClosed(rs::getMetaData);
+            expectClosed(() -> rs.findColumn("C"));
+
+            // Warnings and cursor name
+            expectClosed(rs::getWarnings);
+            expectClosed(rs::clearWarnings);
+            expectClosed(rs::getCursorName);
+
+            // Statement
+            expectClosed(rs::getStatement);
+
+            // Read methods
+            expectClosed(() -> rs.getBoolean(1));
+            expectClosed(() -> rs.getBoolean("C"));
+
+            expectClosed(() -> rs.getByte(1));
+            expectClosed(() -> rs.getByte("C"));
+
+            expectClosed(() -> rs.getShort(1));
+            expectClosed(() -> rs.getShort("C"));
+
+            expectClosed(() -> rs.getInt(1));
+            expectClosed(() -> rs.getInt("C"));
+
+            expectClosed(() -> rs.getLong(1));
+            expectClosed(() -> rs.getLong("C"));
+
+            expectClosed(() -> rs.getFloat(1));
+            expectClosed(() -> rs.getFloat("C"));
+
+            expectClosed(() -> rs.getDouble(1));
+            expectClosed(() -> rs.getDouble("C"));
+
+            expectClosed(() -> rs.getBigDecimal(1));
+            expectClosed(() -> rs.getBigDecimal("C"));
+
+            //noinspection deprecation
+            expectClosed(() -> rs.getBigDecimal(1, 2));
+            //noinspection deprecation
+            expectClosed(() -> rs.getBigDecimal("C", 3));
+
+            expectClosed(() -> rs.getDate(1));
+            expectClosed(() -> rs.getDate("C"));
+            expectClosed(() -> rs.getDate(1, Calendar.getInstance()));
+            expectClosed(() -> rs.getDate("C", Calendar.getInstance()));
+
+            expectClosed(() -> rs.getTime(1));
+            expectClosed(() -> rs.getTime("C"));
+
+            expectClosed(() -> rs.getTime(1, Calendar.getInstance()));
+            expectClosed(() -> rs.getTime("C", Calendar.getInstance()));
+
+            expectClosed(() -> rs.getTimestamp(1));
+            expectClosed(() -> rs.getTimestamp("C"));
+
+            expectClosed(() -> rs.getTimestamp(1, Calendar.getInstance()));
+            expectClosed(() -> rs.getTimestamp("C", Calendar.getInstance()));
+
+            expectClosed(() -> rs.getString(1));
+            expectClosed(() -> rs.getString("C"));
+
+            expectClosed(() -> rs.getNString(1));
+            expectClosed(() -> rs.getNString("C"));
+
+            expectClosed(() -> rs.getObject(1));
+            expectClosed(() -> rs.getObject("C"));
+
+            expectClosed(() -> rs.getObject(1, Boolean.class));
+            expectClosed(() -> rs.getObject("C", Boolean.class));
+
+            expectClosed(() -> rs.getBytes(1));
+            expectClosed(() -> rs.getBytes("C"));
+
+            expectClosed(() -> rs.getURL(1));
+            expectClosed(() -> rs.getURL("C"));
+
+            expectClosed(() -> rs.getAsciiStream(1));
+            expectClosed(() -> rs.getAsciiStream("C"));
+
+            //noinspection deprecation
+            expectClosed(() -> rs.getUnicodeStream(1));
+            //noinspection deprecation
+            expectClosed(() -> rs.getUnicodeStream("C"));
+
+            expectClosed(() -> rs.getBinaryStream(1));
+            expectClosed(() -> rs.getBinaryStream("C"));
+
+            expectClosed(() -> rs.getCharacterStream(1));
+            expectClosed(() -> rs.getCharacterStream("C"));
+
+            expectClosed(() -> rs.getNCharacterStream(1));
+            expectClosed(() -> rs.getNCharacterStream("C"));
+
+            expectClosed(() -> rs.getClob(1));
+            expectClosed(() -> rs.getClob("C"));
+
+            expectClosed(() -> rs.getBlob(1));
+            expectClosed(() -> rs.getBlob("C"));
+
+            expectClosed(() -> rs.getRef(1));
+            expectClosed(() -> rs.getRef("C"));
+
+            expectClosed(() -> rs.getArray(1));
+            expectClosed(() -> rs.getArray("C"));
+
+            expectClosed(() -> rs.getRowId(1));
+            expectClosed(() -> rs.getRowId("C"));
+
+            expectClosed(() -> rs.getNClob(1));
+            expectClosed(() -> rs.getNClob("C"));
+
+            expectClosed(() -> rs.getSQLXML(1));
+            expectClosed(() -> rs.getSQLXML("C"));
+
+            expectClosed(rs::wasNull);
+
+            // Update methods
+
+            expectClosed(() -> rs.updateBoolean(1, true));
+            expectClosed(() -> rs.updateBoolean("C", true));
+
+            expectClosed(() -> rs.updateByte(1, (byte) 0));
+            expectClosed(() -> rs.updateByte("C", (byte) 0));
+
+            expectClosed(() -> rs.updateShort(1, (short) 0));
+            expectClosed(() -> rs.updateShort("C", (short) 0));
+
+            expectClosed(() -> rs.updateInt(1, 0));
+            expectClosed(() -> rs.updateInt("C", 0));
+
+            expectClosed(() -> rs.updateLong(1, 0));
+            expectClosed(() -> rs.updateLong("C", 0));
+
+            expectClosed(() -> rs.updateFloat(1, 0.0f));
+            expectClosed(() -> rs.updateFloat("C", 0.0f));
+
+            expectClosed(() -> rs.updateDouble(1, 0.0));
+            expectClosed(() -> rs.updateDouble("C", 0.0));
+
+            expectClosed(() -> rs.updateString(1, ""));
+            expectClosed(() -> rs.updateString("C", ""));
+
+            expectClosed(() -> rs.updateTime(1, new Time(0)));
+            expectClosed(() -> rs.updateTime("C", new Time(0)));
+
+            expectClosed(() -> rs.updateDate(1, new Date(0)));
+            expectClosed(() -> rs.updateDate("C", new Date(0)));
+
+            expectClosed(() -> rs.updateTimestamp(1, new Timestamp(0)));
+            expectClosed(() -> rs.updateTimestamp("C", new Timestamp(0)));
+
+            expectClosed(() -> rs.updateBytes(1, new byte[]{1}));
+            expectClosed(() -> rs.updateBytes("C", new byte[]{1}));
+
+            expectClosed(() -> rs.updateArray(1, null));
+            expectClosed(() -> rs.updateArray("C", null));
+
+            expectClosed(() -> rs.updateBlob(1, (Blob) null));
+            expectClosed(() -> rs.updateBlob(1, (InputStream) null));
+            expectClosed(() -> rs.updateBlob(1, null, 0L));
+            expectClosed(() -> rs.updateBlob("C", (Blob) null));
+            expectClosed(() -> rs.updateBlob("C", (InputStream) null));
+            expectClosed(() -> rs.updateBlob("C", null, 0L));
+
+            expectClosed(() -> rs.updateClob(1, (Clob) null));
+            expectClosed(() -> rs.updateClob(1, (Reader) null));
+            expectClosed(() -> rs.updateClob(1, null, 0L));
+            expectClosed(() -> rs.updateClob("C", (Clob) null));
+            expectClosed(() -> rs.updateClob("C", (Reader) null));
+            expectClosed(() -> rs.updateClob("C", null, 0L));
+
+            expectClosed(() -> rs.updateNClob(1, (NClob) null));
+            expectClosed(() -> rs.updateNClob(1, (Reader) null));
+            expectClosed(() -> rs.updateNClob(1, null, 0L));
+            expectClosed(() -> rs.updateNClob("C", (NClob) null));
+            expectClosed(() -> rs.updateNClob("C", (Reader) null));
+            expectClosed(() -> rs.updateNClob("C", null, 0L));
+
+            expectClosed(() -> rs.updateAsciiStream(1, null));
+            expectClosed(() -> rs.updateAsciiStream(1, null, 0));
+            expectClosed(() -> rs.updateAsciiStream(1, null, 0L));
+            expectClosed(() -> rs.updateAsciiStream("C", null));
+            expectClosed(() -> rs.updateAsciiStream("C", null, 0));
+            expectClosed(() -> rs.updateAsciiStream("C", null, 0L));
+
+            expectClosed(() -> rs.updateBinaryStream(1, null));
+            expectClosed(() -> rs.updateBinaryStream(1, null, 0));
+            expectClosed(() -> rs.updateBinaryStream(1, null, 0L));
+            expectClosed(() -> rs.updateBinaryStream("C", null));
+            expectClosed(() -> rs.updateBinaryStream("C", null, 0));
+            expectClosed(() -> rs.updateBinaryStream("C", null, 0L));
+
+            expectClosed(() -> rs.updateCharacterStream(1, null));
+            expectClosed(() -> rs.updateCharacterStream(1, null, 0));
+            expectClosed(() -> rs.updateCharacterStream(1, null, 0L));
+            expectClosed(() -> rs.updateCharacterStream("C", null));
+            expectClosed(() -> rs.updateCharacterStream("C", null, 0));
+            expectClosed(() -> rs.updateCharacterStream("C", null, 0L));
+
+            expectClosed(() -> rs.updateNCharacterStream(1, null));
+            expectClosed(() -> rs.updateNCharacterStream(1, null, 0));
+            expectClosed(() -> rs.updateNCharacterStream(1, null, 0L));
+            expectClosed(() -> rs.updateNCharacterStream("C", null));
+            expectClosed(() -> rs.updateNCharacterStream("C", null, 0));
+            expectClosed(() -> rs.updateNCharacterStream("C", null, 0L));
+
+            expectClosed(() -> rs.updateRef(1, null));
+            expectClosed(() -> rs.updateRef("C", null));
+
+            expectClosed(() -> rs.updateRowId(1, null));
+            expectClosed(() -> rs.updateRowId("C", null));
+
+            expectClosed(() -> rs.updateNString(1, null));
+            expectClosed(() -> rs.updateNString("C", null));
+
+            expectClosed(() -> rs.updateSQLXML(1, null));
+            expectClosed(() -> rs.updateSQLXML("C", null));
+
+            expectClosed(() -> rs.updateObject(1, null));
+            expectClosed(() -> rs.updateObject(1, null, 0));
+            expectClosed(() -> rs.updateObject("C", null));
+            expectClosed(() -> rs.updateObject("C", null, 0));
+
+            expectClosed(() -> rs.updateBigDecimal(1, null));
+            expectClosed(() -> rs.updateBigDecimal("C", null));
+
+            expectClosed(() -> rs.updateNull(1));
+            expectClosed(() -> rs.updateNull("C"));
+
+            expectClosed(rs::cancelRowUpdates);
+            expectClosed(rs::updateRow);
+            expectClosed(rs::deleteRow);
+            expectClosed(rs::insertRow);
+            expectClosed(rs::moveToInsertRow);
+        }
+    }
+
+    private static void expectNotSupported(ResultSetMethod m) {
+        assertThrows(SQLFeatureNotSupportedException.class, m::call);
+    }
+
+    private static void expectSqlException(ResultSetMethod m, String message) {
+        SQLException err = assertThrows(SQLException.class, m::call);
+        assertThat(err.getMessage(), containsString(message));
+    }
+
+    private static void expectInvalidColumn(ResultSetPositionalMethod m, int 
column) {
+        SQLException err = assertThrows(SQLException.class, () -> 
m.call(column));
+        assertThat(err.getMessage(), containsString("Invalid column index: " + 
column));
+    }
+
+    private static void expectInvalidColumn(ResultSetNamedMethod m, String 
column) {
+        SQLException err = assertThrows(SQLException.class, () -> 
m.call(column));
+        assertThat(err.getMessage(), containsString("Column not found: " + 
column));
+    }
+
+    private static void expectPositioned(ResultSetMethod m) {
+        SQLException err = assertThrows(SQLException.class, m::call);
+        assertThat(err.getMessage(), containsString("Result set is not 
positioned on a row."));
+    }
+
+    private static void expectClosed(ResultSetMethod method) {
+        expectSqlException(method, "Result set is closed.");
+    }
+
+    /** Result set function. */
+    @FunctionalInterface
+    protected interface ResultSetMethod {
+        void call() throws SQLException;
+    }
+
+    /** Result set function. */
+    @FunctionalInterface
+    protected interface ResultSetPositionalMethod {
+        void call(int column) throws SQLException;
+    }
+
+    /** Result set function. */
+    @FunctionalInterface
+    protected interface ResultSetNamedMethod {
+        void call(String column) throws SQLException;
+    }
+}
diff --git 
a/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/JdbcResultSetSelfTest.java
 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/JdbcResultSetSelfTest.java
new file mode 100644
index 00000000000..773c4359235
--- /dev/null
+++ 
b/modules/jdbc/src/test/java/org/apache/ignite/internal/jdbc/JdbcResultSetSelfTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.mockito.Mockito.when;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.ignite.internal.jdbc.proto.event.JdbcColumnMeta;
+import org.jetbrains.annotations.Nullable;
+import org.mockito.Mockito;
+
+/**
+ * Runs JdbcResultSetCompatibilityBaseTest against legacy 
org.apache.ignite.internal.jdbc.JdbcResultSet.
+ */
+public class JdbcResultSetSelfTest extends JdbcResultSetBaseSelfTest {
+    @Override
+    protected ResultSet createResultSet(@Nullable ZoneId zoneId, 
List<ColumnDefinition> cols, List<List<Object>> rows) throws SQLException {
+        // Convert ColumnSpec to legacy JDBC metadata
+        List<JdbcColumnMeta> jdbcCols = new ArrayList<>();
+        for (ColumnDefinition c : cols) {
+            boolean nullable = true;
+            jdbcCols.add(new JdbcColumnMeta(c.label, c.schema, c.table, 
c.column, c.type, c.precision, c.scale, nullable));
+        }
+
+        JdbcStatement statement = Mockito.mock(JdbcStatement.class);
+
+        if (zoneId != null) {
+            JdbcConnection connection = Mockito.mock(JdbcConnection.class);
+
+            ConnectionPropertiesImpl connectionProperties = new 
ConnectionPropertiesImpl();
+            connectionProperties.setConnectionTimeZone(zoneId);
+
+            when(statement.getConnection()).thenReturn(connection);
+            
when(connection.connectionProperties()).thenReturn(connectionProperties);
+        }
+
+        
when(statement.getResultSetType()).thenReturn(ResultSet.TYPE_FORWARD_ONLY);
+
+        try {
+            return new JdbcResultSet(rows, jdbcCols, statement);
+        } catch (SQLException e) {
+            throw new RuntimeException("Unexpected exception", e);
+        }
+    }
+}

Reply via email to