Repository navigation
Description
SEVERITY: HIGH
Summary
TypeIndex.updateTypeName recomputes the wrapper's logicName from the type's new name, and
nothing re-keys LocalSchema.indexMap. From that moment the index reports Bar[a] while the only
key that reaches it is Foo[a]. Five consequences, all reproduced:
DROP INDEX "Bar[a]"andREBUILD INDEX "Bar[a]"fail with "Index not found", using the exact
nameSELECT FROM schema:indexesprints.DROP INDEX "Foo[a]"succeeds, dropping the index of a type that no longer exists under that
name.- Creating a new type under the freed name
Fooand indexing the same property is refused with
"Index 'Foo[a]' already exists", because the stale key is still occupied by the other type's
index. DROP TYPE Barleaves the wrapper inindexMap(its cleanup looks it up bygetName()), and
SELECT FROM schema:indexesthen throwsSchemaException: Type with name 'Bar' was not found
for every caller until the database is reopened.- A manual index name is lost permanently.
typeIndexForhonoursmetadata.typeIndexNamewhen it
mints a wrapper (issue Manual index name is ignored when create an index #4139), butupdateTypeNameoverwriteslogicNameunconditionally, and
toJSONpersists a manual name only wheni.getName()differs from the auto-derived form, which
it no longer does. After the rename and one reopen the user-chosen name is gone from
schema.json.
LocalSchema.indexRenamed exists precisely to keep this map honest, and its javadoc states the rule
that is broken: "the schema keeps resolving it under IndexInternal#getName() at every moment of
its life" and "Only LSMVectorIndex renames itself ... Every other index type keeps its creation
name for life, which is why this had no counterpart before." TypeIndex renames itself on every
type rename and never calls it.
Where (HEAD 888b83c)
engine/src/main/java/com/arcadedb/index/TypeIndex.java:737-updateTypeNameassigns
logicNameand ignoresmetadata.typeIndexName.engine/src/main/java/com/arcadedb/schema/LocalDocumentType.java:789and:799- the rename
loops callidx.updateTypeName(...)/idx.updateTypeNameOfSubIndexesOn(...)and never call
schema.indexRenamed(...); neither does the rollback at:833.engine/src/main/java/com/arcadedb/schema/LocalSchema.java:5492-indexRenamed, whose only
caller isengine/src/main/java/com/arcadedb/index/vector/LSMVectorIndex.java:886.engine/src/main/java/com/arcadedb/schema/LocalDocumentType.java:2425-typeIndexForhonours a
manual name when minting.engine/src/main/java/com/arcadedb/schema/LocalDocumentType.java:2974-toJSONdecides a name is
"custom" by comparing it with the auto-derived form.
Repro
Probe ProbeB, default-named index:
CREATE DOCUMENT TYPE Foo
CREATE PROPERTY Foo.a STRING
CREATE INDEX ON Foo (a) UNIQUE
ALTER TYPE Foo NAME Bar
after rename: names = Bar[a] Foo_0_4064685322655750
after rename: exists(Bar[a])=false exists(Foo[a])=true
DROP INDEX `Bar[a]` -> CommandExecutionException: Index not found: Bar[a]
REBUILD INDEX `Bar[a]` -> IndexException: Error on rebuilding index 'Bar[a]' (error=Index with name 'Bar[a]' was not found)
DROP INDEX `Foo[a]` (the type Foo no longer exists) -> OK, dropped
end: names =
Probe ProbeG, the same rename followed by recreating a type under the freed name and then dropping
the renamed one:
after rename: names = Bar[a] Foo_0_4065059139257583
CREATE INDEX ON the new Foo (a) -> AlreadyExistsException: Index 'Foo[a]' already exists
after re-creating Foo: names = Bar[a] Foo_0_4065059139257583
exists(Bar[a]) = false
DROP TYPE Bar -> OK
end: names = Bar[a] | existsType(Bar)=false
schema:indexes -> SchemaException: Type with name 'Bar' was not found
after reopen: names =
Probe ProbeA, manual index name, across a close and reopen:
CREATE INDEX MyIdx ON Foo (a) UNIQUE
ALTER TYPE Foo NAME Bar
after create: exists(MyIdx)=true
after create: index names = MyIdx Foo_0_4064593401166791
after rename: exists(MyIdx)=true
after rename: exists(Bar[a])=false
after rename: index names = Bar[a] Foo_0_4064593401166791
after rename: getIndexByName(MyIdx).getName() = Bar[a]
after reopen: exists(MyIdx)=false
after reopen: exists(Bar[a])=true
after reopen: index names = Foo_0_4064593401166791 Bar[a]
Refutation attempted
- Is the map rebuilt soon enough for this to be invisible? No. It is rebuilt only by a schema
load. The probes above run entirely in one session after the rename, and theProbeGcatalog
failure persists until the reopen on the last line. - Is
schema.jsonwrong too? No, and that is worth stating:saveConfigurationserialises
indexes fromgetAllIndexes(), not fromindexMap, so the file is consistent and a reopen heals
consequences 1 to 4. Consequence 5 is the one the reopen makes permanent, because the manual name
is not written at all. - Does the bucket-level index have the same problem? No.
LSMTreeIndex.updateTypeName
(engine/src/main/java/com/arcadedb/index/lsm/LSMTreeIndex.java:299) moves the file and updates
metadata.typeNamebut leavesnamealone, so itsindexMapkey stays correct. Only the
TypeIndexwrapper changes the name it answers to. - Does the transaction-index-lane problem of LSM_VECTOR: writes right after COMPACT INDEX (same session) are silently not indexed #6105 reappear? I could not make it: the lanes are
keyed by the BUCKET-level index name, which does not change. Not reproduced, and I am not claiming
it. - Is the manual-name loss perhaps intended, a manual name being type-scoped? Nothing says so, and
the opposite is documented:typeIndexForcalls the manual name "the name a user's SELECT
resolves", andIndexBuilder.conflictWithExistingIndexNamesays "an index is reachable only
through its name", listingDROP INDEX <name>andREBUILD INDEX <name>.
Suggested fix
In LocalDocumentType.renameInternal, capture each TypeIndex's name before updateTypeName and
call schema.indexRenamed(oldLogicName, idx) after it, and do the same in reverse in the rollback
loops. In TypeIndex.updateTypeName, leave the name alone when the wrapper carries a manual name,
which is the same condition typeIndexFor already evaluates when it mints one.
THE GUARANTEE. Nothing should be able to change what IndexInternal.getName() answers without the
schema's map following in the same step. Make logicName unassignable from outside and give LocalSchema the single entry point (renameIndex(index, newName)) that assigns it, re-keys indexMap and notifies the transaction manager, so there is no way to do half of it. The census
test must enumerate the IndexInternal implementations from the index factory's registered handlers
rather than from a list in the test, call the rename hook on each, and assert schema.getIndexByName(index.getName()) == index and existsIndex(oldName) == false afterwards; a
second case must rename a type carrying a manually named index, reopen, and assert the manual name
still resolves. A weaker assertion passes today: checking only that index.getName() changed, or
that schema.json round-trips, both succeed while every consequence above remains.
Scope / confidence
Executed: ProbeA, ProbeB, ProbeG, outputs verbatim above. Read, not executed: the rollback
path (LocalDocumentType.java:833) needing the inverse call, and the claim that indexRenamed's TransactionManager notification is also skipped.
Dedup
Checked alltitles.txt for rename (#10412, #10317, #10096, #9891, #9762, #9434), for DROP INDEX/REBUILD INDEX/index name (#10734, #10589, #9848, #9812, #9811, #9705, #9651, #9615,
#9254, #9213, #9071) and for schema:indexes (none). #10412 is the nearest neighbour, "DROP TYPE
and ALTER TYPE ... BUCKET fail after an indexed supertype rename", and it is about a SUPERTYPE
rename unlinking sub-index bindings; the defect here is the schema's name-to-index map not following
a rename at all, which bites with no inheritance in the picture. #9213 is about the LSM bucket-index
file name diverging between leader and followers after a compaction, a different map and a different
cause. #9434 is a manual LSM index renamed by a compaction, again the bucket-level component.
Activity
fix(#10751, #10734, #10788): a type rename re-keys its indexes, DROP …
lvca commentedon Oct 27, 2026
Fixed in #10816 (26.11.1).
After
ALTER TYPE Foo NAME Bar, the indexFoo[a]is nowBar[a]everywhere straight away:DROP INDEX,REBUILD INDEXandschema:indexesall find it by its new name, and the old name no longer reaches it. A new type can be created under the freed name and indexed again, and dropping the renamed type leaves nothing behind.An index you named yourself (
CREATE INDEX MyIdx ON Foo (a)) keeps that name through the rename and after a restart.A rename is refused, with nothing changed, if one of the type's indexes would take a name another index already has.
❤️ Thanks for helping make ArcadeDB better! ArcadeDB is free and open source, sustained by its community. If it's useful to you or your company, please consider becoming a sponsor to keep development going.
ruispereira commentedon Oct 28, 2026
Re-validated at main 0d9d8ce. COMPLETE: all five consequences re-run and all five gone, including the manual index name surviving a rename and a reopen. The guard is at the authority:
logicNameis private volatile, assigned only by a privaterename()that callsschema.indexRenamedin the same step and rolls the name back if the map refuses, andindexRenameditself now refuses to take another index's name. Hierarchy cases the shipped test omits (supertype rename with an inherited index, subtype rename, a manual name on a supertype index) were also run; every listed index still resolves to itself after each, and after a reopen.One residual, reproduced. A hand-spelled name that happens to equal the derived form is still lost to a rename:
This is deliberate per the comment at
LocalDocumentType:2498: REBUILD hands the derived name back as the custom one, so the two are indistinguishable after the fact. The shape that would close it is recording manual-ness where the name is SUPPLIED, a flag onIndexBuilderset by CREATE INDEX when it carried a name, instead of deriving it by comparing two strings.Two sub-tests of the new class (
aRenameBackRestoresTheOriginalNames,aRefusedRenameLeavesTheIndexUnderItsOldName) pass with the production change reverted. Five of seven carry the issue, so the class is not inert, but those two are regression guards rather than defect pins.One correction to our own report: in consequence 5 we gave the mechanism backwards.
updateTypeNameoverwrites the name first, and thetoJSONcomparison is only what makes the loss permanent. Both had to change, and both did.fix(#10751, #10734, #10788): a type rename re-keys its indexes, DROP …