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

dspavlov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite-teamcity-bot.git

commit f07fecd9e2c7d87659f6d43738d2a4e4ba2e7a77
Author: Dmitriy Pavlov <[email protected]>
AuthorDate: Wed Apr 29 20:36:53 2026 +0300

    IGNITE-21899 Make GridIntList migration reliable
---
 README.md                                          | 24 +++++++++++++++
 migrator/build.gradle                              |  4 +--
 .../apache/ignite/migrate/GridIntListMigrator.java | 16 ++++++++--
 .../org/apache/ignite/migrate/Transformer.java     | 36 +++++++++++++---------
 4 files changed, 60 insertions(+), 20 deletions(-)

diff --git a/README.md b/README.md
index fd565ec1..2721ed67 100644
--- a/README.md
+++ b/README.md
@@ -73,3 +73,27 @@ TC Bot integrations are placed in corresponding submodules
 | JIRA | [tcbot-jira](tcbot-jira) | [tcbot-jira-ignited](tcbot-jira-ignited)  |
 | GitHub | [tcbot-github](tcbot-github) | 
[tcbot-github-ignited](tcbot-github-ignited)  |
 
+## GridIntList migration
+
+The `migrate-GridIntList` database migration updates persisted TeamCity Bot 
data after replacing Ignite's internal
+`org.apache.ignite.internal.util.GridIntList` with the project-owned
+`org.apache.ignite.tcbot.common.util.GridIntList`.
+
+During startup, `DbMigrations` runs `GridIntListMigrator.migrateOnInstance` 
once and stores the migration marker only
+after the scan finishes successfully. The migrator iterates over Ignite cache 
entries in keep-binary mode, recursively
+checks cache values, nested binary objects, lists, sets, maps, and object 
arrays, and rebuilds only values that contain
+the legacy `GridIntList` type.
+
+For each legacy list, the migration preserves the logical list contents, not 
the backing array capacity. If normal
+deserialization is available, it reads the old object through 
`GridIntList.array()`. If binary fallback is needed, it
+reads both persisted fields, `arr` and `idx`, validates that `idx` is inside 
the backing array bounds, and copies only
+`arr[0..idx)`. The copied values are then written as the new TC Bot 
`GridIntList` type.
+
+The migration is intentionally fail-fast from the database marker point of 
view. Per-entry failures are logged with the
+cache name and key, counted, and reported after the scan. If any entry fails, 
the migration throws an exception and the
+`migrate-GridIntList` marker is not written to `apache.doneMigrations`, so the 
issue can be fixed and the migration can
+be retried instead of silently leaving mixed old and new data.
+
+The same migrator can also be run as a standalone tool from the `migrator` 
module against an Ignite work directory. The
+standalone module uses the same Ignite version as the rest of the project 
through the shared `ignVer` Gradle property.
+
diff --git a/migrator/build.gradle b/migrator/build.gradle
index 937af99e..bd541325 100644
--- a/migrator/build.gradle
+++ b/migrator/build.gradle
@@ -7,11 +7,11 @@ repositories {
 }
 
 dependencies {
-    implementation 'org.apache.ignite:ignite-core:2.16.0'
+    implementation "org.apache.ignite:ignite-core:$ignVer"
     implementation 'ch.qos.logback:logback-classic:1.2.3'
     implementation project(':tcbot-common')
 }
 
 application {
     mainClass = 'org.apache.ignite.migrate.GridIntListMigrator'
-}
\ No newline at end of file
+}
diff --git 
a/migrator/src/main/java/org/apache/ignite/migrate/GridIntListMigrator.java 
b/migrator/src/main/java/org/apache/ignite/migrate/GridIntListMigrator.java
index 14f5bd24..194e7cf6 100644
--- a/migrator/src/main/java/org/apache/ignite/migrate/GridIntListMigrator.java
+++ b/migrator/src/main/java/org/apache/ignite/migrate/GridIntListMigrator.java
@@ -156,6 +156,7 @@ public final class GridIntListMigrator {
 
         Transformer transformer = new Transformer(verbose);
         long totalUpdated = 0;
+        long totalFailed = 0;
 
         for (String cacheName : cacheNames) {
             IgniteCache<Object, Object> rawCache = ignite.cache(cacheName);
@@ -172,6 +173,7 @@ public final class GridIntListMigrator {
 
             AtomicLong scanned = new AtomicLong();
             AtomicLong updated = new AtomicLong();
+            AtomicLong failed = new AtomicLong();
 
             try (QueryCursor<Cache.Entry<Object, Object>> cur = c.query(q)) {
                 for (Cache.Entry<Object, Object> e : cur) {
@@ -196,16 +198,24 @@ public final class GridIntListMigrator {
 
                     }
                     catch (Throwable t) {
-                        log.warn("Entry migration failed, skipping. Cause: 
{}", t.toString());
+                        failed.incrementAndGet();
+
+                        log.error("Entry migration failed [cache={}, key={}]", 
cacheName, e.getKey(), t);
 
                         scanned.incrementAndGet();
                     }
                 }
             }
 
-            log.info("Done {}: scanned={} updated={}", cacheName, 
scanned.get(), updated.get());
+            log.info("Done {}: scanned={} updated={} failed={}", cacheName, 
scanned.get(), updated.get(), failed.get());
 
             totalUpdated += updated.get();
+            totalFailed += failed.get();
+        }
+
+        if (totalFailed > 0) {
+            throw new IllegalStateException("GridIntList migration failed for 
" + totalFailed +
+                " entries. Migration marker will not be written.");
         }
 
         log.info("GridIntList migration finished. Total updated: {}", 
totalUpdated);
@@ -239,4 +249,4 @@ public final class GridIntListMigrator {
     public static Logger GetMigratorLogger() {
         return log;
     }
-}
\ No newline at end of file
+}
diff --git a/migrator/src/main/java/org/apache/ignite/migrate/Transformer.java 
b/migrator/src/main/java/org/apache/ignite/migrate/Transformer.java
index eedc142a..47863c36 100644
--- a/migrator/src/main/java/org/apache/ignite/migrate/Transformer.java
+++ b/migrator/src/main/java/org/apache/ignite/migrate/Transformer.java
@@ -19,6 +19,7 @@ package org.apache.ignite.migrate;
 
 import java.lang.reflect.Constructor;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -63,13 +64,16 @@ public final class Transformer {
     Transformer(boolean verb) {
         verbose = verb;
         newKeysCachedConstruct = findNewKeysConstruct();
+
+        if (newKeysCachedConstruct == null)
+            throw new IllegalStateException("New keys type " + NEW_KEYS_TYPE + 
" is not available.");
     }
 
     /**
      * Recursively transforms a value.
      * <p>
      * Rules:
-     * - BinaryObject of OLD_KEYS_TYPE -> build NEW_KEYS_TYPE or fallback to 
int[].
+     * - BinaryObject of OLD_KEYS_TYPE -> build NEW_KEYS_TYPE.
      * - Java object of OLD_KEYS_TYPE -> same replacement.
      * - BinaryObject (other type) -> rebuild only if some field changed.
      * - List/Set/Map/Object[] -> rebuild container only if any element 
changed.
@@ -227,23 +231,28 @@ public final class Transformer {
                 log.info("Deserialize fallback: {}", t.toString());
         }
 
-        // Hardcode ("arr") based on GridIntList fields
+        // Hardcode ("arr", "idx") based on GridIntList fields.
         Collection<String> childFields = oldKeys.type().fieldNames();
-        if (childFields.contains("arr")) {
+        if (childFields.contains("arr") && childFields.contains("idx")) {
             int[] arr = oldKeys.field("arr");
+            Integer idx = oldKeys.field("idx");
 
-            if (arr != null)
-                return arr;
-        }
+            if (arr != null && idx != null) {
+                if (idx < 0 || idx > arr.length) {
+                    throw new IllegalStateException("Invalid GridIntList size 
" + idx +
+                        " for array length " + arr.length);
+                }
 
-        log.warn("Can't extract ints from {} fields={}", 
oldKeys.type().typeName(), childFields);
+                return Arrays.copyOf(arr, idx);
+            }
+        }
 
-        return new int[0]; // best effort fallback
+        throw new IllegalStateException("Can't extract ints from " + 
oldKeys.type().typeName() +
+            " fields=" + childFields);
     }
 
     /**
-     * Builds an instance of the new GridIntList 
(org.apache.ignite.tcbot.common.util.GridIntList),
-     * or falls back to int[] if the class is not on the classpath.
+     * Builds an instance of the new GridIntList 
(org.apache.ignite.tcbot.common.util.GridIntList).
      */
     private Object buildNewKeys(int[] ints) {
         if (newKeysCachedConstruct != null) {
@@ -251,14 +260,11 @@ public final class Transformer {
                 return newKeysCachedConstruct.newInstance((Object)ints);
             }
             catch (ReflectiveOperationException ignored) {
-                // fall through to fallback
+                throw new IllegalStateException("Failed to build " + 
NEW_KEYS_TYPE, ignored);
             }
         }
 
-        if (verbose)
-            log.warn("NEW_KEYS_TYPE {} is not available; falling back to raw 
int[]", NEW_KEYS_TYPE);
-
-        return ints; // best effort fallback
+        throw new IllegalStateException("New keys type " + NEW_KEYS_TYPE + " 
is not available.");
     }
 
     /**

Reply via email to