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

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


The following commit(s) were added to refs/heads/main by this push:
     new b94a39d6365 IGNITE-27753 IgniteCatalog DSL class inheritance mapping 
(#7536)
b94a39d6365 is described below

commit b94a39d63657e585b1083874d4670a789b40ec73
Author: Vadim Kolodin <[email protected]>
AuthorDate: Thu Feb 19 15:58:01 2026 +0400

    IGNITE-27753 IgniteCatalog DSL class inheritance mapping (#7536)
---
 .../apache/ignite/table/mapper/MapperBuilder.java  |  17 +-
 .../ignite/internal/catalog/ItCatalogDslTest.java  |  19 ++
 .../ignite/internal/catalog/PojoExtended.java      |  77 +++++
 .../catalog/sql/CreateFromAnnotationsImpl.java     |  14 +-
 .../catalog/sql/CreateFromAnnotationsTest.java     |  74 +++++
 .../internal/schema/marshaller/MarshallerTest.java |  18 +
 .../internal/schema/marshaller/Inheritance.java    | 368 +++++++++++++++++++++
 .../ignite/internal/marshaller/FieldAccessor.java  |  19 +-
 8 files changed, 602 insertions(+), 4 deletions(-)

diff --git 
a/modules/api/src/main/java/org/apache/ignite/table/mapper/MapperBuilder.java 
b/modules/api/src/main/java/org/apache/ignite/table/mapper/MapperBuilder.java
index b8aa1028a82..21038eb9000 100644
--- 
a/modules/api/src/main/java/org/apache/ignite/table/mapper/MapperBuilder.java
+++ 
b/modules/api/src/main/java/org/apache/ignite/table/mapper/MapperBuilder.java
@@ -22,9 +22,11 @@ import static 
org.apache.ignite.lang.util.IgniteNameUtils.parseIdentifier;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.AbstractMap.SimpleEntry;
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import org.apache.ignite.catalog.annotations.Column;
@@ -283,7 +285,7 @@ public final class MapperBuilder<T> {
         }
 
         if (automapFlag) {
-            Arrays.stream(targetType.getDeclaredFields())
+            getAllFields(targetType).stream()
                     .filter(fld -> !Modifier.isStatic(fld.getModifiers()) && 
!Modifier.isTransient(fld.getModifiers()))
                     .map(MapperBuilder::getColumnToFieldMapping)
                     .filter(entry -> !fields.contains(entry.getValue()))
@@ -300,4 +302,15 @@ public final class MapperBuilder<T> {
         var columnName = column != null && !column.value().isEmpty() ? 
column.value() : fldName;
         return new SimpleEntry<>(parseIdentifier(columnName), fldName);
     }
+
+    /**
+     * Gets all fields of the given class and its parents (if any).
+     */
+    private static List<Field> getAllFields(Class<?> clazz) {
+        var result = new ArrayList<Field>();
+        for (Class<?> current = clazz; current != Object.class; current = 
current.getSuperclass()) {
+            Collections.addAll(result, current.getDeclaredFields());
+        }
+        return result;
+    }
 }
diff --git 
a/modules/catalog-dsl/src/integrationTest/java/org/apache/ignite/internal/catalog/ItCatalogDslTest.java
 
b/modules/catalog-dsl/src/integrationTest/java/org/apache/ignite/internal/catalog/ItCatalogDslTest.java
index 2aae592eaf9..c05288112e0 100644
--- 
a/modules/catalog-dsl/src/integrationTest/java/org/apache/ignite/internal/catalog/ItCatalogDslTest.java
+++ 
b/modules/catalog-dsl/src/integrationTest/java/org/apache/ignite/internal/catalog/ItCatalogDslTest.java
@@ -71,6 +71,8 @@ class ItCatalogDslTest extends ClusterPerClassIntegrationTest 
{
 
     static final String POJO_RECORD_TABLE_NAME = "pojo_record_test";
 
+    static final String POJO_RECORD_EXTENDED_TABLE_NAME = 
"pojo_record_extended_test";
+
     static final String EXPLICIT_QUOTES_TABLE_NAME = 
"explicit_quotes_test_table";
 
     static final String ZONE_NAME = "ZONE_TEST";
@@ -790,6 +792,23 @@ class ItCatalogDslTest extends 
ClusterPerClassIntegrationTest {
         }
     }
 
+    @Test
+    void inheritance() throws Exception {
+        CompletableFuture<Table> tableFuture = 
catalog().createTableAsync(PojoExtended.class);
+        assertThat(tableFuture, will(not(nullValue())));
+
+        sql("insert into "
+                + POJO_RECORD_EXTENDED_TABLE_NAME
+                + " (id, id_str, f_name, l_name, str, f_name_extended) values 
(1, '1', 'f', 'l', 's', 'e')");
+        List<List<Object>> rows = sql("select id, id_str, f_name, l_name, str, 
f_name_extended from "
+                + POJO_RECORD_EXTENDED_TABLE_NAME);
+
+        assertThat(rows, contains(List.of(1, "1", "f", "l", "s", "e")));
+
+        PojoExtended pojo = new PojoExtended(1, "1", "f", "l", "s", "e");
+        assertThat(tableFuture.get().recordView(PojoExtended.class).get(null, 
pojo), is(pojo));
+    }
+
     private static IgniteCatalog catalog() {
         return CLUSTER.node(0).catalog();
     }
diff --git 
a/modules/catalog-dsl/src/integrationTest/java/org/apache/ignite/internal/catalog/PojoExtended.java
 
b/modules/catalog-dsl/src/integrationTest/java/org/apache/ignite/internal/catalog/PojoExtended.java
new file mode 100644
index 00000000000..18a1c000137
--- /dev/null
+++ 
b/modules/catalog-dsl/src/integrationTest/java/org/apache/ignite/internal/catalog/PojoExtended.java
@@ -0,0 +1,77 @@
+/*
+ * 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.catalog;
+
+import static 
org.apache.ignite.internal.TestDefaultProfilesNames.DEFAULT_AIPERSIST_PROFILE_NAME;
+
+import java.util.Objects;
+import org.apache.ignite.catalog.SortOrder;
+import org.apache.ignite.catalog.annotations.Column;
+import org.apache.ignite.catalog.annotations.ColumnRef;
+import org.apache.ignite.catalog.annotations.Index;
+import org.apache.ignite.catalog.annotations.Table;
+import org.apache.ignite.catalog.annotations.Zone;
+
+/**
+ * A POJO class representing the whole record with inheritance.
+ */
+@Table(
+        value = ItCatalogDslTest.POJO_RECORD_EXTENDED_TABLE_NAME,
+        zone = @Zone(value = ItCatalogDslTest.ZONE_NAME, storageProfiles = 
DEFAULT_AIPERSIST_PROFILE_NAME),
+        colocateBy = @ColumnRef("id"),
+        indexes = @Index(value = "ix_pojo", columns = {
+                @ColumnRef("f_name"),
+                @ColumnRef(value = "l_name", sort = SortOrder.DESC),
+                @ColumnRef(value = "f_name_extended")
+        })
+)
+class PojoExtended extends Pojo {
+    @Column("f_name_extended")
+    String firstNameExtended;
+
+    PojoExtended() {}
+
+    PojoExtended(Integer id, String idStr, String firstName, String lastName, 
String str, String firstNameExtended) {
+        super(id, idStr, firstName, lastName, str);
+        this.firstNameExtended = firstNameExtended;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        PojoExtended pojo = (PojoExtended) o;
+        return Objects.equals(id, pojo.id)
+                && Objects.equals(idStr, pojo.idStr)
+                && Objects.equals(firstName, pojo.firstName)
+                && Objects.equals(lastName, pojo.lastName)
+                && Objects.equals(str, pojo.str)
+                && Objects.equals(firstNameExtended, pojo.firstNameExtended);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, idStr, firstName, lastName, str, 
firstNameExtended);
+    }
+}
diff --git 
a/modules/catalog-dsl/src/main/java/org/apache/ignite/internal/catalog/sql/CreateFromAnnotationsImpl.java
 
b/modules/catalog-dsl/src/main/java/org/apache/ignite/internal/catalog/sql/CreateFromAnnotationsImpl.java
index df167cc7b38..524d4f17f52 100644
--- 
a/modules/catalog-dsl/src/main/java/org/apache/ignite/internal/catalog/sql/CreateFromAnnotationsImpl.java
+++ 
b/modules/catalog-dsl/src/main/java/org/apache/ignite/internal/catalog/sql/CreateFromAnnotationsImpl.java
@@ -25,6 +25,7 @@ import static 
org.apache.ignite.table.mapper.Mapper.nativelySupported;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import org.apache.ignite.catalog.ColumnSorted;
 import org.apache.ignite.catalog.ColumnType;
@@ -201,7 +202,7 @@ class CreateFromAnnotationsImpl extends 
AbstractCatalogQuery<TableZoneId> {
     }
 
     private static void processColumnsInPojo(CreateTableImpl createTable, 
Class<?> clazz, List<ColumnSorted> idColumns) {
-        for (Field f : clazz.getDeclaredFields()) {
+        for (Field f : getAllFields(clazz)) {
             if (Modifier.isStatic(f.getModifiers()) || 
Modifier.isTransient(f.getModifiers())) {
                 continue;
             }
@@ -230,4 +231,15 @@ class CreateFromAnnotationsImpl extends 
AbstractCatalogQuery<TableZoneId> {
         }
     }
 
+    /**
+     * Gets all fields of the given class and its parents (if any).
+     */
+    private static List<Field> getAllFields(Class<?> clazz) {
+        var result = new ArrayList<Field>();
+        for (Class<?> current = clazz; current != Object.class; current = 
current.getSuperclass()) {
+            Collections.addAll(result, current.getDeclaredFields());
+        }
+        return result;
+    }
+
 }
diff --git 
a/modules/catalog-dsl/src/test/java/org/apache/ignite/internal/catalog/sql/CreateFromAnnotationsTest.java
 
b/modules/catalog-dsl/src/test/java/org/apache/ignite/internal/catalog/sql/CreateFromAnnotationsTest.java
index 5e6a577d9de..c9e93a9790e 100644
--- 
a/modules/catalog-dsl/src/test/java/org/apache/ignite/internal/catalog/sql/CreateFromAnnotationsTest.java
+++ 
b/modules/catalog-dsl/src/test/java/org/apache/ignite/internal/catalog/sql/CreateFromAnnotationsTest.java
@@ -219,6 +219,61 @@ class CreateFromAnnotationsTest {
         );
     }
 
+    @Test
+    void inheritance() {
+        // Record class
+        CreateFromAnnotationsImpl fromAnnotations = 
createTable().processRecordClass(PojoValueExtended.class);
+        String sqlFromAnnotations = fromAnnotations.toString();
+
+        assertThat(
+                sqlFromAnnotations,
+                is("CREATE TABLE IF NOT EXISTS PUBLIC.POJO_VALUE_EXTENDED_TEST 
("
+                        + "F_NAME_EXTENDED VARCHAR, F_NAME VARCHAR, L_NAME 
VARCHAR, STR VARCHAR);"
+                        + System.lineSeparator()
+                        + "CREATE INDEX IF NOT EXISTS IX_POJO ON 
PUBLIC.POJO_VALUE_EXTENDED_TEST (F_NAME, L_NAME DESC, F_NAME_EXTENDED);")
+        );
+
+        TableDefinition definition = 
TableDefinition.builder("pojo_value_extended_test")
+                .ifNotExists()
+                .record(PojoValueExtended.class)
+                .index("ix_pojo", IndexType.DEFAULT, column("f_name"), 
column("l_name").desc(), column("f_name_extended"))
+                .build();
+        CreateFromDefinitionImpl fromDefinition = new 
CreateFromDefinitionImpl(null).from(definition);
+        String sqlFromDefinition = fromDefinition.toString();
+
+        assertThat(
+                sqlFromAnnotations,
+                is(sqlFromDefinition)
+        );
+
+        // Key Value class
+        fromAnnotations = 
createTable().processKeyValueClasses(PojoKeyExtended.class, 
PojoValueExtended.class);
+        sqlFromAnnotations = fromAnnotations.toString();
+
+        assertThat(
+                sqlFromAnnotations,
+                is("CREATE TABLE IF NOT EXISTS PUBLIC.POJO_VALUE_EXTENDED_TEST 
("
+                        + "ID_STR_EXTENDED VARCHAR(20), ID INT, ID_STR 
VARCHAR(20), F_NAME_EXTENDED VARCHAR, F_NAME VARCHAR, "
+                        + "L_NAME VARCHAR, STR VARCHAR, PRIMARY KEY 
(ID_STR_EXTENDED, ID, ID_STR));"
+                        + System.lineSeparator()
+                        + "CREATE INDEX IF NOT EXISTS IX_POJO ON 
PUBLIC.POJO_VALUE_EXTENDED_TEST (F_NAME, L_NAME DESC, F_NAME_EXTENDED);")
+        );
+
+        definition = TableDefinition.builder("pojo_value_extended_test")
+                .ifNotExists()
+                .key(PojoKeyExtended.class)
+                .value(PojoValueExtended.class)
+                .index("ix_pojo", IndexType.DEFAULT, column("f_name"), 
column("l_name").desc(), column("f_name_extended"))
+                .build();
+        fromDefinition = new CreateFromDefinitionImpl(null).from(definition);
+        sqlFromDefinition = fromDefinition.toString();
+
+        assertThat(
+                sqlFromAnnotations,
+                is(sqlFromDefinition)
+        );
+    }
+
     @SuppressWarnings("unused")
     private static class PojoKey {
         @Id
@@ -402,6 +457,25 @@ class CreateFromAnnotationsTest {
         String str;
     }
 
+    static class PojoKeyExtended extends PojoKey {
+        @Id
+        @Column(value = "id_str_extended", length = 20)
+        String idStrExtended;
+    }
+
+    @Table(
+            value = "pojo_value_extended_test",
+            indexes = @Index(value = "ix_pojo", columns = {
+                    @ColumnRef("f_name"),
+                    @ColumnRef(value = "l_name", sort = SortOrder.DESC),
+                    @ColumnRef("f_name_extended"),
+            })
+    )
+    static class PojoValueExtended extends PojoValue {
+        @Column("f_name_extended")
+        String firstNameExtended;
+    }
+
     private static CreateFromAnnotationsImpl createTable() {
         return new CreateFromAnnotationsImpl(null);
     }
diff --git 
a/modules/java-records-tests/src/test/java/org/apache/ignite/internal/schema/marshaller/MarshallerTest.java
 
b/modules/java-records-tests/src/test/java/org/apache/ignite/internal/schema/marshaller/MarshallerTest.java
index 6fee1caa1d8..3402948d238 100644
--- 
a/modules/java-records-tests/src/test/java/org/apache/ignite/internal/schema/marshaller/MarshallerTest.java
+++ 
b/modules/java-records-tests/src/test/java/org/apache/ignite/internal/schema/marshaller/MarshallerTest.java
@@ -20,6 +20,8 @@ package org.apache.ignite.internal.schema.marshaller;
 import static 
org.apache.ignite.internal.schema.marshaller.AssertMarshaller.assertMarshaller;
 import static 
org.apache.ignite.internal.schema.marshaller.AssertMarshaller.assertMarshallerThrows;
 
+import org.apache.ignite.internal.schema.marshaller.Inheritance.Kv;
+import org.apache.ignite.internal.schema.marshaller.Inheritance.Kv.ChildV;
 import org.apache.ignite.internal.schema.marshaller.Records.ComponentsEmpty;
 import org.apache.ignite.internal.schema.marshaller.Records.ComponentsExact;
 import org.apache.ignite.internal.schema.marshaller.Records.ComponentsNarrow;
@@ -171,4 +173,20 @@ class MarshallerTest extends BaseIgniteAbstractTest {
                 new C()
         );
     }
+
+    @Test
+    void inheritance() {
+        // recordView
+        assertMarshaller(new Inheritance.AbstractParent.Child(1, "a", "b"));
+        assertMarshaller(new Inheritance.RegularParent.Child(1, "a", "b"));
+        assertMarshaller(new Inheritance.MultipleParent.Child(1, "a", "b"));
+        assertMarshaller(new Inheritance.ParentWithPrivateField.Child(1, "a", 
"b"));
+
+        // kvView
+        assertMarshaller(new Kv.Key(1), new ChildV("a", "b"));
+
+        String msgSubstring = "Unsupported class. Only top-level or nested 
static classes are supported";
+        assertMarshallerThrows(IllegalArgumentException.class, msgSubstring, 
new Inheritance.LocalClass().newChild(1, "a", "b"));
+    }
+
 }
diff --git 
a/modules/java-records-tests/src/testFixtures/java/org/apache/ignite/internal/schema/marshaller/Inheritance.java
 
b/modules/java-records-tests/src/testFixtures/java/org/apache/ignite/internal/schema/marshaller/Inheritance.java
new file mode 100644
index 00000000000..72369d3616e
--- /dev/null
+++ 
b/modules/java-records-tests/src/testFixtures/java/org/apache/ignite/internal/schema/marshaller/Inheritance.java
@@ -0,0 +1,368 @@
+/*
+ * 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.schema.marshaller;
+
+import java.util.Objects;
+import org.apache.ignite.catalog.annotations.Column;
+
+/**
+ * Declaration variants fixture. Each nested class contains various cases.
+ */
+class Inheritance {
+
+    Inheritance() {}
+
+    static class AbstractParent {
+        abstract static class Parent {
+            @Column("key")
+            Integer key;
+
+            @Column("val")
+            String val;
+
+            Parent() {}
+
+            Parent(Integer key, String val) {
+                this.key = key;
+                this.val = val;
+            }
+        }
+
+        static class Child extends Parent {
+            @Column("val2")
+            String val2;
+
+            Child() {}
+
+            Child(Integer key, String val, String val2) {
+                super(key, val);
+                this.val2 = val2;
+            }
+
+            @Override
+            public boolean equals(Object o) {
+                if (this == o) {
+                    return true;
+                }
+
+                if (o == null || getClass() != o.getClass()) {
+                    return false;
+                }
+                Child that = (Child) o;
+                return Objects.equals(key, that.key)
+                        && Objects.equals(this.val, that.val)
+                        && Objects.equals(this.val2, that.val2);
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(key, val, val2);
+            }
+        }
+    }
+
+    static class RegularParent {
+        static class Parent {
+            @Column("key")
+            Integer key;
+
+            @Column("val")
+            String val;
+
+            Parent() {}
+
+            Parent(Integer key, String val) {
+                this.key = key;
+                this.val = val;
+            }
+        }
+
+        static class Child extends Parent {
+            @Column("val2")
+            String val2;
+
+            Child() {}
+
+            Child(Integer key, String val, String val2) {
+                super(key, val);
+                this.val2 = val2;
+            }
+
+            @Override
+            public boolean equals(Object o) {
+                if (this == o) {
+                    return true;
+                }
+
+                if (o == null || getClass() != o.getClass()) {
+                    return false;
+                }
+                Child that = (Child) o;
+                return Objects.equals(key, that.key)
+                        && Objects.equals(this.val, that.val)
+                        && Objects.equals(this.val2, that.val2);
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(key, val, val2);
+            }
+        }
+    }
+
+    static class MultipleParent {
+        static class Parent1 {
+            @Column("key")
+            Integer key;
+
+            Parent1() {}
+
+            Parent1(Integer key) {
+                this.key = key;
+            }
+        }
+
+        static class Parent2 extends Parent1 {
+            @Column("val")
+            String val;
+
+            Parent2() {}
+
+            Parent2(Integer key, String val) {
+                super(key);
+                this.val = val;
+            }
+        }
+
+        static class Child extends Parent2 {
+            @Column("val2")
+            String val2;
+
+            Child() {}
+
+            Child(Integer key, String val, String val2) {
+                super(key, val);
+                this.val2 = val2;
+            }
+
+            @Override
+            public boolean equals(Object o) {
+                if (this == o) {
+                    return true;
+                }
+
+                if (o == null || getClass() != o.getClass()) {
+                    return false;
+                }
+                Child that = (Child) o;
+                return Objects.equals(key, that.key)
+                        && Objects.equals(this.val, that.val)
+                        && Objects.equals(this.val2, that.val2);
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(key, val, val2);
+            }
+        }
+
+    }
+
+    static class ParentWithPrivateField {
+        static class Parent {
+            @Column("key")
+            private Integer key;
+
+            @Column("val")
+            private String val;
+
+            Parent() {
+            }
+
+            Parent(Integer key, String val) {
+                this.key = key;
+                this.val = val;
+            }
+
+            Integer getKey() {
+                return key;
+            }
+
+            String getVal() {
+                return val;
+            }
+        }
+
+        static class Child extends Parent {
+            @Column("val2")
+            String val2;
+
+            Child() {
+            }
+
+            Child(Integer key, String val, String val2) {
+                super(key, val);
+                this.val2 = val2;
+            }
+
+            @Override
+            public boolean equals(Object o) {
+                if (this == o) {
+                    return true;
+                }
+
+                if (o == null || getClass() != o.getClass()) {
+                    return false;
+                }
+                Child that = (Child) o;
+                return Objects.equals(getKey(), that.getKey())
+                        && Objects.equals(this.getVal(), that.getVal())
+                        && Objects.equals(this.val2, that.val2);
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(getKey(), getVal(), val2);
+            }
+        }
+    }
+
+    static class LocalClass {
+        Child newChild(Integer key, String val, String val2) {
+            return new Child(key, val, val2);
+        }
+
+        class Parent {
+            @Column("key")
+            Integer key;
+
+            @Column("val")
+            String val;
+
+            Parent() {
+            }
+
+            Parent(Integer key, String val) {
+                this.key = key;
+                this.val = val;
+            }
+        }
+
+        class Child extends Parent {
+            @Column("val2")
+            String val2;
+
+            Child() {
+            }
+
+            Child(Integer key, String val, String val2) {
+                super(key, val);
+                this.val2 = val2;
+            }
+
+            @Override
+            public boolean equals(Object o) {
+                if (this == o) {
+                    return true;
+                }
+
+                if (o == null || getClass() != o.getClass()) {
+                    return false;
+                }
+                Child that = (Child) o;
+                return Objects.equals(key, that.key)
+                        && Objects.equals(this.val, that.val)
+                        && Objects.equals(this.val2, that.val2);
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(key, val, val2);
+            }
+        }
+    }
+
+    static class Kv {
+        static class Key {
+            @Column("key")
+            Integer key;
+
+            Key() {}
+
+            Key(Integer key) {
+                this.key = key;
+            }
+
+            @Override
+            public boolean equals(Object o) {
+                if (o == null || getClass() != o.getClass()) {
+                    return false;
+                }
+                Key that = (Key) o;
+                return Objects.equals(key, that.key);
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hashCode(key);
+            }
+        }
+
+        static class ParentV {
+            @Column("val")
+            String val;
+
+            ParentV() {}
+
+            ParentV(String val) {
+                this.val = val;
+            }
+        }
+
+        static class ChildV extends ParentV {
+            @Column("val2")
+            String val2;
+
+            ChildV() {}
+
+            ChildV(String val, String val2) {
+                super(val);
+                this.val2 = val2;
+            }
+
+            @Override
+            public boolean equals(Object o) {
+                if (this == o) {
+                    return true;
+                }
+
+                if (o == null || getClass() != o.getClass()) {
+                    return false;
+                }
+                ChildV that = (ChildV) o;
+                return Objects.equals(this.val, that.val)
+                        && Objects.equals(this.val2, that.val2);
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(val, val2);
+            }
+        }
+    }
+}
diff --git 
a/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/FieldAccessor.java
 
b/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/FieldAccessor.java
index 8075ff8106e..f74b0360c29 100644
--- 
a/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/FieldAccessor.java
+++ 
b/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/FieldAccessor.java
@@ -86,7 +86,7 @@ abstract class FieldAccessor {
             @Nullable TypeConverter<?, ?> typeConverter
     ) {
         try {
-            Field field = type.getDeclaredField(fldName);
+            Field field = getField(type, fldName);
 
             if (typeConverter == null) {
                 validateColumnType(col, field.getType());
@@ -135,6 +135,23 @@ abstract class FieldAccessor {
         }
     }
 
+    /**
+     * Gets an accessible field by name of the given class and its parents (if 
any).
+     */
+    private static Field getField(Class<?> clazz, String fieldName) throws 
NoSuchFieldException {
+        var current = clazz;
+        while (current != Object.class) {
+            try {
+                return current.getDeclaredField(fieldName);
+            } catch (NoSuchFieldException ignored) {
+                // ignore
+            }
+
+            current = current.getSuperclass();
+        }
+        throw new NoSuchFieldException("Field '" + fieldName + "' not found in 
class hierarchy " + clazz);
+    }
+
     /**
      * Create accessor for the field.
      *

Reply via email to