Skip to content

Fix ArrayIndexOutOfBoundsException in CompositeBitmapIndex null handling#624

Open
hrstoyanov wants to merge 4 commits intoeclipse-store:mainfrom
hrstoyanov:fix/composite-index-null-handling
Open

Fix ArrayIndexOutOfBoundsException in CompositeBitmapIndex null handling#624
hrstoyanov wants to merge 4 commits intoeclipse-store:mainfrom
hrstoyanov:fix/composite-index-null-handling

Conversation

@hrstoyanov
Copy link
Copy Markdown

Summary

Fixes ArrayIndexOutOfBoundsException in AbstractCompositeBitmapIndex.internalHandleChanged() when updating an entity from null to non-null composite key value (or vice versa).

Issue Description

When an entity with a composite-indexed field (e.g., IndexerInstant for Instant fields) is:

  1. Added with a null value → composite key = NULL() = Object[1]
  2. Updated to a non-null value → composite key = Object[6] (for Instant's 6 components)

The method would throw:

java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
    at org.eclipse.store.gigamap.types.SubBitmapIndexHashing.index()

Root Cause

The original internalHandleChanged() method called ensureSubIndices(newKeys) which grew the subIndices array, then iterated all sub-indices calling internalHandleChanged(oldKeys, ...). When oldKeys was shorter than the new array (e.g., length 1 vs 6), accessing oldKeys[position] threw ArrayIndexOutOfBoundsException.

Solution

Modified internalHandleChanged() to check isEmpty(oldKeys, i) and isEmpty(newKeys, i) per sub-index position:

  • Both empty: Skip (nothing to do)
  • Old empty, new non-empty: ADD via internalAddToEntry()
  • Old non-empty, new empty: REMOVE via internalRemove()
  • Both non-empty: HANDLE CHANGE via internalHandleChanged()

Files Changed

  1. AbstractCompositeBitmapIndex.java - Fixed internalHandleChanged() method (lines 529-568)
  2. CompositeIndexNullHandlingTest.java (NEW) - Comprehensive test coverage with 5 test methods

Testing

Test covers:

  • null→non-null updates
  • non-null→null updates
  • Persistence with EmbeddedStorage
  • Multiple entity updates
  • Query operations with mixed null/non-null values

Impact

  • Breaking Changes: None
  • Backward Compatible: Yes
  • Performance Impact: Minimal (adds 2 boolean checks per iteration)

Discovered by TourBiz project during Booking entity updates with nullable Instant fields.

- Add isEmpty() guards in internalHandleChanged() to properly handle
  composite key array length changes (e.g. NULL() Object[1] vs
  decomposed Instant Object[6])
- Dispatch to internalAddToEntry(), internalRemove(), or
  internalHandleChanged() based on old/new key presence per position
- Add comprehensive test coverage for null↔non-null updates
- Fixes issue reported by TourBiz project with Booking entity updates

Signed-off-by: Hristo Todorov
@hrstoyanov
Copy link
Copy Markdown
Author

This addresses #623

@hrstoyanov
Copy link
Copy Markdown
Author

hrstoyanov commented Mar 13, 2026

I have 100+ tests for Eclipse Store and with this fix it all pass now.

Please release 4.0.2

@zdenek-jonas
Copy link
Copy Markdown
Contributor

Thanks for the PR and the contribution. Due to our current review capacity, this will likely not be reviewed for at least a couple of weeks. Thank you for your patience.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a composite-bitmap-index update bug where transitioning an indexed composite key between null and non-null could trigger ArrayIndexOutOfBoundsException due to mismatched key-array lengths across sub-indices.

Changes:

  • Update AbstractCompositeBitmapIndex.internalHandleChanged() to dispatch per sub-index position (add/remove/change/skip) based on old/new key presence.
  • Add a new JUnit test suite covering null↔non-null composite key updates, persistence restart, and query behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
gigamap/gigamap/src/main/java/org/eclipse/store/gigamap/types/AbstractCompositeBitmapIndex.java Fixes update logic to handle old/new composite key arrays of different lengths safely.
gigamap/gigamap/src/test/java/org/eclipse/store/gigamap/indexer/CompositeIndexNullHandlingTest.java Adds regression tests for null-handling transitions, persistence, multi-entity updates, and queries.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


// Verify queries after update
assertEquals(0, map.query(indexer.is((Instant) null)).count());
assertEquals(2, map.query(indexer.is((Instant) null).or(indexer.is(Instant.parse("2025-06-15T10:30:00Z")))).count());
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The final assertion expects 2 results for null OR 2025-06-15T10:30:00Z after event1 is updated to 11:30. At that point neither entity has a null timestamp, and only event2 matches 10:30, so the expected count should be 1 (or adjust the predicate to match both entities).

Suggested change
assertEquals(2, map.query(indexer.is((Instant) null).or(indexer.is(Instant.parse("2025-06-15T10:30:00Z")))).count());
assertEquals(1, map.query(indexer.is((Instant) null).or(indexer.is(Instant.parse("2025-06-15T10:30:00Z")))).count());

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +21
import org.eclipse.store.gigamap.types.BitmapIndices;
import org.eclipse.store.gigamap.types.GigaMap;
import org.eclipse.store.gigamap.types.IndexerInstant;
import org.eclipse.store.storage.embedded.types.EmbeddedStorage;
import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager;
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import: org.eclipse.store.gigamap.types.BitmapIndices is not referenced in this test class. Please remove it to avoid unused-import warnings/failures in stricter builds.

Copilot uses AI. Check for mistakes.
@fh-ms fh-ms added bug Something isn't working GigaMap labels Mar 30, 2026
@fh-ms
Copy link
Copy Markdown
Contributor

fh-ms commented Mar 30, 2026

Thank you for contributing!

The code looks fine, but the provided test fails: https://github.com/eclipse-store/store/actions/runs/23066299067/job/69194135361?pr=624#step:4:11679

This needs to be addressed before merging, as it is mandatory for you to sign the ECA in order to contribute:
https://www.eclipse.org/projects/handbook/#resources-eca

Please have a look at Copilot's mentions too.

@fh-ms fh-ms linked an issue Mar 30, 2026 that may be closed by this pull request
hrstoyanov and others added 2 commits March 30, 2026 15:26
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@hrstoyanov
Copy link
Copy Markdown
Author

hrstoyanov commented Mar 31, 2026

@fh-ms Tried to create Eclipse account for contributor, but fit fails and does not work ... I fixed the junit test

@fh-ms
Copy link
Copy Markdown
Contributor

fh-ms commented Apr 2, 2026

If you have trouble creating an Eclipse account, I'll close this PR and copy your changes to a new one if you are fine with it.

@hrstoyanov
Copy link
Copy Markdown
Author

hrstoyanov commented Apr 2, 2026

@fh-ms - Yes, do it, please - anyway we fix this is a good way!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working GigaMap

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[4.0.1] ArrayIndexOutOfBoundsException

4 participants