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

NSAmelchev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 5394a9fa23a IGNITE-28736 Fixed order of fields in 
BinaryType#fieldNames (#13411)
5394a9fa23a is described below

commit 5394a9fa23a67754e6fa75f2c2ff601cca1df662
Author: Nikita Amelchev <[email protected]>
AuthorDate: Mon Aug 3 13:58:03 2026 +0300

    IGNITE-28736 Fixed order of fields in BinaryType#fieldNames (#13411)
---
 .../java/org/apache/ignite/binary/BinaryType.java  |  6 ++-
 .../ignite/internal/binary/BinaryContext.java      |  2 +-
 .../ignite/internal/binary/BinaryMetadata.java     |  7 ++-
 .../internal/binary/BinaryMetadataCollector.java   |  6 +--
 .../MoveBinaryMetadataCompatibility.java           | 23 +++++++---
 .../org/apache/ignite/client/IgniteBinaryTest.java | 23 ++++++++++
 .../internal/binary/BinaryMarshallerSelfTest.java  | 51 +++++++++++++++++++++-
 .../ignite/internal/client/thin/ClientUtils.java   |  5 +--
 8 files changed, 103 insertions(+), 20 deletions(-)

diff --git 
a/modules/binary/api/src/main/java/org/apache/ignite/binary/BinaryType.java 
b/modules/binary/api/src/main/java/org/apache/ignite/binary/BinaryType.java
index e108349c9fb..147e69ba040 100644
--- a/modules/binary/api/src/main/java/org/apache/ignite/binary/BinaryType.java
+++ b/modules/binary/api/src/main/java/org/apache/ignite/binary/BinaryType.java
@@ -41,7 +41,11 @@ public interface BinaryType {
     public int typeId();
 
     /**
-     * Gets collection of all field names for this binary type.
+     * Gets collection of all field names for this binary type in the order in 
which the fields were initially
+     * registered.
+     * <p>
+     * Older Ignite versions did not guarantee field registration order. For 
metadata created or processed by such a
+     * version, the returned order is not guaranteed to match the original 
registration order.
      *
      * @return Collection of all field names for this binary type.
      */
diff --git 
a/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
 
b/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
index fa4bd41cc2c..f00e26fb26a 100644
--- 
a/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
+++ 
b/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
@@ -264,7 +264,7 @@ public class BinaryContext {
 
         
BinaryUtils.binariesFactory.predefinedTypes().forEach(this::registerPredefinedType);
 
-        // BinaryUtils.FIELDS_SORTED_ORDER support, since it uses TreeMap at 
BinaryMetadata.
+        // TreeMap and TreeSet binary write-replacement support.
         registerBinarilizableSystemClass(BinaryTreeMap.class);
         registerBinarilizableSystemClass(TreeMap.class);
         registerBinarilizableSystemClass(TreeSet.class);
diff --git 
a/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryMetadata.java
 
b/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryMetadata.java
index 601550ea63b..9fb8a918fa6 100644
--- 
a/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryMetadata.java
+++ 
b/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryMetadata.java
@@ -26,7 +26,6 @@ import java.io.ObjectOutput;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
@@ -53,7 +52,7 @@ public class BinaryMetadata implements Externalizable {
     @GridToStringInclude(sensitive = true)
     private String typeName;
 
-    /** Recorded object fields. */
+    /** Recorded object fields in initial registration order. */
     @GridToStringInclude(sensitive = true)
     private Map<String, BinaryFieldMetadata> fields;
 
@@ -117,7 +116,7 @@ public class BinaryMetadata implements Externalizable {
 
         this.typeId = typeId;
         this.typeName = typeName;
-        this.fields = fields;
+        this.fields = fields == null ? null : new LinkedHashMap<>(fields);
         this.affKeyFieldName = affKeyFieldName;
         this.schemas = schemas;
 
@@ -304,7 +303,7 @@ public class BinaryMetadata implements Externalizable {
         if (fieldsSize == -1)
             fields = null;
         else {
-            fields = new HashMap<>();
+            fields = new LinkedHashMap<>();
 
             for (int i = 0; i < fieldsSize; i++) {
                 String fieldName = CommonUtils.readString(in);
diff --git 
a/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryMetadataCollector.java
 
b/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryMetadataCollector.java
index 3700159b573..a946c5724ea 100644
--- 
a/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryMetadataCollector.java
+++ 
b/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryMetadataCollector.java
@@ -25,7 +25,7 @@ import java.sql.Time;
 import java.sql.Timestamp;
 import java.util.Collection;
 import java.util.Date;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.UUID;
 import org.apache.ignite.binary.BinaryObjectException;
@@ -46,8 +46,8 @@ class BinaryMetadataCollector implements BinaryWriter {
     /** Name mapper. */
     private final BinaryInternalMapper mapper;
 
-    /** Collected metadata. */
-    private final Map<String, BinaryFieldMetadata> meta = new HashMap<>();
+    /** Collected metadata in field write order. */
+    private final Map<String, BinaryFieldMetadata> meta = new 
LinkedHashMap<>();
 
     /** Schema builder. */
     private BinarySchema.Builder schemaBuilder = 
BinarySchema.Builder.newBuilder();
diff --git 
a/modules/compatibility/src/test/java/org/apache/ignite/compatibility/persistence/MoveBinaryMetadataCompatibility.java
 
b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/persistence/MoveBinaryMetadataCompatibility.java
index 94fe6130a03..8ab80b32ba1 100644
--- 
a/modules/compatibility/src/test/java/org/apache/ignite/compatibility/persistence/MoveBinaryMetadataCompatibility.java
+++ 
b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/persistence/MoveBinaryMetadataCompatibility.java
@@ -21,6 +21,7 @@ import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteBinary;
 import org.apache.ignite.IgniteCache;
 import org.apache.ignite.binary.BinaryObject;
+import org.apache.ignite.binary.BinaryObjectBuilder;
 import 
org.apache.ignite.compatibility.testframework.junits.SkipTestIfIsJdkNewer;
 import org.apache.ignite.configuration.DataRegionConfiguration;
 import org.apache.ignite.configuration.DataStorageConfiguration;
@@ -59,14 +60,14 @@ public class MoveBinaryMetadataCompatibility extends 
IgnitePersistenceCompatibil
     public void test() throws Exception {
         String typeName = "TestBinaryType";
 
-        String fieldName = "testField";
-
-        String fieldVal = "testVal";
+        String[] fieldNames = {"field9", "field8", "field0", "field1", 
"field2"};
 
         String cacheName = "testCache";
 
         String objKey = "obj";
 
+        String fieldOrderKey = "fieldOrder";
+
         String consid = "node1";
 
         // start old version and save some binary object to cache
@@ -83,11 +84,17 @@ public class MoveBinaryMetadataCompatibility extends 
IgnitePersistenceCompatibil
 
             IgniteBinary binary = ignite.binary();
 
-            BinaryObject test = binary.builder(typeName).setField(fieldName, 
fieldVal).build();
+            BinaryObjectBuilder builder = binary.builder(typeName);
+
+            for (String fieldName : fieldNames)
+                builder.setField(fieldName, fieldName);
+
+            BinaryObject test = builder.build();
 
             IgniteCache<Object, Object> cache = 
ignite.getOrCreateCache(cacheName).withKeepBinary();
 
             cache.put(objKey, test);
+            cache.put(fieldOrderKey, String.join(",", 
binary.type(typeName).fieldNames()));
         });
 
         stopAllGrids();
@@ -103,9 +110,13 @@ public class MoveBinaryMetadataCompatibility extends 
IgnitePersistenceCompatibil
         // check that binary object is still available in new version of grid
         BinaryObject obj = (BinaryObject)cache.get(objKey);
 
-        assertTrue(obj.hasField(fieldName));
+        for (String fieldName : fieldNames) {
+            assertTrue(obj.hasField(fieldName));
+
+            assertEquals(fieldName, obj.field(fieldName));
+        }
 
-        assertEquals(fieldVal, obj.field(fieldName));
+        assertEquals(cache.get(fieldOrderKey), String.join(",", 
obj.type().fieldNames()));
 
         newGrid.close();
     }
diff --git 
a/modules/core/src/test/java/org/apache/ignite/client/IgniteBinaryTest.java 
b/modules/core/src/test/java/org/apache/ignite/client/IgniteBinaryTest.java
index 2059d1ee137..8bb6781861b 100644
--- a/modules/core/src/test/java/org/apache/ignite/client/IgniteBinaryTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/client/IgniteBinaryTest.java
@@ -140,6 +140,29 @@ public class IgniteBinaryTest extends 
GridCommonAbstractTest {
         }
     }
 
+    /** Tests that binary type field order is preserved when metadata is read 
by a thin client. */
+    @Test
+    public void testBinaryTypeFieldOrder() throws Exception {
+        String typeName = "OrderedType";
+        String[] fieldNames = {"field9", "field8", "field0", "field1", 
"field2"};
+
+        try (Ignite ignite = Ignition.start(Config.getServerConfiguration())) {
+            BinaryObjectBuilder builder = ignite.binary().builder(typeName);
+
+            for (String fieldName : fieldNames)
+                builder.setField(fieldName, 0);
+
+            BinaryType serverType = builder.build().type();
+            Object[] serverFieldNames = serverType.fieldNames().toArray();
+
+            try (IgniteClient client = Ignition.startClient(new 
ClientConfiguration().setAddresses(Config.SERVER))) {
+                BinaryType clientType = 
client.binary().type(serverType.typeId());
+
+                assertArrayEquals(serverFieldNames, 
clientType.fieldNames().toArray());
+            }
+        }
+    }
+
     /**
      * Tests that {@code 
org.apache.ignite.cache.CacheInterceptor#onBeforePut(javax.cache.Cache.Entry, 
java.lang.Object)}
      * throws correct exception in case while cache operations are called from 
thin client. Only BinaryObject`s are
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryMarshallerSelfTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryMarshallerSelfTest.java
index 6f724f526e5..3e52bbc0e35 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryMarshallerSelfTest.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryMarshallerSelfTest.java
@@ -3770,6 +3770,37 @@ public class BinaryMarshallerSelfTest extends 
AbstractBinaryArraysTest {
         }
     }
 
+    /** @throws Exception If failed. */
+    @Test
+    public void testFieldOrderByBinarylizable() throws Exception {
+        BinaryObjectImpl binObj = marshal(new FieldOrderBinarylizable(), 
binaryMarshaller());
+
+        assertArrayEquals(FieldOrderBinarylizable.FIELD_NAMES, 
binObj.type().fieldNames().toArray());
+    }
+
+    /** @throws Exception If failed. */
+    @Test
+    public void testMetadataFieldOrderAfterSerialization() throws Exception {
+        String[] fieldNames = {"field9", "field8", "field0", "field1", 
"field2"};
+
+        Map<String, BinaryFieldMetadata> fields = new LinkedHashMap<>();
+
+        for (String fieldName : fieldNames)
+            fields.put(fieldName, new 
BinaryFieldMetadata(GridBinaryMarshaller.INT, fieldName.hashCode()));
+
+        BinaryMetadata meta = new BinaryMetadata(1, "type", fields, null, 
false, null);
+        BinaryMarshaller marsh = binaryMarshaller();
+        BinaryOutputStream out = BinaryStreams.outputStream(1024);
+
+        meta.writeTo(marsh.binaryMarshaller().writer(out));
+
+        BinaryMetadata restoredMeta = new BinaryMetadata();
+
+        
restoredMeta.readFrom(marsh.binaryMarshaller().reader(BinaryStreams.inputStream(out.array())));
+
+        assertArrayEquals(fieldNames, restoredMeta.fields().toArray());
+    }
+
     /**
      * @param obj Instance of the BinaryObjectImpl to offheap marshalling.
      * @param marsh Binary marshaller.
@@ -5065,8 +5096,24 @@ public class BinaryMarshallerSelfTest extends 
AbstractBinaryArraysTest {
         }
     }
 
-    /**
-     */
+    /** */
+    private static class FieldOrderBinarylizable implements Binarylizable {
+        /** Field names. */
+        private static final String[] FIELD_NAMES = {"field9", "field8", 
"field0", "field1", "field2"};
+
+        /** {@inheritDoc} */
+        @Override public void writeBinary(BinaryWriter writer) throws 
BinaryObjectException {
+            for (String fieldName : FIELD_NAMES)
+                writer.writeInt(fieldName, 0);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void readBinary(BinaryReader reader) throws 
BinaryObjectException {
+            // No-op.
+        }
+    }
+
+    /** */
     private static class CustomSerializedObject1 implements Binarylizable {
         /** */
         private int val;
diff --git 
a/modules/thin-client/impl/src/main/java/org/apache/ignite/internal/client/thin/ClientUtils.java
 
b/modules/thin-client/impl/src/main/java/org/apache/ignite/internal/client/thin/ClientUtils.java
index 474c4e45709..ac8a9a4c7d3 100644
--- 
a/modules/thin-client/impl/src/main/java/org/apache/ignite/internal/client/thin/ClientUtils.java
+++ 
b/modules/thin-client/impl/src/main/java/org/apache/ignite/internal/client/thin/ClientUtils.java
@@ -22,7 +22,6 @@ import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
@@ -146,7 +145,7 @@ public final class ClientUtils {
     }
 
     /**
-     * @return Deserialized map
+     * @return Deserialized map preserving entry order.
      */
     private static <K, V> Map<K, V> map(
         BinaryInputStream in,
@@ -155,7 +154,7 @@ public final class ClientUtils {
     ) {
         int cnt = in.readInt();
 
-        Map<K, V> map = new HashMap<>(cnt);
+        Map<K, V> map = new LinkedHashMap<>(cnt);
 
         for (int i = 0; i < cnt; i++)
             map.put(keyReader.apply(in), valReader.apply(in));

Reply via email to