diff --git a/query/src/main/kotlin/jetbrains/exodus/query/metadata/IndicesCreator.kt b/query/src/main/kotlin/jetbrains/exodus/query/metadata/IndicesCreator.kt index 9c92678a9..f402abeb5 100644 --- a/query/src/main/kotlin/jetbrains/exodus/query/metadata/IndicesCreator.kt +++ b/query/src/main/kotlin/jetbrains/exodus/query/metadata/IndicesCreator.kt @@ -73,7 +73,7 @@ data class DeferredIndex( ) { constructor(ownerVertexName: String, properties: List, unique: Boolean): this( ownerVertexName, - indexName = "${ownerVertexName}_${properties.joinToString("_") { it.name }}", + indexName = "${ownerVertexName}_${properties.joinToString("_") { it.name }}${if (unique) "_unique" else ""}", properties, unique = unique ) diff --git a/query/src/main/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializer.kt b/query/src/main/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializer.kt index 0faab4b42..414cb6aef 100644 --- a/query/src/main/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializer.kt +++ b/query/src/main/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializer.kt @@ -24,8 +24,10 @@ import com.orientechnologies.orient.core.metadata.sequence.OSequence import com.orientechnologies.orient.core.record.ODirection import com.orientechnologies.orient.core.record.OVertex import jetbrains.exodus.entitystore.orientdb.OVertexEntity +import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.BACKWARD_COMPATIBLE_LOCAL_ENTITY_ID_PROPERTY_NAME import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.CLASS_ID_CUSTOM_PROPERTY_NAME import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.CLASS_ID_SEQUENCE_NAME +import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.localEntityIdSequenceName import mu.KotlinLogging private val log = KotlinLogging.logger {} @@ -57,10 +59,10 @@ internal class OrientDbSchemaInitializer( private fun appendLine(s: String = "") = paddedLogger.appendLine(s) - private val indices = HashMap>() + private val indices = HashMap>() private fun addIndex(index: DeferredIndex) { - indices.getOrPut(index.ownerVertexName) { HashSet() }.add(index) + indices.getOrPut(index.ownerVertexName) { HashMap() }[index.indexName] = index } private fun simplePropertyIndex(entityName: String, propertyName: String): DeferredIndex { @@ -70,7 +72,7 @@ internal class OrientDbSchemaInitializer( return DeferredIndex(entityName, listOf(indexField), unique = false) } - fun getIndices(): Map> = indices + fun getIndices(): Map> = indices.map { it.key to it.value.values.toSet() }.toMap() fun apply() { try { @@ -128,8 +130,8 @@ internal class OrientDbSchemaInitializer( for ((indexOwner, indices) in indices) { appendLine("$indexOwner:") withPadding { - for (index in indices) { - appendLine(index.indexName) + for ((indexName, _) in indices) { + appendLine(indexName) } } } @@ -142,12 +144,7 @@ internal class OrientDbSchemaInitializer( // ClassId private fun createClassIdSequenceIfAbsent() { - val sequences = oSession.metadata.sequenceLibrary - if (sequences.getSequence(CLASS_ID_SEQUENCE_NAME) == null) { - val params = OSequence.CreateParams() - params.start = 0L - sequences.createSequence(CLASS_ID_SEQUENCE_NAME, OSequence.SEQUENCE_TYPE.ORDERED, params) - } + createSequenceIfAbsent(CLASS_ID_SEQUENCE_NAME) } private fun OClass.setClassIdIfAbsent() { @@ -169,6 +166,12 @@ internal class OrientDbSchemaInitializer( if (backwardCompatibleEntityId) { oClass.setClassIdIfAbsent() + createSequenceIfAbsent(localEntityIdSequenceName(dnqEntity.type)) + /* + * We do not apply a unique index to the localEntityId property because indices in OrientDB are polymorphic. + * So, you can not have the same value in a property in an instance of a superclass and in an instance of its subclass. + * But it exactly what happens in the original Xodus. + * */ } /* @@ -333,6 +336,12 @@ internal class OrientDbSchemaInitializer( oClass.applySimpleProperty(propertyMetaData, required || requiredBecauseOfIndex) } } + if (backwardCompatibleEntityId) { + val prop = SimplePropertyMetaDataImpl(BACKWARD_COMPATIBLE_LOCAL_ENTITY_ID_PROPERTY_NAME, "long") + oClass.applySimpleProperty(prop, true) + // we need this index regardless what we have in indexForEverySimpleProperty + addIndex(simplePropertyIndex(dnqEntity.type, BACKWARD_COMPATIBLE_LOCAL_ENTITY_ID_PROPERTY_NAME)) + } } } @@ -546,6 +555,15 @@ internal class OrientDbSchemaInitializer( return oProperty } + private fun createSequenceIfAbsent(sequenceName: String) { + val sequences = oSession.metadata.sequenceLibrary + if (sequences.getSequence(sequenceName) == null) { + val params = OSequence.CreateParams() + params.start = 0L + sequences.createSequence(sequenceName, OSequence.SEQUENCE_TYPE.ORDERED, params) + } + } + private fun getOType(jvmTypeName: String): OType { return when (jvmTypeName.lowercase()) { "boolean" -> OType.BOOLEAN diff --git a/query/src/test/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializerTest.kt b/query/src/test/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializerTest.kt index 03f8c5051..ea9b9e838 100644 --- a/query/src/test/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializerTest.kt +++ b/query/src/test/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializerTest.kt @@ -22,13 +22,16 @@ import com.orientechnologies.orient.core.metadata.schema.OType import com.orientechnologies.orient.core.record.ODirection import com.orientechnologies.orient.core.record.OVertex import jetbrains.exodus.entitystore.orientdb.OVertexEntity +import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.BACKWARD_COMPATIBLE_LOCAL_ENTITY_ID_PROPERTY_NAME import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.CLASS_ID_CUSTOM_PROPERTY_NAME +import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.localEntityIdSequenceName import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryOrientDB import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Rule import org.junit.Test import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull import kotlin.test.assertNull class OrientDbSchemaInitializerTest { @@ -393,8 +396,62 @@ class OrientDbSchemaInitializerTest { assertEquals(setOf(1, 2, 3, 4, 5), classIds) } + @Test + fun `backward compatible EntityId enabled, every class gets localEntityId property`(): Unit = orientDb.withSession { oSession -> + val types = mutableListOf("type1", "type2", "type3") + val model = model { + for (type in types) { + entity(type) + } + } + + val indices = oSession.applySchema(model, backwardCompatibleEntityId = true) + + val sequences = oSession.metadata.sequenceLibrary + for (type in types) { + assertNotNull(oSession.getClass(type).getProperty(BACKWARD_COMPATIBLE_LOCAL_ENTITY_ID_PROPERTY_NAME)) + // index for the localEntityId must be created regardless the indexForEverySimpleProperty param + indices.checkIndex(type, false, BACKWARD_COMPATIBLE_LOCAL_ENTITY_ID_PROPERTY_NAME) + // the index for localEntityId must not be unique, otherwise it will not let the same localEntityId + // for subtypes of a supertype + assertTrue(indices.getValue(type).none { it.unique }) + + val sequence = sequences.getSequence(localEntityIdSequenceName(type)) + assertNotNull(sequence) + assertEquals(1, sequence.next()) + } + + // emulate the next run of the application + oSession.applySchema(model, backwardCompatibleEntityId = true) + + for (type in types) { + val sequence = sequences.getSequence(localEntityIdSequenceName(type)) + // sequences are the same + assertEquals(2, sequence.next()) + } + } + + @Test + fun `backward compatible EntityId disable, localEntityId is ignored`(): Unit = orientDb.withSession { oSession -> + val types = mutableListOf("type1", "type2", "type3") + val model = model { + for (type in types) { + entity(type) + } + } + + val indices = oSession.applySchema(model) + + val sequences = oSession.metadata.sequenceLibrary + for (type in types) { + assertNull(oSession.getClass(type).getProperty(BACKWARD_COMPATIBLE_LOCAL_ENTITY_ID_PROPERTY_NAME)) + assertTrue(indices.isEmpty()) + assertNull(sequences.getSequence(localEntityIdSequenceName(type))) + } + } + private fun OClass.checkIndex(unique: Boolean, vararg fieldNames: String) { - val indexName = "${name}_${fieldNames.joinToString("_")}" + val indexName = indexName(name, unique, *fieldNames) val index = indexes.first { it.name == indexName } assertEquals(unique, index.isUnique) @@ -410,7 +467,7 @@ class OrientDbSchemaInitializerTest { } private fun Map>.checkIndex(entityName: String, unique: Boolean, vararg fieldNames: String) { - val indexName = "${entityName}_${fieldNames.joinToString("_")}" + val indexName = indexName(entityName, unique, *fieldNames) val indices = getValue(entityName) val index = indices.first { it.indexName == indexName } @@ -424,6 +481,8 @@ class OrientDbSchemaInitializerTest { } } + private fun indexName(entityName: String, unique: Boolean, vararg fieldNames: String): String = "${entityName}_${fieldNames.joinToString("_")}${if (unique) "_unique" else ""}" + private fun ODatabaseSession.checkAssociation(edgeName: String, outClass: OClass, inClass: OClass, cardinality: AssociationEndCardinality?) { val edge = requireEdgeClass(edgeName)