Skip to content

Derive the Elasticsearch index name of a store in one place (#4929) - #4938

Open
batrived wants to merge 1 commit into
JanusGraph:masterfrom
batrived:fix/4929-clearstore-lowercase
Open

batrived wants to merge 1 commit into
JanusGraph:masterfrom
batrived:fix/4929-clearstore-lowercase

Conversation

@batrived

Copy link
Copy Markdown
Contributor

Fixes #4929.

Every read and write path maps a JanusGraph store name to an Elasticsearch index name through generateIndexStoreName, which lowercases the store. RestElasticSearchClient.clearStore composed the same name by hand and did not lowercase it.

A mixed index stores its JanusGraph index name as the store name verbatim, and JanusGraph index names are case-sensitive. So for an index named vertexByName:

written and queried as <indexName>_vertexbyname
clearStore targeted <indexName>_vertexByName

An Elasticsearch index name is always lowercase, so the second cannot exist. Either the existence check returned false and the call silently did nothing, or Elasticsearch rejected the name and ManagementSystem reported the misleading "Index removal is not supported for this Backend". Either way the documents remained while the schema was marked DISCARDED, so JanusGraph believed the data was gone.

Approach

I took the second option from the issue — removing the duplicated derivation rather than correcting it. ElasticSearchIndex.clearStore now derives the name through getIndexStoreName, the same call every other path uses, and passes it down. So the mapping exists in exactly one place and cannot drift again.

This changes an interface signature: ElasticSearchClient.clearStore(String indexName, String storeName) becomes clearStore(String indexStoreName). RestElasticSearchClient is the only implementation and ElasticSearchIndex the only caller, both in this module, so nothing else in the tree is affected. IndexProvider.clearStore(String storeName) is a different interface and is untouched.

If you would rather not change the signature, the one-line alternative from the issue — lowercasing inside clearStore — also fixes the reported bug, and I am happy to switch to it. I preferred this version because the duplication is the root cause.

Testing

Docker was not available to me, so I could not run the container-backed ElasticsearchIndexTest locally and did not want to add an integration test I could not verify. RestClientClearStoreTest unit-tests the client contract with a mocked RestClient: the existence check and the delete both address the exact name given, no delete is issued when the index is absent, and the method does not lowercase what it receives — which is what makes passing the pre-derived name correct.

The lowercasing itself stays covered by the existing paths, since getIndexStoreName is now the only derivation and every read and write already depends on it.

Also worth a look, not fixed here

Two things I noticed next to this code, both from your issue and both left alone deliberately:

  • Because generateIndexStoreName lowercases, two mixed indexes differing only in case (byName and byname) map to the same Elasticsearch index and silently share documents. ManagementSystem.checkIndexName only enforces uniqueness on the JanusGraph side. That is a validation change, not a rename, so it belongs in its own PR.
  • RestElasticSearchClient.deleteIndex does nothing at all when the name is not an alias, which looks unintended but is a separate concern.

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

@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.

This PR fixes mixed-index DISCARD_INDEX cleanup by ensuring the Elasticsearch index-store name is derived in one place (via getIndexStoreName) and passed through to clearStore unchanged, avoiding case-related mismatches.

Changes:

  • Refactors ElasticSearchClient.clearStore to accept a pre-derived indexStoreName (signature change) and updates the REST implementation accordingly.
  • Updates ElasticSearchIndex.clearStore to derive the index-store name via getIndexStoreName and improve the thrown error context.
  • Adds a unit test covering the REST client clearStore contract (verbatim name usage + no delete when absent).

Reviewed changes

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

File Description
janusgraph-es/src/test/java/org/janusgraph/diskstorage/es/rest/RestClientClearStoreTest.java Adds unit tests asserting clearStore uses the provided index-store name verbatim and guards deletion by existence.
janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/rest/RestElasticSearchClient.java Updates clearStore to operate on a single indexStoreName argument and removes duplicated name composition.
janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/ElasticSearchIndex.java Derives the index-store name once via getIndexStoreName and passes it to the client; updates exception message accordingly.
janusgraph-es/src/main/java/org/janusgraph/diskstorage/es/ElasticSearchClient.java Changes the interface signature for clearStore and documents the “pre-derived name” contract.

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

…ph#4929)

Every read and write path mapped a JanusGraph store name to an Elasticsearch
index name through generateIndexStoreName, which lowercases the store.
RestElasticSearchClient.clearStore composed the same name by hand and did not
lowercase it.

A mixed index stores its JanusGraph index name as the store name verbatim, and
JanusGraph index names are case sensitive. So for an index named vertexByName,
documents were written to and queried from <indexName>_vertexbyname, while
DISCARD_INDEX asked Elasticsearch to delete <indexName>_vertexByName. An
Elasticsearch index name is always lowercase, so that name cannot exist. Either
the existence check returned false and the call did nothing, or Elasticsearch
rejected the name and ManagementSystem reported that the backend does not
support index removal. The documents stayed, and the schema was marked
DISCARDED, so JanusGraph believed the data was gone.

Remove the second derivation instead of correcting it. ElasticSearchIndex now
passes the name it already derived, so ElasticSearchClient.clearStore takes the
Elasticsearch index name rather than the pair it used to rebuild.

Cover the client contract with RestClientClearStoreTest, which pins that the
name it is given is used verbatim for both the existence check and the deletion,
and cover the derivation itself against a real Elasticsearch with
testClearStoreOfAMixedCaseStoreName, which fails on the previous behavior with
the index still present after the store was cleared.

Signed-off-by: Balmukund Trivedi <btrivedipublic@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Oleksandr Porunov <alexandr.porunov@gmail.com>
Signed-off-by: Oleksandr Porunov <alexandr.porunov@gmail.com>
@porunov
porunov force-pushed the fix/4929-clearstore-lowercase branch from e419026 to 9c49d8d Compare September 20, 2026 23:59
@porunov

porunov commented Sep 21, 2026

Copy link
Copy Markdown
Member

Reviewed, rebased on current master (no conflicts) and force-pushed as a single commit. Three of Copilot's four comments are applied; I replied on the fourth explaining why I kept Mockito.reset.

The bug is real, and I have it on film

Docker was the gap in your own verification, so I closed it. I added testClearStoreOfAMixedCaseStoreName to ElasticsearchIndexTest, which registers a store called vertexByName against a real Elasticsearch 9.5.4, asserts janusgraph_vertexbyname exists, calls index.clearStore("vertexByName") and asserts it is gone.

I then put the old derivation back, in clearStore only, and ran it again:

lowercase-exists-before=true   mixedcase-exists-before=false
lowercase-exists-after=true
ElasticsearchIndexTest.testClearStoreOfAMixedCaseStoreName:357 expected: <false> but was: <true>

So it is the silent branch of the two you described: indexExists("janusgraph_vertexByName") returns 404, no DELETE is issued, nothing is logged, and the documents are still there afterwards. No exception, no "Index removal is not supported for this Backend" — just data that outlives the schema entry which said it was discarded.

That test matters beyond confirming the bug: RestClientClearStoreTest pins the client contract, but nothing pinned the derivation in ElasticSearchIndex.clearStore itself, which is where the defect actually lived. A future change that passes storeName through again would have left the unit test green.

On the approach

I agree with taking the second option from the issue rather than the one-line toLowerCase(). The duplication is the root cause, the signature change is contained — RestElasticSearchClient is the only implementation of ElasticSearchClient and ElasticSearchIndex the only caller, both in this module — and IndexProvider.clearStore(String storeName) is a different interface and stays as it is. I verified all three of those rather than taking them on trust.

I also confirmed the premise: ManagementSystem.createMixedIndex sets INDEXSTORE_NAME to the index name verbatim, and checkIndexName only rejects blank or duplicate names, so an uppercase character in a mixed index name is entirely legal on the JanusGraph side.

Verification

RestClientClearStoreTest 3/3, ElasticsearchIndexTest 267/267 and ElasticsearchConfigTest 12/12 against a real Elasticsearch 9.5.4 container. Checkstyle clean.

Follow-ups

Both of the things you set aside are worth their own issues, and I agree with leaving them here:

  • Two mixed indexes differing only in case map to the same Elasticsearch index and silently share documents, because generateIndexStoreName lowercases while checkIndexName only enforces uniqueness case-sensitively. That one is a validation change with an upgrade story, so it needs its own discussion.
  • RestElasticSearchClient.deleteIndex doing nothing when the name is not an alias does look unintended.

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.

Copilot review overview

🟡 Changes recommended

The public client interface change lacks a compatibility path for downstream implementations and compiled consumers.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (4)

* JanusGraph store name, so that the mapping between the two exists in exactly one place.
* It is used verbatim; in particular it is not lowercased here.
*/
void clearStore(String indexStoreName) throws IOException;
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.

RestElasticSearchClient.clearStore does not lowercase the store name, so DISCARD_INDEX cannot delete data for mixed-case index names

3 participants