Skip to content

Partitioned type with a subtype of a different bucket count crashes on the first indexed insert #5645

Description

@lvca

Found while covering the inheritance path for #5637 (PR #5644). Unrelated to that issue and to the whole partitioned-strategy series - the line predates all of it - so it is filed rather than folded in.

TypeIndex.getIndexesByKeys prunes to one bucket by asking the type's strategy for a bucket index, then applies that same index to every subtype:

final int bucketIndex = type.getBucketIndexByKeys(propNames, keys, ...);
if (bucketIndex > -1) {
  List<IndexInternal> polymorphicIndexesOnKeys = type.getPolymorphicBucketIndexByBucketId(
      type.getBuckets(false).get(bucketIndex).getFileId(), propNames);

  for (DocumentType s : type.getSubTypes()) {
    final List<IndexInternal> subIndexes = s.getPolymorphicBucketIndexByBucketId(
        s.getBuckets(false).get(bucketIndex).getFileId(), propNames);   // <-- here
    ...

The index is only meaningful modulo the bucket count it was computed against. A subtype is free to declare a different one, and then s.getBuckets(false).get(bucketIndex) is out of range.

Reproduce

CREATE DOCUMENT TYPE Base BUCKETS 3;
CREATE PROPERTY Base.k STRING;
CREATE INDEX ON Base(k) UNIQUE;
ALTER TYPE Base BucketSelectionStrategy `partitioned('k')`;

CREATE DOCUMENT TYPE Derived EXTENDS Base;   -- inherits the strategy, but gets the DEFAULT bucket count
INSERT INTO Derived SET k = 'acme';
java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1
	at java.base/java.util.Collections$UnmodifiableList.get(Collections.java:1502)
	at com.arcadedb.index.TypeIndex.getIndexesByKeys(TypeIndex.java:627)
	at com.arcadedb.database.TransactionIndexContext.addIndexKeyLock(TransactionIndexContext.java:456)
	at com.arcadedb.database.TransactionContext.addIndexOperation(TransactionContext.java:1848)
	at com.arcadedb.index.lsm.LSMTreeIndex.put(LSMTreeIndex.java:637)
	at com.arcadedb.database.DocumentIndexer.addToIndex(DocumentIndexer.java:103)
	at com.arcadedb.database.MutableDocument.save(MutableDocument.java:367)

Giving Derived the same BUCKETS 3 makes it pass, which is what PartitionedStrategyLifecycleTest.aSubtypeInheritsAPartitionedStrategyAndItStillPrunes does, with a comment pointing here.

Note the crash needs no INSERT on the subtype specifically - it is on the read side of the index lookup, so a SELECT through the index reaches it too. The direction of the mismatch decides which: a subtype with FEWER buckets than its parent throws, and one with MORE silently prunes to a bucket the record was never placed in, which is the #5589 failure mode again (wrong bucket, record silently missed) rather than an exception.

Where the fix belongs

bucketIndex is the wrong currency to pass across a type boundary. Each subtype has to re-derive its own placement from the key, through its own strategy and its own modulus - s.getBucketIndexByKeys(propNames, keys, async) rather than reusing the parent's answer - and decline to prune that subtype (fall back to its full bucket list) whenever it cannot, e.g. when the subtype is not partitioned at all or its strategy declines. Worth checking the symmetric assumption elsewhere: SelectExecutionPlanner and the Cypher PartitionPruning rule both turn a bucket index into a bucket name and would have the same question to answer for polymorphic queries.

Alternatively, refuse the mismatch at schema time - a subtype of a partitioned type must inherit its parent's bucket count - but that is a real restriction on existing databases and does nothing for the ones already in that shape, so the read-side fix looks like the one worth having.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions