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."); } /**
