Skip to content

Report why an update action does not apply to a mixed index (#4930) - #4939

Open
batrived wants to merge 1 commit into
JanusGraph:masterfrom
batrived:fix/4930-mixed-index-update-status-guard
Open

Report why an update action does not apply to a mixed index (#4930)#4939
batrived wants to merge 1 commit into
JanusGraph:masterfrom
batrived:fix/4930-mixed-index-update-status-guard

Conversation

@batrived

Copy link
Copy Markdown
Contributor

Fixes #4930.

A mixed index keeps a SchemaStatus per field instead of one status on the index, so updateIndex collects the fields whose status the action applies to. When no field matched, that empty set was passed to setStatus, which routed to setStatusVertex, whose precondition can never hold for a mixed index.

One correction to the issue: that precondition is Preconditions.checkArgument(condition) with no message argument, so the exception's message is null. The caller does not get a confusing message about RelationTypeVertex — it gets a bare IllegalArgumentException with no message at all. I found this while writing the test, which failed with a NullPointerException on e.getMessage() before the fix.

Re-running an idempotent schema script is enough to reach it, exactly as the issue describes: REGISTER_INDEX applies to INSTALLED and DISABLED, so it matches no field once the index is enabled. An index built on a property key created in the same management transaction is enabled immediately, so a script that creates an index and then registers it fails the second time it runs.

Change

The composite branch guards this a few lines earlier via isApplicableStatus. Worth noting that method never returns false — it throws — so the return null after it in the composite branch is unreachable, and the composite behaviour is "throw a descriptive error". This gives the mixed branch the equivalent check, and additionally reports the status of every field so it is clear which one blocked the action:

Update action [REGISTER_INDEX] cannot be invoked for index [nameMixed] because none of its fields has one of
the applicable statuses [INSTALLED, DISABLED]. The current status of each field is [name=ENABLED]

Two consequences worth your attention

The guard applies to every action, which is what makes a mixed index behave the way a composite index already does. Two of those are behaviour changes beyond the reported symptom, both of which I believe are improvements — but they are yours to judge:

  1. DISCARD_INDEX no longer clears the index backend before failing. Previously it called IndexSerializer.clearStore and then threw from setStatusVertex, so the documents were already gone while the status change had not happened.
  2. DROP_INDEX on a mixed index which is not DISCARDED is now rejected. This is the second problem you flagged in the issue as possibly deserving its own issue — schemaVertex.remove() used to run regardless, dropping the schema vertex and orphaning every document in the index backend. A composite index is already gated this way.

I also removed the !keySubset.isEmpty() check that REMOVE_STALE_ENTRIES made for itself, since the new guard runs earlier and makes it unreachable. If you would rather narrow the guard to only the status-changing actions, that check needs to come back.

Testing

MixedIndexUpdateStatusTest runs against the in-memory backend with the existing RecordingIndexProvider as the index backend. It covers the rejected case and asserts the message names the action, the index, the blocking status and the applicable statuses; plus two paths that must keep working, registering an INSTALLED index and disabling an ENABLED one.

I checked every existing caller of DISCARD_INDEX and DROP_INDEX in the test tree. The mixed-index sequences in JanusGraphIndexTest and ElasticsearchJanusGraphIndexTest all discard or mark-discarded before dropping, so they stay compatible; the sequences in JanusGraphTest are composite and relation indexes, which this does not touch. IndexSerializerTest still passes. Docker was unavailable to me, so I could not run the container-backed suites locally.


For all changes:

  • Is there an issue associated with this PR? Is it referenced in the commit message?
  • Does your PR body contain #xyz where xyz is the issue number you are trying to resolve?
  • Has your PR been rebased against the latest commit within the target branch (typically master)?
  • Is your initial contribution a single, squashed commit?

For code changes:

  • Have you written and/or updated unit tests to verify your changes?
  • If adding new dependencies to the code, are these dependencies licensed in a way that is compatible for inclusion under ASF 2.0? — no new dependencies
  • If applicable, have you updated the LICENSE.txt file, including the main LICENSE.txt file in the root of this repository? — not applicable
  • If applicable, have you updated the NOTICE.txt file, including the main NOTICE.txt file found in the root of this repository? — not applicable

…ph#4930)

A mixed index keeps a SchemaStatus per field instead of one status on the index,
so updateIndex collects the fields whose status the action applies to. When no
field matched, the empty set was passed on to setStatus, which routed to
setStatusVertex, whose precondition can never hold for a mixed index. That
precondition carries no message, so the caller received a bare
IllegalArgumentException with nothing to act on.

Re-running an idempotent schema script is enough to reach this. REGISTER_INDEX
applies to INSTALLED and DISABLED, so it matches no field once the index is
enabled. An index built on a property key created in the same management
transaction is enabled immediately, so a script which creates an index and then
registers it fails the second time it runs.

The composite branch guards against this a few lines earlier with
isApplicableStatus, which throws a message naming the action and the status.
Give the mixed branch the equivalent check, and report the status of every field
so it is clear which one blocked the action.

This makes a mixed index behave as a composite index already does for an action
which does not apply to it. It also means DISCARD_INDEX no longer clears the
index backend before failing, and DROP_INDEX on a mixed index which is not
DISCARDED is now rejected rather than dropping the schema vertex and orphaning
the documents.

The equivalent check that REMOVE_STALE_ENTRIES made for itself is now
unreachable, so remove it.

Signed-off-by: Balmukund Trivedi <btrivedipublic@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@batrived
batrived force-pushed the fix/4930-mixed-index-update-status-guard branch from e8f1099 to 7525f26 Compare August 13, 2026 17:10
@porunov
porunov requested a lite review from Copilot August 14, 2026 17:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Fixes a failure mode when applying updateIndex actions to mixed indexes where no field matches the action’s applicable statuses, by rejecting early with a descriptive error (and adding regression tests).

Changes:

  • Add a precondition guard for mixed indexes to reject update actions that match no fields, including reporting per-field statuses.
  • Remove a now-redundant/late guard in the mixed REMOVE_STALE_ENTRIES path.
  • Add MixedIndexUpdateStatusTest covering the rejected case and two success paths (registering INSTALLED, disabling ENABLED).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
janusgraph-test/src/test/java/org/janusgraph/graphdb/database/management/MixedIndexUpdateStatusTest.java Adds regression tests ensuring a descriptive exception is thrown when an update action matches no mixed-index fields.
janusgraph-core/src/main/java/org/janusgraph/graphdb/database/management/ManagementSystem.java Adds an early guard for mixed-index updates when no fields match, and includes per-field status in the rejection message.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +988 to +995
//A mixed index keeps a status per field instead of one status on the index, so this is the
//equivalent of the isApplicableStatus check the composite branch makes above. Without it an action
//which matches no field reaches setStatusVertex, whose precondition then fails with a message about
//RelationTypeVertex and composite indexes which says nothing about the real problem
Preconditions.checkArgument(!keySubset.isEmpty(),
"Update action [%s] cannot be invoked for index [%s] because none of its fields has one of the" +
" applicable statuses %s. The current status of each field is %s", updateAction, index.name(),
applicableStatus, fieldStatuses(mixedIndexType));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

updateIndex on a mixed index fails an unrelated precondition when no key is in an applicable status

2 participants