From 33edcb5d0b309360269d43f770aa6f7419301335 Mon Sep 17 00:00:00 2001 From: andrii0lomakin Date: Fri, 10 Jan 2025 20:16:49 +0100 Subject: [PATCH] YTDB upgrade. --- build.gradle.kts | 8 +- entity-store/build.gradle.kts | 2 +- .../core/db/OrientDbInternalAccessor.kt | 23 - .../entitystore/orientdb/OComparableSet.kt | 5 +- .../orientdb/ODatabaseCompacter.kt | 25 +- .../entitystore/orientdb/ODatabaseConfig.kt | 16 +- .../orientdb/ODatabaseConnectionConfig.kt | 8 +- .../entitystore/orientdb/ODatabaseProvider.kt | 52 +- .../orientdb/ODatabaseProviderImpl.kt | 47 +- .../exodus/entitystore/orientdb/OEntityId.kt | 5 +- .../entitystore/orientdb/OEntityIterables.kt | 10 +- .../orientdb/OPersistentEntityStore.kt | 12 +- .../entitystore/orientdb/ORIDEntityId.kt | 20 +- .../orientdb/OReadonlyVertexEntity.kt | 5 +- .../entitystore/orientdb/OSchemaBuddy.kt | 172 +++-- .../entitystore/orientdb/OStoreTransaction.kt | 30 +- .../orientdb/OStoreTransactionImpl.kt | 89 +-- .../entitystore/orientdb/OVertexEntity.kt | 126 ++-- .../iterate/link/OVertexEntityIterable.kt | 10 +- .../iterate/property/OSequenceImpl.kt | 4 +- .../orientdb/query/OQueryCancellingPolicy.kt | 4 +- .../orientdb/query/OQueryExecution.kt | 4 +- .../entitystore/orientdb/query/OSelect.kt | 16 +- .../exodus/entitystore/WrongUsernameTest.kt | 8 +- .../entitystore/orientdb/DBCompactTest.kt | 4 +- .../entitystore/orientdb/EncryptedDBTest.kt | 31 +- .../orientdb/ODatabaseProviderTest.kt | 25 +- .../entitystore/orientdb/OEntityTest.kt | 249 +++---- .../orientdb/OPersistentStoreTest.kt | 142 ++-- .../entitystore/orientdb/ORIDEntityIdTest.kt | 30 +- .../entitystore/orientdb/OSchemaBuddyTest.kt | 48 +- .../entitystore/orientdb/OSequenceImplTest.kt | 33 +- .../OStoreTransactionLifecycleTest.kt | 52 +- .../orientdb/OStoreTransactionTest.kt | 65 +- .../orientdb/OTransactionLifecycleTest.kt | 145 +++-- .../orientdb/iterate/OEntityIterableTest.kt | 24 +- ...emoryOrientDB.kt => InMemoryYouTrackDB.kt} | 30 +- .../orientdb/testutil/OTaskTrackerTestCase.kt | 2 +- .../orientdb/testutil/OTestMixin.kt | 31 +- .../testutil/OUsersWithInheritanceTestCase.kt | 8 +- .../orientdb/testutil/OrientDBIssues.kt | 32 +- .../exodus/env/StuckTransactionMonitor.kt | 2 +- .../jetbrains/exodus/gc/BackgroundCleaner.kt | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 6 +- gradlew.bat | 22 +- query/build.gradle.kts | 1 - .../metadata/DataAfterMigrationChecker.kt | 1 + .../exodus/query/metadata/IndicesCreator.kt | 30 +- .../query/metadata/MigrateXodusToOrient.kt | 13 +- .../exodus/query/metadata/OModelMetaData.kt | 8 +- .../jetbrains/exodus/query/metadata/Utils.kt | 4 +- .../metadata/XodusToOrientDataMigrator.kt | 28 +- .../XodusToOrientDataMigratorLauncher.kt | 6 +- ...izer.kt => YouTrackDbSchemaInitializer.kt} | 222 ++++--- .../query/OBinaryOperationsWithSortTest.kt | 33 +- .../exodus/query/OQueryEngineTest.kt | 6 +- .../exodus/query/metadata/MigrateDataTest.kt | 156 +++-- .../MigrateXodusToOrientDbSmokeTest.kt | 25 +- .../query/metadata/MigrateYourDatabaseTest.kt | 8 +- .../query/metadata/OModelMetaDataTest.kt | 71 +- .../metadata/OrientDbSchemaInitializerTest.kt | 566 ---------------- .../exodus/query/metadata/SchemaTestUtils.kt | 85 +-- ...rackDbSchemaInitializerLinkIndicesTest.kt} | 40 +- .../YouTrackDbSchemaInitializerTest.kt | 612 ++++++++++++++++++ settings.gradle.kts | 8 +- 67 files changed, 1945 insertions(+), 1664 deletions(-) delete mode 100644 entity-store/src/main/kotlin/com/orientechnologies/orient/core/db/OrientDbInternalAccessor.kt rename entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/{InMemoryOrientDB.kt => InMemoryYouTrackDB.kt} (81%) rename query/src/main/kotlin/jetbrains/exodus/query/metadata/{OrientDbSchemaInitializer.kt => YouTrackDbSchemaInitializer.kt} (79%) delete mode 100644 query/src/test/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializerTest.kt rename query/src/test/kotlin/jetbrains/exodus/query/metadata/{OrientDbSchemaInitializerLinkIndicesTest.kt => YouTrackDbSchemaInitializerLinkIndicesTest.kt} (93%) create mode 100644 query/src/test/kotlin/jetbrains/exodus/query/metadata/YouTrackDbSchemaInitializerTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 17c372514..429a77275 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,7 +49,7 @@ fun shouldApplyDokka(project: Project): Boolean { } tasks.wrapper { - gradleVersion = "8.5" + gradleVersion = "8.12" } defaultTasks("assemble") @@ -64,7 +64,7 @@ nexusStaging { allprojects { repositories { mavenCentral() - maven { url = uri("https://packages.jetbrains.team/maven/p/xodus/orientdb-daily") } + maven { url = uri("https://packages.jetbrains.team/maven/p/xodus/youtrackdb-daily") } } } @@ -158,7 +158,7 @@ subprojects { isFailOnError = false options.quiet() (options as CoreJavadocOptions).addStringOption("Xdoclint:none", "-quiet") - (options as CoreJavadocOptions).addStringOption("source", 17.toString()) + (options as CoreJavadocOptions).addStringOption("source", 21.toString()) } @@ -186,7 +186,7 @@ subprojects { withSourcesJar() toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) + languageVersion.set(JavaLanguageVersion.of(21)) } } diff --git a/entity-store/build.gradle.kts b/entity-store/build.gradle.kts index dec74fe6f..01352426c 100644 --- a/entity-store/build.gradle.kts +++ b/entity-store/build.gradle.kts @@ -1,6 +1,6 @@ dependencies { api(project(":xodus-openAPI")) - api("com.orientechnologies:orientdb-core:4.0.0-20241126.153402-203") + api("io.youtrackdb:youtrackdb-core:1.0.0-20250110.154816-3") implementation(project(":xodus-utils")) implementation(project(":xodus-environment")) diff --git a/entity-store/src/main/kotlin/com/orientechnologies/orient/core/db/OrientDbInternalAccessor.kt b/entity-store/src/main/kotlin/com/orientechnologies/orient/core/db/OrientDbInternalAccessor.kt deleted file mode 100644 index 6485ede5c..000000000 --- a/entity-store/src/main/kotlin/com/orientechnologies/orient/core/db/OrientDbInternalAccessor.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright ${inceptionYear} - ${year} ${owner} - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.orientechnologies.orient.core.db - -object OrientDbInternalAccessor { - val OrientDB.accessInternal: OrientDBInternal - get() { - return this.getInternal() - } -} diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OComparableSet.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OComparableSet.kt index 0e94d7ebf..9f860c514 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OComparableSet.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OComparableSet.kt @@ -15,13 +15,12 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.db.record.OTrackedSet +import com.jetbrains.youtrack.db.internal.core.db.record.TrackedSet class OComparableSet(val source: MutableSet) : MutableSet by source, Comparable> { - val isDirty: Boolean get() { - return if (source is OTrackedSet) { + return if (source is TrackedSet) { source.isTransactionModified } else { true diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseCompacter.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseCompacter.kt index eb81eeb0b..2e0ed5e67 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseCompacter.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseCompacter.kt @@ -15,16 +15,16 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.command.OCommandOutputListener -import com.orientechnologies.orient.core.db.ODatabaseSessionInternal -import com.orientechnologies.orient.core.db.OrientDB -import com.orientechnologies.orient.core.db.tool.ODatabaseExport -import com.orientechnologies.orient.core.db.tool.ODatabaseImport +import com.jetbrains.youtrack.db.api.YouTrackDB +import com.jetbrains.youtrack.db.internal.core.command.CommandOutputListener +import com.jetbrains.youtrack.db.internal.core.db.DatabaseSessionInternal +import com.jetbrains.youtrack.db.internal.core.db.tool.DatabaseExport +import com.jetbrains.youtrack.db.internal.core.db.tool.DatabaseImport import mu.KLogging import java.io.File class ODatabaseCompacter( - private val db: OrientDB, + private val db: YouTrackDB, private val dbProvider: ODatabaseProvider, private val config: ODatabaseConfig ) { @@ -34,11 +34,11 @@ class ODatabaseCompacter( val databaseLocation = File(dbProvider.databaseLocation) val backupFile = File(databaseLocation, "temp${System.currentTimeMillis()}") backupFile.parentFile.mkdirs() - val listener = OCommandOutputListener { iText -> logger.info("Compacting database: $iText") } + val listener = CommandOutputListener { iText -> logger.info("Compacting database: $iText") } dbProvider.withSession { session -> - val exporter = ODatabaseExport( - session as ODatabaseSessionInternal, + val exporter = DatabaseExport( + session as DatabaseSessionInternal, backupFile.outputStream(), listener ) @@ -49,12 +49,13 @@ class ODatabaseCompacter( logger.info("Dropping existing database...") db.drop(config.databaseName) - db.create(config.databaseName, config.databaseType) + db.create(config.databaseName, config.databaseType, + config.connectionConfig.userName, config.connectionConfig.password, "admin") dbProvider.withSession { session -> logger.info("Importing database from dump") - val importer = ODatabaseImport( - session as ODatabaseSessionInternal, + val importer = DatabaseImport( + session as DatabaseSessionInternal, backupFile.inputStream(), listener ) diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseConfig.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseConfig.kt index a7713b445..20df682e8 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseConfig.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseConfig.kt @@ -16,18 +16,18 @@ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.db.ODatabaseType -import com.orientechnologies.orient.core.db.OrientDBConfigBuilder +import com.jetbrains.youtrack.db.api.DatabaseType +import com.jetbrains.youtrack.db.api.config.YouTrackDBConfigBuilder import kotlin.math.min class ODatabaseConfig private constructor( val connectionConfig: ODatabaseConnectionConfig, val databaseName: String, - val databaseType: ODatabaseType, + val databaseType: DatabaseType, val closeAfterDelayTimeout: Int, val cipherKey: ByteArray?, val closeDatabaseInDbProvider: Boolean, - val tweakConfig: OrientDBConfigBuilder.() -> Unit + val tweakConfig: YouTrackDBConfigBuilder.() -> Unit ) { companion object { fun builder(): Builder { @@ -39,17 +39,17 @@ class ODatabaseConfig private constructor( class Builder internal constructor() { private lateinit var connectionConfig: ODatabaseConnectionConfig private var databaseName: String = "" - private var databaseType: ODatabaseType? = null + private var databaseType: DatabaseType? = null private var closeAfterDelayTimeout: Int? = null private var cipherKey: ByteArray? = null private var closeDatabaseInDbProvider = true - private var tweakConfig: OrientDBConfigBuilder.() -> Unit = {} + private var tweakConfig: YouTrackDBConfigBuilder.() -> Unit = {} fun withDatabaseName(databaseName: String) = apply { this.databaseName = databaseName } fun withConnectionConfig(connectionConfig: ODatabaseConnectionConfig) = apply { this.connectionConfig = connectionConfig } - fun withDatabaseType(databaseType: ODatabaseType) = apply { this.databaseType = databaseType } + fun withDatabaseType(databaseType: DatabaseType) = apply { this.databaseType = databaseType } fun withCloseAfterDelayTimeout(closeAfterDelayTimeout: Int) = apply { this.closeAfterDelayTimeout = closeAfterDelayTimeout } @@ -63,7 +63,7 @@ class ODatabaseConfig private constructor( cipherKey = hexStringToByteArray(key.substring(0, min(16 * 2, key.length))) + longToByteArray(IV) } - fun tweakConfig(tweakConfig: OrientDBConfigBuilder.() -> Unit) = apply { this.tweakConfig = tweakConfig } + fun tweakConfig(tweakConfig: YouTrackDBConfigBuilder.() -> Unit) = apply { this.tweakConfig = tweakConfig } fun build() = ODatabaseConfig( connectionConfig, databaseName, databaseType ?: connectionConfig.databaseType, closeAfterDelayTimeout ?: connectionConfig.closeAfterDelayTimeout, diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseConnectionConfig.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseConnectionConfig.kt index e3ac1addd..0177f8326 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseConnectionConfig.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseConnectionConfig.kt @@ -15,13 +15,13 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.db.ODatabaseType +import com.jetbrains.youtrack.db.api.DatabaseType class ODatabaseConnectionConfig private constructor( val databaseRoot: String, val userName: String, val password: String, - val databaseType: ODatabaseType, + val databaseType: DatabaseType, val closeAfterDelayTimeout: Int ) { companion object { @@ -35,13 +35,13 @@ class ODatabaseConnectionConfig private constructor( private lateinit var databaseRoot: String private lateinit var userName: String private lateinit var password: String - private var databaseType: ODatabaseType = ODatabaseType.MEMORY + private var databaseType: DatabaseType = DatabaseType.MEMORY private var closeAfterDelayTimeout: Int = 10 fun withDatabaseRoot(databaseURL: String) = apply { this.databaseRoot = databaseURL } fun withUserName(userName: String) = apply { this.userName = userName } fun withPassword(password: String) = apply { this.password = password } - fun withDatabaseType(databaseType: ODatabaseType) = apply { this.databaseType = databaseType } + fun withDatabaseType(databaseType: DatabaseType) = apply { this.databaseType = databaseType } fun withCloseAfterDelayTimeout(closeAfterDelayTimeout: Int) = apply { this.closeAfterDelayTimeout = closeAfterDelayTimeout } diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseProvider.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseProvider.kt index 8a6e66cf8..cb2860be8 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseProvider.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseProvider.kt @@ -15,15 +15,17 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.config.OGlobalConfiguration -import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal -import com.orientechnologies.orient.core.db.ODatabaseSession -import com.orientechnologies.orient.core.db.OrientDB -import com.orientechnologies.orient.core.db.OrientDBConfigBuilder +import com.jetbrains.youtrack.db.api.DatabaseSession +import com.jetbrains.youtrack.db.api.YouTrackDB +import com.jetbrains.youtrack.db.api.YourTracks +import com.jetbrains.youtrack.db.api.config.GlobalConfiguration +import com.jetbrains.youtrack.db.api.config.YouTrackDBConfig +import com.jetbrains.youtrack.db.api.exception.RecordDuplicatedException +import com.jetbrains.youtrack.db.internal.core.db.DatabaseRecordThreadLocal interface ODatabaseProvider { val databaseLocation: String - fun acquireSession(): ODatabaseSession + fun acquireSession(): DatabaseSession /** * If there is a session on the current thread, create a new session, executes the action in it, @@ -33,8 +35,8 @@ interface ODatabaseProvider { * and do not hesitate to invite people to review your code. */ fun executeInASeparateSession( - currentSession: ODatabaseSession, - action: (ODatabaseSession) -> T + currentSession: DatabaseSession, + action: (DatabaseSession) -> T ): T /** @@ -46,7 +48,7 @@ interface ODatabaseProvider { fun close() } -fun ODatabaseProvider.withSession(block: (ODatabaseSession) -> R): R { +fun ODatabaseProvider.withSession(block: (DatabaseSession) -> R): R { acquireSession().use { session -> return block(session) } @@ -54,10 +56,10 @@ fun ODatabaseProvider.withSession(block: (ODatabaseSession) -> R): R { fun ODatabaseProvider.withCurrentOrNewSession( requireNoActiveTransaction: Boolean = false, - block: (ODatabaseSession) -> R + block: (DatabaseSession) -> R ): R { return if (hasActiveSession()) { - val activeSession = ODatabaseSession.getActiveSession() as ODatabaseSession + val activeSession = DatabaseRecordThreadLocal.instance().getIfDefined() as DatabaseSession if (requireNoActiveTransaction) { activeSession.requireNoActiveTransaction() } @@ -69,15 +71,15 @@ fun ODatabaseProvider.withCurrentOrNewSession( } } -internal fun ODatabaseSession.hasActiveTransaction(): Boolean { +internal fun DatabaseSession.hasActiveTransaction(): Boolean { return isActiveOnCurrentThread && activeTxCount() > 0 } -internal fun ODatabaseSession.requireActiveTransaction() { +internal fun DatabaseSession.requireActiveTransaction() { require(hasActiveTransaction()) { "No active transaction is found. Happy debugging, pal!" } } -internal fun ODatabaseSession.requireNoActiveTransaction() { +internal fun DatabaseSession.requireNoActiveTransaction() { assert(isActiveOnCurrentThread && activeTxCount() == 0) { "Active transaction is detected. Changes in the schema must not happen in a transaction." } } @@ -86,24 +88,28 @@ internal fun requireNoActiveSession() { } internal fun hasActiveSession(): Boolean { - val db = ODatabaseRecordThreadLocal.instance().getIfDefined() + val db = DatabaseRecordThreadLocal.instance().getIfDefined() return db != null } -fun initOrientDbServer(config: ODatabaseConnectionConfig): OrientDB { - val orientConfig = OrientDBConfigBuilder().apply { - addConfig(OGlobalConfiguration.AUTO_CLOSE_AFTER_DELAY, true) - addConfig(OGlobalConfiguration.AUTO_CLOSE_DELAY, config.closeAfterDelayTimeout) - addConfig(OGlobalConfiguration.NON_TX_READS_WARNING_MODE, "SILENT") +fun iniYouTrackDb(config: ODatabaseConnectionConfig): YouTrackDB { + val orientConfig = YouTrackDBConfig.builder().apply { + addGlobalConfigurationParameter(GlobalConfiguration.AUTO_CLOSE_AFTER_DELAY, true) + addGlobalConfigurationParameter( + GlobalConfiguration.AUTO_CLOSE_DELAY, + config.closeAfterDelayTimeout + ) + addGlobalConfigurationParameter(GlobalConfiguration.NON_TX_READS_WARNING_MODE, "SILENT") }.build() require(config.userName.matches(Regex("^[a-zA-Z0-9]*$"))) - val dbType = config.databaseType.name.lowercase() - val db = OrientDB("$dbType:${config.databaseRoot}", orientConfig) + val db = YourTracks.embedded(config.databaseRoot, orientConfig) + try { db.execute("create system user ${config.userName} identified by :pass role root", mapOf( "pass" to config.password, )) - } catch (_: com.orientechnologies.orient.core.storage.ORecordDuplicatedException) { + } catch (_: RecordDuplicatedException) { } + return db } diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseProviderImpl.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseProviderImpl.kt index 516bdcdb4..52c3b95cd 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseProviderImpl.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseProviderImpl.kt @@ -15,11 +15,10 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.config.OGlobalConfiguration -import com.orientechnologies.orient.core.db.ODatabaseSession -import com.orientechnologies.orient.core.db.OrientDB -import com.orientechnologies.orient.core.db.OrientDBConfig -import com.orientechnologies.orient.core.db.OrientDBConfigBuilder +import com.jetbrains.youtrack.db.api.DatabaseSession +import com.jetbrains.youtrack.db.api.YouTrackDB +import com.jetbrains.youtrack.db.api.config.GlobalConfiguration +import com.jetbrains.youtrack.db.api.config.YouTrackDBConfig import java.io.File import java.util.* @@ -27,16 +26,22 @@ import java.util.* //todo this params also should be collected in some config entity class ODatabaseProviderImpl( private val config: ODatabaseConfig, - private val database: OrientDB + private val database: YouTrackDB ) : ODatabaseProvider { - private val orientConfig: OrientDBConfig + private val youTrackDBConfig: YouTrackDBConfig init { - orientConfig = OrientDBConfigBuilder().apply { - addConfig(OGlobalConfiguration.AUTO_CLOSE_AFTER_DELAY, true) - addConfig(OGlobalConfiguration.AUTO_CLOSE_DELAY, config.closeAfterDelayTimeout) + youTrackDBConfig = YouTrackDBConfig.builder().apply { + addGlobalConfigurationParameter(GlobalConfiguration.AUTO_CLOSE_AFTER_DELAY, true) + addGlobalConfigurationParameter( + GlobalConfiguration.AUTO_CLOSE_DELAY, + config.closeAfterDelayTimeout + ) config.cipherKey?.let { - addConfig(OGlobalConfiguration.STORAGE_ENCRYPTION_KEY, Base64.getEncoder().encodeToString(it)) + addGlobalConfigurationParameter( + GlobalConfiguration.STORAGE_ENCRYPTION_KEY, + Base64.getEncoder().encodeToString(it) + ) } config.tweakConfig(this) }.build() @@ -44,7 +49,7 @@ class ODatabaseProviderImpl( database.createIfNotExists( config.databaseName, config.databaseType, - orientConfig + youTrackDBConfig ) //todo migrate to some config entity instead of System props @@ -64,11 +69,14 @@ class ODatabaseProviderImpl( override val databaseLocation: String get() = File(config.connectionConfig.databaseRoot, config.databaseName).absolutePath - override fun acquireSession(): ODatabaseSession { + override fun acquireSession(): DatabaseSession { return acquireSessionImpl(true) } - override fun executeInASeparateSession(currentSession: ODatabaseSession, action: (ODatabaseSession) -> T): T { + override fun executeInASeparateSession( + currentSession: DatabaseSession, + action: (DatabaseSession) -> T + ): T { val result = try { acquireSessionImpl(checkNoActiveSession = false).use { session -> action(session) @@ -101,17 +109,22 @@ class ODatabaseProviderImpl( _readOnly = value } - private fun acquireSessionImpl(checkNoActiveSession: Boolean = true): ODatabaseSession { + private fun acquireSessionImpl(checkNoActiveSession: Boolean = true): DatabaseSession { if (checkNoActiveSession) { requireNoActiveSession() } - return database.cachedPool(config.databaseName, config.connectionConfig.userName, config.connectionConfig.password, orientConfig).acquire() + return database.cachedPool( + config.databaseName, + config.connectionConfig.userName, + config.connectionConfig.password, + youTrackDBConfig + ).acquire() } override fun close() { // OxygenDB cannot close the database if it is read-only (frozen) readOnly = false - if (config.closeDatabaseInDbProvider){ + if (config.closeDatabaseInDbProvider) { database.close() } } diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OEntityId.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OEntityId.kt index 9ca745bf6..e025a28e7 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OEntityId.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OEntityId.kt @@ -15,12 +15,11 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.id.ORID +import com.jetbrains.youtrack.db.api.record.RID import jetbrains.exodus.entitystore.EntityId interface OEntityId : EntityId { - - fun asOId(): ORID + fun asOId(): RID fun getTypeName(): String } \ No newline at end of file diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OEntityIterables.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OEntityIterables.kt index 03bc42fdb..e1eab1a53 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OEntityIterables.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OEntityIterables.kt @@ -15,17 +15,11 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.db.ODatabaseSession -import com.orientechnologies.orient.core.sql.executor.OResultSet +import com.jetbrains.youtrack.db.api.query.ResultSet import jetbrains.exodus.entitystore.Entity -fun OResultSet.toEntityIterator(store: OEntityStore): Iterator { +fun ResultSet.toEntityIterator(store: OEntityStore): Iterator { return this.vertexStream().map { OVertexEntity(it, store) }.iterator() } -fun ODatabaseSession.queryEntities(query: String, store: OEntityStore): Iterable { - return Iterable { - query(query).toEntityIterator(store) - } -} diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OPersistentEntityStore.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OPersistentEntityStore.kt index cde645d89..6d749690f 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OPersistentEntityStore.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OPersistentEntityStore.kt @@ -15,8 +15,8 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal -import com.orientechnologies.orient.core.db.ODatabaseSession +import com.jetbrains.youtrack.db.api.DatabaseSession +import com.jetbrains.youtrack.db.internal.core.db.DatabaseRecordThreadLocal import jetbrains.exodus.backup.BackupStrategy import jetbrains.exodus.bindings.ComparableBinding import jetbrains.exodus.entitystore.* @@ -74,22 +74,22 @@ class OPersistentEntityStore( return currentTx } - private fun onTransactionFinished(session: ODatabaseSession, tx: OStoreTransaction) { + private fun onTransactionFinished(session: DatabaseSession, tx: OStoreTransaction) { check(currentTransaction.get() == tx) { "The current transaction at EntityStore is different for one that just has finished. It must not happen." } check(!session.isClosed) { "The session should not be closed at this point." } currentTransaction.remove() session.close() } - private fun onTransactionDeactivated(session: ODatabaseSession, tx: OStoreTransaction) { + private fun onTransactionDeactivated(session: DatabaseSession, tx: OStoreTransaction) { check(currentTransaction.get() == tx) { "Impossible to deactivate the transaction. The transaction on the current thread is different from one that wants to suspend. It must not ever happen." } check(!tx.isFinished) { "Cannot deactivate a finished transaction" } check(!session.isClosed) { "Cannot deactivate a closed session" } currentTransaction.remove() - ODatabaseRecordThreadLocal.instance().remove() + DatabaseRecordThreadLocal.instance().remove() } - private fun onTransactionActivated(session: ODatabaseSession, tx: OStoreTransaction) { + private fun onTransactionActivated(session: DatabaseSession, tx: OStoreTransaction) { check(currentTransaction.get() == null) { "Impossible to activate the transaction. There is already an active transaction on the current thread." } check(!hasActiveSession()) { "There is an active session on the current thread" } check(!tx.isFinished) { "Cannot activate a finished transaction" } diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ORIDEntityId.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ORIDEntityId.kt index 04e5ca1ce..d10cf5e34 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ORIDEntityId.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/ORIDEntityId.kt @@ -15,25 +15,25 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.id.OEmptyRecordId -import com.orientechnologies.orient.core.id.ORID -import com.orientechnologies.orient.core.metadata.schema.OClass -import com.orientechnologies.orient.core.record.OVertex +import com.jetbrains.youtrack.db.api.record.RID +import com.jetbrains.youtrack.db.api.record.Vertex +import com.jetbrains.youtrack.db.api.schema.SchemaClass +import com.jetbrains.youtrack.db.internal.core.id.ImmutableRecordId import jetbrains.exodus.entitystore.EntityId class ORIDEntityId( private val classId: Int, private val localEntityId: Long, - private val oId: ORID, - private val schemaClass: OClass? + private val oId: RID, + private val schemaClass: SchemaClass? ) : OEntityId { companion object { - @JvmStatic - val EMPTY_ID: ORIDEntityId = ORIDEntityId(-1, -1, OEmptyRecordId(), null) + val EMPTY_ID: ORIDEntityId = ORIDEntityId(-1, -1, + ImmutableRecordId.EMPTY_RECORD_ID, null) - fun fromVertex(vertex: OVertex): ORIDEntityId { + fun fromVertex(vertex: Vertex): ORIDEntityId { val oClass = vertex.requireSchemaClass() val classId = oClass.requireClassId() val localEntityId = vertex.requireLocalEntityId() @@ -41,7 +41,7 @@ class ORIDEntityId( } } - override fun asOId(): ORID { + override fun asOId(): RID { return oId } diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OReadonlyVertexEntity.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OReadonlyVertexEntity.kt index 5079256e0..17fc4e51c 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OReadonlyVertexEntity.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OReadonlyVertexEntity.kt @@ -15,9 +15,10 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.record.OVertex +import com.jetbrains.youtrack.db.api.record.Vertex -class OReadonlyVertexEntity(vertex: OVertex, store: OEntityStore) : OVertexEntity(vertex, store) { + +class OReadonlyVertexEntity(vertex: Vertex, store: OEntityStore) : OVertexEntity(vertex, store) { override fun requireActiveWritableTransaction(): OStoreTransaction { throw IllegalArgumentException("Can't update readonly entity (id=${id})") } diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OSchemaBuddy.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OSchemaBuddy.kt index efec3b2f0..29fef3178 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OSchemaBuddy.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OSchemaBuddy.kt @@ -15,13 +15,12 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.db.ODatabaseSession -import com.orientechnologies.orient.core.metadata.schema.OClass -import com.orientechnologies.orient.core.metadata.sequence.OSequence -import com.orientechnologies.orient.core.metadata.sequence.OSequence.CreateParams -import com.orientechnologies.orient.core.metadata.sequence.OSequence.SEQUENCE_TYPE -import com.orientechnologies.orient.core.record.OVertex -import com.orientechnologies.orient.core.sql.executor.OResultSet +import com.jetbrains.youtrack.db.api.DatabaseSession +import com.jetbrains.youtrack.db.api.query.ResultSet +import com.jetbrains.youtrack.db.api.record.Vertex +import com.jetbrains.youtrack.db.api.schema.SchemaClass +import com.jetbrains.youtrack.db.internal.core.db.DatabaseSessionInternal +import com.jetbrains.youtrack.db.internal.core.metadata.sequence.DBSequence import jetbrains.exodus.entitystore.EntityRemovedInDatabaseException import jetbrains.exodus.entitystore.PersistentEntityId import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.CLASS_ID_CUSTOM_PROPERTY_NAME @@ -31,38 +30,47 @@ import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.localEntity import java.util.concurrent.ConcurrentHashMap interface OSchemaBuddy { - fun initialize(session: ODatabaseSession) + fun initialize(session: DatabaseSession) - fun getOEntityId(session: ODatabaseSession, entityId: PersistentEntityId): ORIDEntityId + fun getOEntityId(session: DatabaseSession, entityId: PersistentEntityId): ORIDEntityId /** * If the class has not been found, returns -1. It is how it was in the Classic Xodus. */ - fun getTypeId(session: ODatabaseSession, entityType: String): Int + fun getTypeId(session: DatabaseSession, entityType: String): Int - fun getType(session: ODatabaseSession, entityTypeId: Int): String + fun getType(session: DatabaseSession, entityTypeId: Int): String - fun requireTypeExists(session: ODatabaseSession, entityType: String) + fun requireTypeExists(session: DatabaseSession, entityType: String) - fun getOrCreateSequence(session: ODatabaseSession, sequenceName: String, initialValue: Long): OSequence + fun getOrCreateSequence( + session: DatabaseSession, + sequenceName: String, + initialValue: Long + ): DBSequence - fun getSequence(session: ODatabaseSession, sequenceName: String): OSequence + fun getSequence(session: DatabaseSession, sequenceName: String): DBSequence - fun getSequenceOrNull(session: ODatabaseSession, sequenceName: String): OSequence? + fun getSequenceOrNull(session: DatabaseSession, sequenceName: String): DBSequence? - fun updateSequence(session: ODatabaseSession, sequenceName: String, currentValue: Long) + fun updateSequence(session: DatabaseSession, sequenceName: String, currentValue: Long) - fun renameOClass(session: ODatabaseSession, oldName: String, newName: String) + fun renameOClass(session: DatabaseSession, oldName: String, newName: String) - fun getOrCreateEdgeClass(session: ODatabaseSession, linkName: String, outClassName: String, inClassName: String): OClass + fun getOrCreateEdgeClass( + session: DatabaseSession, + linkName: String, + outClassName: String, + inClassName: String + ): SchemaClass } class OSchemaBuddyImpl( private val dbProvider: ODatabaseProvider, autoInitialize: Boolean = true, -): OSchemaBuddy { +) : OSchemaBuddy { companion object { - val INTERNAL_CLASS_NAMES = hashSetOf(OClass.VERTEX_CLASS_NAME) + val INTERNAL_CLASS_NAMES = hashSetOf(SchemaClass.VERTEX_CLASS_NAME) } private val classIdToOClassId = ConcurrentHashMap>() @@ -75,33 +83,48 @@ class OSchemaBuddyImpl( } } - override fun initialize(session: ODatabaseSession) { + override fun initialize(session: DatabaseSession) { session.createClassIdSequenceIfAbsent() - for (oClass in session.metadata.schema.classes) { + for (oClass in session.schema.classes) { if (oClass.isVertexType && !INTERNAL_CLASS_NAMES.contains(oClass.name)) { - classIdToOClassId[oClass.requireClassId()] = oClass.defaultClusterId to oClass.name + classIdToOClassId[oClass.requireClassId()] = oClass.clusterIds[0] to oClass.name } } } - override fun getOrCreateSequence(session: ODatabaseSession, sequenceName: String, initialValue: Long): OSequence { - val oSequence = session.metadata.sequenceLibrary.getSequence(sequenceName) + override fun getOrCreateSequence( + session: DatabaseSession, + sequenceName: String, + initialValue: Long + ): DBSequence { + val oSequence = + (session as DatabaseSessionInternal).metadata.sequenceLibrary.getSequence(sequenceName) if (oSequence != null) return oSequence return session.executeInASeparateSessionIfCurrentHasTransaction(dbProvider) { sessionToWork -> - val params = CreateParams().setStart(initialValue).setIncrement(1) - sessionToWork.metadata.sequenceLibrary.createSequence(sequenceName, SEQUENCE_TYPE.ORDERED, params) + val params = DBSequence.CreateParams().setStart(initialValue).setIncrement(1) + (sessionToWork as DatabaseSessionInternal).metadata.sequenceLibrary.createSequence( + sequenceName, + DBSequence.SEQUENCE_TYPE.ORDERED, + params + ) } } - override fun renameOClass(session: ODatabaseSession, oldName: String, newName: String) { + override fun renameOClass(session: DatabaseSession, oldName: String, newName: String) { session.executeInASeparateSessionIfCurrentHasTransaction(dbProvider) { sessionToWork -> - val oldClass = sessionToWork.metadata.schema.getClass(oldName) ?: throw IllegalArgumentException("Class $oldName not found") - oldClass.setName(newName) + val oldClass = sessionToWork.schema.getClass(oldName) + ?: throw IllegalArgumentException("Class $oldName not found") + oldClass.setName(sessionToWork, newName) } } - override fun getOrCreateEdgeClass(session: ODatabaseSession, linkName: String, outClassName: String, inClassName: String): OClass { + override fun getOrCreateEdgeClass( + session: DatabaseSession, + linkName: String, + outClassName: String, + inClassName: String + ): SchemaClass { val edgeClassName = OVertexEntity.edgeClassName(linkName) val oClass = session.getClass(edgeClassName) if (oClass != null) return oClass @@ -111,33 +134,52 @@ class OSchemaBuddyImpl( } } - override fun getSequence(session: ODatabaseSession, sequenceName: String): OSequence { - return session.metadata.sequenceLibrary.getSequence(sequenceName) ?: throw IllegalStateException("$sequenceName sequence not found") + override fun getSequence(session: DatabaseSession, sequenceName: String): DBSequence { + return (session as DatabaseSessionInternal).metadata.sequenceLibrary.getSequence( + sequenceName + ) + ?: throw IllegalStateException("$sequenceName sequence not found") } - override fun getSequenceOrNull(session: ODatabaseSession, sequenceName: String): OSequence? { - return session.metadata.sequenceLibrary.getSequence(sequenceName) + override fun getSequenceOrNull(session: DatabaseSession, sequenceName: String): DBSequence? { + return (session as DatabaseSessionInternal).metadata.sequenceLibrary.getSequence( + sequenceName + ) } - override fun updateSequence(session: ODatabaseSession, sequenceName: String, currentValue: Long) { + override fun updateSequence( + session: DatabaseSession, + sequenceName: String, + currentValue: Long + ) { session.executeInASeparateSessionIfCurrentHasTransaction(dbProvider) { sessionToWork -> sessionToWork.begin() - getSequence(sessionToWork, sequenceName).updateParams(CreateParams().setCurrentValue(currentValue)) + getSequence(sessionToWork, sequenceName).updateParams( + DBSequence.CreateParams().setCurrentValue( + currentValue + ) + ) sessionToWork.commit() } } - override fun getOEntityId(session: ODatabaseSession, entityId: PersistentEntityId): ORIDEntityId { + override fun getOEntityId( + session: DatabaseSession, + entityId: PersistentEntityId + ): ORIDEntityId { // Keep in mind that it is possible that we are given an entityId that is not in the database. // It is a valid case. val classId = entityId.typeId val localEntityId = entityId.localId val oClassId = classIdToOClassId[classId]?.first ?: return ORIDEntityId.EMPTY_ID - val schema = session.metadata.schema + val schema = session.schema val oClass = schema.getClassByClusterId(oClassId) ?: return ORIDEntityId.EMPTY_ID - val resultSet: OResultSet = session.query("SELECT FROM ${oClass.name} WHERE $LOCAL_ENTITY_ID_PROPERTY_NAME = ?", localEntityId) + val resultSet: ResultSet = session.query( + "SELECT FROM ${oClass.name} WHERE $LOCAL_ENTITY_ID_PROPERTY_NAME = ?", + localEntityId + ) val oid = if (resultSet.hasNext()) { val result = resultSet.next() result.toVertex()?.identity ?: return ORIDEntityId.EMPTY_ID @@ -148,29 +190,34 @@ class OSchemaBuddyImpl( return ORIDEntityId(classId, localEntityId, oid, oClass) } - override fun getTypeId(session: ODatabaseSession, entityType: String): Int { + override fun getTypeId(session: DatabaseSession, entityType: String): Int { return session.getClass(entityType)?.requireClassId() ?: -1 } override fun getType( - session: ODatabaseSession, + session: DatabaseSession, entityTypeId: Int ): String { val (_, typeName) = classIdToOClassId.computeIfAbsent(entityTypeId) { - val oClass = session.schema.classes.find { oClass -> oClass.getCustom(CLASS_ID_CUSTOM_PROPERTY_NAME)?.toInt() == entityTypeId } ?: throw EntityRemovedInDatabaseException("Invalid type ID $entityTypeId") + val oClass = session.schema.classes.find { oClass -> + oClass.getCustom(CLASS_ID_CUSTOM_PROPERTY_NAME)?.toInt() == entityTypeId + } ?: throw EntityRemovedInDatabaseException("Invalid type ID $entityTypeId") oClass.requireClassId() to oClass.name } return typeName } - override fun requireTypeExists(session: ODatabaseSession, entityType: String) { + override fun requireTypeExists(session: DatabaseSession, entityType: String) { val oClass = session.getClass(entityType) check(oClass != null) { "$entityType has not been found" } } } -fun ODatabaseSession.executeInASeparateSessionIfCurrentHasTransaction(dbProvider: ODatabaseProvider, action: (ODatabaseSession) -> T): T { +fun DatabaseSession.executeInASeparateSessionIfCurrentHasTransaction( + dbProvider: ODatabaseProvider, + action: (DatabaseSession) -> T +): T { return if (this.hasActiveTransaction()) { dbProvider.executeInASeparateSession(this) { newSession -> action(newSession) @@ -180,40 +227,45 @@ fun ODatabaseSession.executeInASeparateSessionIfCurrentHasTransaction(dbProv } } -fun ODatabaseSession.createClassIdSequenceIfAbsent(startFrom: Long = -1L) { +fun DatabaseSession.createClassIdSequenceIfAbsent(startFrom: Long = -1L) { createSequenceIfAbsent(CLASS_ID_SEQUENCE_NAME, startFrom) } -fun ODatabaseSession.createLocalEntityIdSequenceIfAbsent(oClass: OClass, startFrom: Long = -1L) { +fun DatabaseSession.createLocalEntityIdSequenceIfAbsent( + oClass: SchemaClass, + startFrom: Long = -1L +) { createSequenceIfAbsent(localEntityIdSequenceName(oClass.name), startFrom) } -private fun ODatabaseSession.createSequenceIfAbsent(sequenceName: String, startFrom: Long = 0L) { - val sequences = metadata.sequenceLibrary +private fun DatabaseSession.createSequenceIfAbsent(sequenceName: String, startFrom: Long = 0L) { + val sequences = (this as DatabaseSessionInternal).metadata.sequenceLibrary if (sequences.getSequence(sequenceName) == null) { - val params = CreateParams() + val params = DBSequence.CreateParams() params.start = startFrom - sequences.createSequence(sequenceName, SEQUENCE_TYPE.ORDERED, params) + sequences.createSequence(sequenceName, DBSequence.SEQUENCE_TYPE.ORDERED, params) } } -fun ODatabaseSession.setClassIdIfAbsent(oClass: OClass) { +fun DatabaseSession.setClassIdIfAbsent(oClass: SchemaClass) { if (oClass.getCustom(CLASS_ID_CUSTOM_PROPERTY_NAME) == null) { - val sequences = metadata.sequenceLibrary - val sequence: OSequence = sequences.getSequence(CLASS_ID_SEQUENCE_NAME) ?: throw IllegalStateException("$CLASS_ID_SEQUENCE_NAME not found") + val sequences = (this as DatabaseSessionInternal).metadata.sequenceLibrary + val sequence: DBSequence = sequences.getSequence(CLASS_ID_SEQUENCE_NAME) + ?: throw IllegalStateException("$CLASS_ID_SEQUENCE_NAME not found") - oClass.setCustom(CLASS_ID_CUSTOM_PROPERTY_NAME, sequence.next().toString()) + oClass.setCustom(this, CLASS_ID_CUSTOM_PROPERTY_NAME, sequence.next().toString()) } } -fun ODatabaseSession.setLocalEntityId(className: String, vertex: OVertex) { - val sequences = metadata.sequenceLibrary +fun DatabaseSession.setLocalEntityId(className: String, vertex: Vertex) { + val sequences = (this as DatabaseSessionInternal).metadata.sequenceLibrary val sequenceName = localEntityIdSequenceName(className) - val sequence: OSequence = sequences.getSequence(sequenceName) ?: throw IllegalStateException("$sequenceName not found") + val sequence: DBSequence = sequences.getSequence(sequenceName) + ?: throw IllegalStateException("$sequenceName not found") vertex.setProperty(LOCAL_ENTITY_ID_PROPERTY_NAME, sequence.next()) } -fun ODatabaseSession.createVertexClassWithClassId(className: String): OClass { +fun DatabaseSession.createVertexClassWithClassId(className: String): SchemaClass { requireNoActiveTransaction() createClassIdSequenceIfAbsent() val oClass = createVertexClass(className) @@ -222,7 +274,7 @@ fun ODatabaseSession.createVertexClassWithClassId(className: String): OClass { return oClass } -internal fun ODatabaseSession.getOrCreateVertexClass(className: String): OClass { +internal fun DatabaseSession.getOrCreateVertexClass(className: String): SchemaClass { val existingClass = this.getClass(className) if (existingClass != null) return existingClass diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OStoreTransaction.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OStoreTransaction.kt index f3f2a969c..1a508bdc6 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OStoreTransaction.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OStoreTransaction.kt @@ -15,11 +15,11 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.metadata.schema.OClass -import com.orientechnologies.orient.core.metadata.sequence.OSequence -import com.orientechnologies.orient.core.record.ORecord -import com.orientechnologies.orient.core.record.OVertex -import com.orientechnologies.orient.core.sql.executor.OResultSet +import com.jetbrains.youtrack.db.api.query.ResultSet +import com.jetbrains.youtrack.db.api.record.DBRecord +import com.jetbrains.youtrack.db.api.record.Vertex +import com.jetbrains.youtrack.db.api.schema.SchemaClass +import com.jetbrains.youtrack.db.internal.core.metadata.sequence.DBSequence import jetbrains.exodus.entitystore.PersistentEntityId import jetbrains.exodus.entitystore.StoreTransaction @@ -40,17 +40,17 @@ interface OStoreTransaction : StoreTransaction { fun getRecord(id: OEntityId): T - where T: ORecord + where T : DBRecord fun newEntity(entityType: String, localEntityId: Long): OVertexEntity - fun generateEntityId(entityType: String, vertex: OVertex) + fun generateEntityId(entityType: String, vertex: Vertex) - fun bindToSession(vertex: OVertex):OVertex + fun bindToSession(vertex: Vertex): Vertex fun bindToSession(entity: OVertexEntity): OVertexEntity - fun query(sql: String, params: Map): OResultSet + fun query(sql: String, params: Map): ResultSet fun getOEntityId(entityId: PersistentEntityId): OEntityId @@ -60,17 +60,21 @@ interface OStoreTransaction : StoreTransaction { fun getTypeId(entityType: String): Int /** - If the class has not been found, will throw EntityRemovedInDatabaseException with invalid type id + If the class has not been found, will throw EntityRemovedInDatabaseException with invalid type id */ fun getType(entityTypeId: Int): String - fun getOSequence(sequenceName: String): OSequence + fun getOSequence(sequenceName: String): DBSequence fun updateOSequence(sequenceName: String, currentValue: Long) fun renameOClass(oldName: String, newName: String) - fun getOrCreateEdgeClass(linkName: String, outClassName: String, inClassName: String): OClass + fun getOrCreateEdgeClass( + linkName: String, + outClassName: String, + inClassName: String + ): SchemaClass - fun bindResultSet(resultSet: OResultSet) + fun bindResultSet(resultSet: ResultSet) } diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OStoreTransactionImpl.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OStoreTransactionImpl.kt index 1b96e4a62..3236c029f 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OStoreTransactionImpl.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OStoreTransactionImpl.kt @@ -15,17 +15,17 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.common.concur.lock.OModificationOperationProhibitedException -import com.orientechnologies.orient.core.db.ODatabaseSession -import com.orientechnologies.orient.core.db.ODatabaseSessionInternal -import com.orientechnologies.orient.core.exception.ORecordNotFoundException -import com.orientechnologies.orient.core.id.OEmptyRecordId -import com.orientechnologies.orient.core.metadata.schema.OClass -import com.orientechnologies.orient.core.metadata.sequence.OSequence -import com.orientechnologies.orient.core.record.ORecord -import com.orientechnologies.orient.core.record.OVertex -import com.orientechnologies.orient.core.sql.executor.OResultSet -import com.orientechnologies.orient.core.tx.OTransaction.TXSTATUS +import com.jetbrains.youtrack.db.api.DatabaseSession +import com.jetbrains.youtrack.db.api.exception.ModificationOperationProhibitedException +import com.jetbrains.youtrack.db.api.exception.RecordNotFoundException +import com.jetbrains.youtrack.db.api.query.ResultSet +import com.jetbrains.youtrack.db.api.record.DBRecord +import com.jetbrains.youtrack.db.api.record.Vertex +import com.jetbrains.youtrack.db.api.schema.SchemaClass +import com.jetbrains.youtrack.db.internal.core.db.DatabaseSessionInternal +import com.jetbrains.youtrack.db.internal.core.id.ImmutableRecordId +import com.jetbrains.youtrack.db.internal.core.metadata.sequence.DBSequence +import com.jetbrains.youtrack.db.internal.core.tx.FrontendTransaction import jetbrains.exodus.entitystore.* import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.LOCAL_ENTITY_ID_PROPERTY_NAME import jetbrains.exodus.entitystore.orientdb.iterate.OEntityOfTypeIterable @@ -34,10 +34,10 @@ import jetbrains.exodus.entitystore.orientdb.iterate.property.* import jetbrains.exodus.entitystore.orientdb.query.OQueryCancellingPolicy import jetbrains.exodus.env.ReadonlyTransactionException -internal typealias TransactionEventHandler = (ODatabaseSession, OStoreTransaction) -> Unit +internal typealias TransactionEventHandler = (DatabaseSession, OStoreTransaction) -> Unit class OStoreTransactionImpl( - private val session: ODatabaseSession, + val session: DatabaseSession, private val store: OPersistentEntityStore, private val schemaBuddy: OSchemaBuddy, private val onFinished: TransactionEventHandler, @@ -56,27 +56,27 @@ class OStoreTransactionImpl( */ private val transactionIdImpl by lazy { requireActiveTransaction() - (session as ODatabaseSessionInternal).transaction.id.toLong() + (session as DatabaseSessionInternal).transaction.id } - private val resultSets: MutableCollection = arrayListOf() + private val resultSets: MutableCollection = arrayListOf() override fun getTransactionId(): Long { return transactionIdImpl } override fun getRecord(id: OEntityId): T - where T : ORecord { + where T : DBRecord { requireActiveTransaction() try { return session.load(id.asOId()) - } catch (e: ORecordNotFoundException) { + } catch (e: RecordNotFoundException) { throw EntityRemovedInDatabaseException(id.getTypeName(), id) } } - override fun query(sql: String, params: Map): OResultSet { + override fun query(sql: String, params: Map): ResultSet { requireActiveTransaction() return session.query(sql, params) } @@ -86,7 +86,7 @@ class OStoreTransactionImpl( } override fun isIdempotent(): Boolean { - return readOnly || (session as ODatabaseSessionInternal).transaction.recordOperations.none() + return readOnly || (session as DatabaseSessionInternal).transaction.recordOperations.none() } override fun isReadonly(): Boolean { @@ -94,7 +94,7 @@ class OStoreTransactionImpl( } override fun isFinished(): Boolean { - return session.status == ODatabaseSession.STATUS.CLOSED + return session.status == DatabaseSession.STATUS.CLOSED } override fun isCurrent(): Boolean { @@ -106,9 +106,15 @@ class OStoreTransactionImpl( } override fun requireActiveTransaction() { - check(session.status == ODatabaseSession.STATUS.OPEN) { "The transaction is finished, the internal session state: ${session.status}" } - check(session.isActiveOnCurrentThread) { "The active session is no the session the transaction was started in" } - check((session as ODatabaseSessionInternal).transaction.status == TXSTATUS.BEGUN) { "The current OTransaction status is ${session.transaction.status}, but the status ${TXSTATUS.BEGUN} was expected." } + check(session.status == DatabaseSession.STATUS.OPEN) { + "The transaction is finished, the internal session state: ${session.status}" + } + check(session.isActiveOnCurrentThread) { + "The active session is no the session the transaction was started in" + } + check((session as DatabaseSessionInternal).transaction.status == FrontendTransaction.TXSTATUS.BEGUN) { + "The current OTransaction status is ${session.transaction.status}, but the status ${FrontendTransaction.TXSTATUS.BEGUN} was expected." + } } override fun requireActiveWritableTransaction() { @@ -122,13 +128,13 @@ class OStoreTransactionImpl( } override fun activateOnCurrentThread() { - check(session.status == ODatabaseSession.STATUS.OPEN) { "The transaction is finished, the internal session state: ${session.status}" } + check(session.status == DatabaseSession.STATUS.OPEN) { "The transaction is finished, the internal session state: ${session.status}" } check(!session.isActiveOnCurrentThread) { "The transaction is already active on the current thread" } onActivated(session, this) } fun begin() { - check(session.status == ODatabaseSession.STATUS.OPEN) { "The session status is ${session.status}. But ${ODatabaseSession.STATUS.OPEN} is required." } + check(session.status == DatabaseSession.STATUS.OPEN) { "The session status is ${session.status}. But ${DatabaseSession.STATUS.OPEN} is required." } check(session.isActiveOnCurrentThread) { "The session is not active on the current thread" } check(session.activeTxCount() == 0) { "The session must not have a transaction" } try { @@ -156,7 +162,7 @@ class OStoreTransactionImpl( try { session.commit() session.begin() - } catch (_: OModificationOperationProhibitedException) { + } catch (_: ModificationOperationProhibitedException) { throw ReadonlyTransactionException() } finally { cleanUpTxIfNeeded() @@ -174,7 +180,7 @@ class OStoreTransactionImpl( } } - override fun bindToSession(vertex: OVertex): OVertex { + override fun bindToSession(vertex: Vertex): Vertex { requireActiveTransaction() return session.bindToSession(vertex) @@ -201,8 +207,8 @@ class OStoreTransactionImpl( } private fun cleanUpTxIfNeeded() { - if (session.status == ODatabaseSession.STATUS.OPEN && session.activeTxCount() == 0) { - resultSets.forEach(OResultSet::close) + if (session.status == DatabaseSession.STATUS.OPEN && session.activeTxCount() == 0) { + resultSets.forEach(ResultSet::close) resultSets.clear() onFinished(session, this) } @@ -217,7 +223,7 @@ class OStoreTransactionImpl( schemaBuddy.requireTypeExists(session, entityType) val vertex = session.newVertex(entityType) session.setLocalEntityId(entityType, vertex) - vertex.save() + vertex.save() return OVertexEntity(vertex, store) } @@ -226,13 +232,13 @@ class OStoreTransactionImpl( schemaBuddy.requireTypeExists(session, entityType) val vertex = session.newVertex(entityType) vertex.setProperty(LOCAL_ENTITY_ID_PROPERTY_NAME, localEntityId) - vertex.save() + vertex.save() return OVertexEntity(vertex, store) } - override fun generateEntityId(entityType: String, vertex: OVertex) { + override fun generateEntityId(entityType: String, vertex: Vertex) { session.setLocalEntityId(entityType, vertex) - vertex.save() + vertex.save() } override fun saveEntity(entity: Entity) { @@ -248,16 +254,16 @@ class OStoreTransactionImpl( throw EntityRemovedInDatabaseException(oId.getTypeName(), id) } try { - val vertex: OVertex = session.load(oId.asOId()) + val vertex: Vertex = session.load(oId.asOId()) return OVertexEntity(vertex, store) - } catch (e: ORecordNotFoundException) { + } catch (e: RecordNotFoundException) { throw EntityRemovedInDatabaseException(oId.getTypeName(), id) } } override fun getEntityTypes(): MutableList { requireActiveTransaction() - return session.metadata.schema.classes.map { it.name }.toMutableList() + return session.schema.classes.map { it.name }.toMutableList() } override fun getAll(entityType: String): EntityIterable { @@ -447,7 +453,10 @@ class OStoreTransactionImpl( val legacyId = PersistentEntityId.toEntityId(representation) val oEntityId = store.requireOEntityId(legacyId) return if (oEntityId == ORIDEntityId.EMPTY_ID) { - ORIDEntityId(legacyId.typeId, legacyId.localId, OEmptyRecordId(), null) + ORIDEntityId( + legacyId.typeId, legacyId.localId, + ImmutableRecordId.EMPTY_RECORD_ID, null + ) } else { oEntityId } @@ -464,7 +473,7 @@ class OStoreTransactionImpl( return OSequenceImpl(sequenceName, store) } - override fun getOSequence(sequenceName: String): OSequence { + override fun getOSequence(sequenceName: String): DBSequence { requireActiveTransaction() return schemaBuddy.getSequence(session, sequenceName) } @@ -483,7 +492,7 @@ class OStoreTransactionImpl( linkName: String, outClassName: String, inClassName: String - ): OClass { + ): SchemaClass { requireActiveTransaction() return schemaBuddy.getOrCreateEdgeClass(session, linkName, outClassName, inClassName) } @@ -510,7 +519,7 @@ class OStoreTransactionImpl( return schemaBuddy.getType(session, entityTypeId) } - override fun bindResultSet(resultSet: OResultSet) { + override fun bindResultSet(resultSet: ResultSet) { resultSets.add(resultSet) } diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OVertexEntity.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OVertexEntity.kt index cead0a857..5c6a32064 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OVertexEntity.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/OVertexEntity.kt @@ -15,16 +15,17 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.db.record.OTrackedSet -import com.orientechnologies.orient.core.db.record.ridbag.ORidBag -import com.orientechnologies.orient.core.id.ORID -import com.orientechnologies.orient.core.id.ORecordId -import com.orientechnologies.orient.core.metadata.schema.OClass -import com.orientechnologies.orient.core.record.ODirection -import com.orientechnologies.orient.core.record.OEdge -import com.orientechnologies.orient.core.record.ORecordAbstract -import com.orientechnologies.orient.core.record.OVertex -import com.orientechnologies.orient.core.record.impl.ORecordBytes +import com.jetbrains.youtrack.db.api.record.Direction +import com.jetbrains.youtrack.db.api.record.Edge +import com.jetbrains.youtrack.db.api.record.RID +import com.jetbrains.youtrack.db.api.record.Vertex +import com.jetbrains.youtrack.db.api.schema.SchemaClass +import com.jetbrains.youtrack.db.internal.core.db.DatabaseSessionInternal +import com.jetbrains.youtrack.db.internal.core.db.record.TrackedSet +import com.jetbrains.youtrack.db.internal.core.db.record.ridbag.RidBag +import com.jetbrains.youtrack.db.internal.core.id.RecordId +import com.jetbrains.youtrack.db.internal.core.record.RecordAbstract +import com.jetbrains.youtrack.db.internal.core.record.impl.RecordBytes import jetbrains.exodus.ByteIterable import jetbrains.exodus.entitystore.Entity import jetbrains.exodus.entitystore.EntityId @@ -43,7 +44,7 @@ import java.io.File import java.io.InputStream import kotlin.jvm.optionals.getOrNull -open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEntity { +open class OVertexEntity(vertex: Vertex, private val store: OEntityStore) : OEntity { companion object : KLogging() { const val EDGE_CLASS_SUFFIX = "_link" @@ -84,7 +85,7 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn } } - private var vertexRecord: OVertex + private var vertexRecord: Vertex private var oEntityId: ORIDEntityId init { @@ -99,7 +100,7 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn vertexRecord = v } - val vertex: OVertex + val vertex: Vertex get() { if (vertexRecord.isUnloaded) { val session = store.requireActiveTransaction() @@ -130,11 +131,10 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn override fun resetToNew() { val clusterId = vertexRecord.identity.clusterId - vertexRecord.identity.reset() - - (vertexRecord as ORecordAbstract).also { + (vertexRecord.identity as RecordId).reset() + (vertexRecord as RecordAbstract).also { it.resetToNew() - (it.identity as ORecordId).clusterId = clusterId + (it.identity as RecordId).clusterId = clusterId } } @@ -173,7 +173,7 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn return false } else { vertex.setProperty(propertyName, value) - vertex.save() + vertex.save() return true } } @@ -184,15 +184,15 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn } else { vertex.setProperty(propertyName, value) } - vertex.save() - return vertex.getProperty>(propertyName)?.isTransactionModified == true + vertex.save() + return vertex.getProperty>(propertyName)?.isTransactionModified == true } override fun deleteProperty(propertyName: String): Boolean { requireActiveWritableTransaction() if (vertex.hasProperty(propertyName)) { vertex.removeProperty(propertyName) - vertex.save() + vertex.save() return true } else { return false @@ -215,7 +215,7 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn override fun getBlob(blobName: String): InputStream? { requireActiveTx() - val blob: ORecordBytes = vertex.getProperty(blobName) ?: return null + val blob: RecordBytes = vertex.getProperty(blobName) ?: return null return ByteArrayInputStream(blob.toStream()) } @@ -233,10 +233,10 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn } val allBytes = blob.readAllBytes() - val oBlob = ORecordBytes(allBytes) + val oBlob = RecordBytes(allBytes) vertex.setProperty(blobName, oBlob) vertex.setProperty(blobSizeProperty(blobName), allBytes.size.toLong()) - vertex.save() + vertex.save() } override fun deleteBlob(blobName: String): Boolean { @@ -245,7 +245,7 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn vertex.removeProperty(blobName) vertex.removeProperty(blobSizeProperty(blobName)) vertex.removeProperty(blobHashProperty(blobName)) - vertex.save() + vertex.save() return true } return false @@ -253,7 +253,7 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn override fun getBlobString(blobName: String): String? { requireActiveTx() - val blob: ORecordBytes = vertex.getProperty(blobName) ?: return null + val blob: RecordBytes = vertex.getProperty(blobName) ?: return null return UTFUtil.readUTF(ByteArrayInputStream(blob.toStream())) } @@ -282,11 +282,11 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn vertex.removeProperty(blobName) } - val oBlob = ORecordBytes(baos.toByteArray()) + val oBlob = RecordBytes(baos.toByteArray()) vertex.setProperty(blobName, oBlob) vertex.setProperty(blobHashProperty(blobName), blobString.hashCode()) vertex.setProperty(blobSizeProperty(blobName), baos.size().toLong()) - vertex.save() + vertex.save() return true } @@ -312,14 +312,14 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn return false } try { - val target = currentTx.getRecord(targetOId) + val target = currentTx.getRecord(targetOId) return currentTx.addLinkImpl(linkName, target) } catch (e: EntityRemovedInDatabaseException) { return false } } - private fun OStoreTransaction.addLinkImpl(linkName: String, target: OVertex): Boolean { + private fun OStoreTransaction.addLinkImpl(linkName: String, target: Vertex): Boolean { val outClassName = vertex.requireSchemaClass().name val inClassName = target.requireSchemaClass().name val edgeClass = getOrCreateEdgeClass(linkName, outClassName, inClassName) @@ -337,8 +337,13 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn Well, during the data migration process, there are no any indices and skipping this findEdge(...) call is exactly what we need. */ - val currentEdge: OEdge? = - if (edgeClass.areIndexed(OEdge.DIRECTION_IN, OEdge.DIRECTION_OUT)) { + val currentEdge: Edge? = + if (edgeClass.areIndexed( + vertex.boundedToSession, + Edge.DIRECTION_IN, + Edge.DIRECTION_OUT + ) + ) { findEdge(edgeClassName, target.identity) } else null @@ -346,17 +351,18 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn vertex.addEdge(target, edgeClassName) // If the link is indexed, we have to update the complementary internal property. vertex.addTargetEntityIdIfLinkIndexed(linkName, target.identity) - vertex.save() + vertex.save() return true } else { return false } } - private fun OVertex.addTargetEntityIdIfLinkIndexed(linkName: String, targetId: ORID) { + private fun Vertex.addTargetEntityIdIfLinkIndexed(linkName: String, targetId: RID) { val linkTargetEntityIdPropertyName = linkTargetEntityIdPropertyName(linkName) if (requireSchemaClass().existsProperty(linkTargetEntityIdPropertyName)) { - val bag = getProperty(linkTargetEntityIdPropertyName) ?: ORidBag() + val bag = getProperty(linkTargetEntityIdPropertyName) + ?: RidBag(boundedToSession as DatabaseSessionInternal) bag.add(targetId) setProperty(linkTargetEntityIdPropertyName, bag) } @@ -381,14 +387,14 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn override fun deleteLinks(linkName: String) { requireActiveWritableTransaction() val edgeClassName = edgeClassName(linkName) - vertex.getEdges(ODirection.OUT, edgeClassName).forEach { + vertex.getEdges(Direction.OUT, edgeClassName).forEach { it.delete() } vertex.deleteAllTargetEntityIdsIfLinkIndexed(linkName) - vertex.save() + vertex.save() } - private fun OStoreTransaction.deleteLinkImpl(linkName: String, targetId: ORID): Boolean { + private fun OStoreTransaction.deleteLinkImpl(linkName: String, targetId: RID): Boolean { val edgeClassName = edgeClassName(linkName) val edge = findEdge(edgeClassName, targetId) @@ -396,26 +402,27 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn edge.delete() // if the link in a composite index, we have to update the complementary internal property. vertex.deleteTargetEntityIdIfLinkIndexed(linkName, targetId) - vertex.save() + vertex.save() return true } return false } - private fun OVertex.deleteTargetEntityIdIfLinkIndexed(linkName: String, targetId: ORID) { + private fun Vertex.deleteTargetEntityIdIfLinkIndexed(linkName: String, targetId: RID) { val linkTargetEntityIdPropertyName = linkTargetEntityIdPropertyName(linkName) if (requireSchemaClass().existsProperty(linkTargetEntityIdPropertyName)) { - val bag = getProperty(linkTargetEntityIdPropertyName) ?: ORidBag() + val bag = getProperty(linkTargetEntityIdPropertyName) + ?: RidBag(boundedToSession as DatabaseSessionInternal) bag.remove(targetId) setProperty(linkTargetEntityIdPropertyName, bag) } } - private fun OVertex.deleteAllTargetEntityIdsIfLinkIndexed(linkName: String) { + private fun Vertex.deleteAllTargetEntityIdsIfLinkIndexed(linkName: String) { val propName = linkTargetEntityIdPropertyName(linkName) if (requireSchemaClass().existsProperty(propName)) { - setProperty(propName, ORidBag()) + setProperty(propName, RidBag(boundedToSession as DatabaseSessionInternal)) } } @@ -435,14 +442,14 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn return false } try { - val target = currentTx.getRecord(targetOId) + val target = currentTx.getRecord(targetOId) return currentTx.setLinkImpl(linkName, target) } catch (e: EntityRemovedInDatabaseException) { return false } } - private fun OStoreTransaction.setLinkImpl(linkName: String, target: OVertex?): Boolean { + private fun OStoreTransaction.setLinkImpl(linkName: String, target: Vertex?): Boolean { val currentLink = getLinkImpl(linkName) if (currentLink == target) { @@ -464,15 +471,15 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn return getLinkImpl(linkName).toOEntityOrNull() } - private fun getLinkImpl(linkName: String): OVertex? { + private fun getLinkImpl(linkName: String): Vertex? { val edgeClassName = edgeClassName(linkName) - return vertex.getVertices(ODirection.OUT, edgeClassName).firstOrNull() + return vertex.getVertices(Direction.OUT, edgeClassName).firstOrNull() } override fun getLinks(linkName: String): EntityIterable { val txn = requireActiveTx() val edgeClassName = edgeClassName(linkName) - val links = vertex.getVertices(ODirection.OUT, edgeClassName) + val links = vertex.getVertices(Direction.OUT, edgeClassName) return OVertexEntityIterable(txn, links, store, linkName, this.oEntityId) } @@ -499,14 +506,14 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn override fun getLinkNames(): List { requireActiveTx() return ArrayList( - vertex.getEdgeNames(ODirection.OUT) + vertex.getEdgeNames(Direction.OUT) .filter { it.endsWith(EDGE_CLASS_SUFFIX) } .map { it.substringBefore(EDGE_CLASS_SUFFIX) }) } - private fun OStoreTransaction.findEdge(edgeClassName: String, targetId: ORID): OEdge? { + private fun OStoreTransaction.findEdge(edgeClassName: String, targetId: RID): Edge? { val query = - "SELECT FROM $edgeClassName WHERE ${OEdge.DIRECTION_OUT} = :outId AND ${OEdge.DIRECTION_IN} = :inId" + "SELECT FROM $edgeClassName WHERE ${Edge.DIRECTION_OUT} = :outId AND ${Edge.DIRECTION_IN} = :inId" val result = query(query, mapOf("outId" to vertex.identity, "inId" to targetId)) val foundEdge = result.edgeStream().findFirst() return foundEdge.getOrNull() @@ -528,7 +535,7 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn override fun save(): OVertexEntity { requireActiveWritableTransaction() - vertex.save() + vertex.save() return this } @@ -538,27 +545,28 @@ open class OVertexEntity(vertex: OVertex, private val store: OEntityStore) : OEn return store.requireActiveWritableTransaction() } - private fun OVertex?.toOEntityOrNull(): OEntity? = this?.let { OVertexEntity(this, store) } + private fun Vertex?.toOEntityOrNull(): OEntity? = this?.let { OVertexEntity(this, store) } } -fun OClass.requireClassId(): Int { +fun SchemaClass.requireClassId(): Int { return getCustom(CLASS_ID_CUSTOM_PROPERTY_NAME)?.toInt() ?: throw IllegalStateException("classId not found for ${this.name}") } -fun OVertex.getTargetLocalEntityIds(linkName: String): ORidBag { - return getProperty(linkTargetEntityIdPropertyName(linkName)) ?: ORidBag() +fun Vertex.getTargetLocalEntityIds(linkName: String): RidBag { + return getProperty(linkTargetEntityIdPropertyName(linkName)) + ?: RidBag(boundedToSession as DatabaseSessionInternal) } -fun OVertex.setTargetLocalEntityIds(linkName: String, ids: ORidBag) { +fun Vertex.setTargetLocalEntityIds(linkName: String, ids: RidBag) { setProperty(linkTargetEntityIdPropertyName(linkName), ids) } -fun OVertex.requireSchemaClass(): OClass { +fun Vertex.requireSchemaClass(): SchemaClass { return schemaClass ?: throw IllegalStateException("schemaClass not found for $this") } -fun OVertex.requireLocalEntityId(): Long { +fun Vertex.requireLocalEntityId(): Long { return getProperty(LOCAL_ENTITY_ID_PROPERTY_NAME) ?: throw IllegalStateException("localEntityId not found for the vertex") } diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/iterate/link/OVertexEntityIterable.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/iterate/link/OVertexEntityIterable.kt index 629cb33e2..4cdfabce1 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/iterate/link/OVertexEntityIterable.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/iterate/link/OVertexEntityIterable.kt @@ -15,8 +15,8 @@ */ package jetbrains.exodus.entitystore.orientdb.iterate.link -import com.orientechnologies.common.util.OSizeable -import com.orientechnologies.orient.core.record.OVertex +import com.jetbrains.youtrack.db.api.record.Vertex +import com.jetbrains.youtrack.db.internal.common.util.Sizeable import jetbrains.exodus.entitystore.Entity import jetbrains.exodus.entitystore.EntityIterable import jetbrains.exodus.entitystore.EntityIterator @@ -32,7 +32,7 @@ import org.apache.commons.collections4.functors.EqualPredicate class OVertexEntityIterable( private val tx: OStoreTransaction, - private val vertices: Iterable, + private val vertices: Iterable, private val store: OEntityStore, private val linkName: String, private val targetEntityID: OEntityId @@ -64,13 +64,13 @@ class OVertexEntityIterable( override fun size() = when (vertices) { is Collection<*> -> vertices.size.toLong() - is OSizeable -> vertices.size().toLong() + is Sizeable -> vertices.size().toLong() else -> IterableUtils.size(vertices).toLong() } override fun count() = when (vertices) { is Collection<*> -> vertices.size.toLong() - is OSizeable -> vertices.size().toLong() + is Sizeable -> vertices.size().toLong() else -> -1 } diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/iterate/property/OSequenceImpl.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/iterate/property/OSequenceImpl.kt index f8ea486e1..691103d4e 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/iterate/property/OSequenceImpl.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/iterate/property/OSequenceImpl.kt @@ -15,7 +15,7 @@ */ package jetbrains.exodus.entitystore.orientdb.iterate.property -import com.orientechnologies.orient.core.metadata.sequence.OSequence +import com.jetbrains.youtrack.db.internal.core.metadata.sequence.DBSequence import jetbrains.exodus.entitystore.Sequence import jetbrains.exodus.entitystore.orientdb.OPersistentEntityStore @@ -36,7 +36,7 @@ internal class OSequenceImpl( currentTx.updateOSequence(sequenceName, l) } - private fun getOSequence(): OSequence { + private fun getOSequence(): DBSequence { val currentTx = store.requireActiveTransaction() return currentTx.getOSequence(sequenceName) } diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/query/OQueryCancellingPolicy.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/query/OQueryCancellingPolicy.kt index 337a38d31..9bd53cb25 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/query/OQueryCancellingPolicy.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/query/OQueryCancellingPolicy.kt @@ -15,7 +15,7 @@ */ package jetbrains.exodus.entitystore.orientdb.query -import com.orientechnologies.common.concur.OTimeoutException +import com.jetbrains.youtrack.db.internal.common.concur.TimeoutException import jetbrains.exodus.entitystore.QueryCancellingPolicy class OQueryTimeoutException(message: String, source: Throwable) : RuntimeException(message, source) { @@ -25,7 +25,7 @@ class OQueryTimeoutException(message: String, source: Throwable) : RuntimeExcept fun withTimeoutWrap(block: () -> R): R { try { return block() - } catch (e: OTimeoutException) { + } catch (e: TimeoutException) { throw OQueryTimeoutException("Query execution timed out", e) } } diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/query/OQueryExecution.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/query/OQueryExecution.kt index a9734284a..3dd04e725 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/query/OQueryExecution.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/query/OQueryExecution.kt @@ -15,13 +15,13 @@ */ package jetbrains.exodus.entitystore.orientdb.query -import com.orientechnologies.orient.core.sql.executor.OResultSet +import com.jetbrains.youtrack.db.api.query.ResultSet import jetbrains.exodus.entitystore.orientdb.OStoreTransaction import mu.KLogging object OQueryExecution : KLogging() { - fun execute(query: OQuery, tx: OStoreTransaction): OResultSet { + fun execute(query: OQuery, tx: OStoreTransaction): ResultSet { val sqlQuery = tx.buildSql(query) val resultSet = tx.query(sqlQuery.sql, sqlQuery.params) diff --git a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/query/OSelect.kt b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/query/OSelect.kt index a0ef210b8..88f6aba0d 100644 --- a/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/query/OSelect.kt +++ b/entity-store/src/main/kotlin/jetbrains/exodus/entitystore/orientdb/query/OSelect.kt @@ -15,7 +15,7 @@ */ package jetbrains.exodus.entitystore.orientdb.query -import com.orientechnologies.orient.core.id.ORID +import com.jetbrains.youtrack.db.api.record.RID interface OConditional { val condition: OCondition? @@ -115,7 +115,7 @@ class OLinkInFromSubQuerySelect( class OLinkInFromIdsSelect( val linkName: String, - val targetIds: List, + val targetIds: List, order: OOrder? = null, skip: OSkip? = null, limit: OLimit? = null @@ -126,13 +126,13 @@ class OLinkInFromIdsSelect( .append(targetIdsSql) } - private val targetIdsSql get() = "[${targetIds.map(ORID::toString).joinToString(", ")}]" + private val targetIdsSql get() = "[${targetIds.map(RID::toString).joinToString(", ")}]" } class OLinkOfTypeInFromIdsSelect( val linkName: String, - val targetIds: List, + val targetIds: List, val targetEntityType: String, order: OOrder? = null, skip: OSkip? = null, @@ -148,12 +148,12 @@ class OLinkOfTypeInFromIdsSelect( } - private val targetIdsSql get() = "[${targetIds.map(ORID::toString).joinToString(", ")}]" + private val targetIdsSql get() = "[${targetIds.map(RID::toString).joinToString(", ")}]" } class OLinkOutFromIdSelect( val linkName: String, - private val targetIds: List, + private val targetIds: List, order: OOrder? = null, skip: OSkip? = null, limit: OLimit? = null @@ -164,7 +164,7 @@ class OLinkOutFromIdSelect( .append(targetIdsSql) } - private val targetIdsSql get() = "[${targetIds.map(ORID::toString).joinToString(", ")}]" + private val targetIdsSql get() = "[${targetIds.map(RID::toString).joinToString(", ")}]" } @@ -326,7 +326,7 @@ class ODifferenceSelect( } class ORecordIdSelect( - val recordIds: Collection, + val recordIds: Collection, order: OOrder? = null ) : OSelectBase(order) { diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/WrongUsernameTest.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/WrongUsernameTest.kt index b082009f1..d078a21b4 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/WrongUsernameTest.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/WrongUsernameTest.kt @@ -15,9 +15,9 @@ */ package jetbrains.exodus.entitystore -import com.orientechnologies.orient.core.db.ODatabaseType +import com.jetbrains.youtrack.db.api.DatabaseType import jetbrains.exodus.entitystore.orientdb.ODatabaseConnectionConfig -import jetbrains.exodus.entitystore.orientdb.initOrientDbServer +import jetbrains.exodus.entitystore.orientdb.iniYouTrackDb import java.nio.file.Files import kotlin.io.path.absolutePathString import kotlin.test.Test @@ -30,10 +30,10 @@ class WrongUsernameTest { .builder() .withPassword("hello") .withUserName(";drop database users") - .withDatabaseType(ODatabaseType.MEMORY) + .withDatabaseType(DatabaseType.MEMORY) .withDatabaseRoot(Files.createTempDirectory("haha").absolutePathString()) .build() - initOrientDbServer(cfg) + iniYouTrackDb(cfg) } diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/DBCompactTest.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/DBCompactTest.kt index f169d8515..462e6360f 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/DBCompactTest.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/DBCompactTest.kt @@ -15,7 +15,7 @@ */ package jetbrains.exodus.entitystore.orientdb -import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryOrientDB +import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryYouTrackDB import jetbrains.exodus.entitystore.orientdb.testutil.Issues import jetbrains.exodus.entitystore.orientdb.testutil.createIssueImpl import org.junit.Assert @@ -26,7 +26,7 @@ class DBCompactTest { @Rule @JvmField - val orientDb = InMemoryOrientDB() + val orientDb = InMemoryYouTrackDB() @Test fun `database compacter should work`() { diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/EncryptedDBTest.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/EncryptedDBTest.kt index bf7ac2ad4..44cf79b46 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/EncryptedDBTest.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/EncryptedDBTest.kt @@ -15,11 +15,10 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.Orient -import com.orientechnologies.orient.core.db.ODatabaseType -import com.orientechnologies.orient.core.db.OrientDB -import com.orientechnologies.orient.core.exception.OStorageException -import com.orientechnologies.orient.core.record.OVertex +import com.jetbrains.youtrack.db.api.DatabaseType +import com.jetbrains.youtrack.db.api.YouTrackDB +import com.jetbrains.youtrack.db.internal.core.YouTrackDBEnginesManager +import com.jetbrains.youtrack.db.internal.core.exception.StorageException import jetbrains.exodus.crypto.toByteArray import mu.KLogging import org.junit.After @@ -43,7 +42,7 @@ class EncryptedDBTest(val number: Int) { } lateinit var provider: ODatabaseProviderImpl - lateinit var db: OrientDB + lateinit var db: YouTrackDB private fun createConfig(key: ByteArray?): ODatabaseConfig { val password = "admin" @@ -53,13 +52,13 @@ class EncryptedDBTest(val number: Int) { val connConfig = ODatabaseConnectionConfig.builder() .withPassword(password) .withUserName(username) - .withDatabaseType(ODatabaseType.PLOCAL) + .withDatabaseType(DatabaseType.PLOCAL) .withDatabaseRoot(Files.createTempDirectory("oxigenDB_test$number").absolutePathString()) .build() return ODatabaseConfig.builder() .withConnectionConfig(connConfig) - .withDatabaseType(ODatabaseType.PLOCAL) + .withDatabaseType(DatabaseType.PLOCAL) .withDatabaseName(dbName) .withCipherKey(key) .build() @@ -74,7 +73,7 @@ class EncryptedDBTest(val number: Int) { val config = createConfig(cipherKey) val noEncryptionConfig = createConfig(null) logger.info("Connect to db and create test vertex class") - db = initOrientDbServer(config.connectionConfig) + db = iniYouTrackDb(config.connectionConfig) provider = ODatabaseProviderImpl(config, db) provider.withSession { session -> session.createVertexClass("TEST") @@ -84,14 +83,14 @@ class EncryptedDBTest(val number: Int) { session.executeInTx { val vertex = session.newVertex("TEST") vertex.setProperty("hello", "world") - vertex.save() + vertex.save() } } db.close() logger.info("Close the DB") Thread.sleep(1000) logger.info("Connect to db one more time and read") - db = initOrientDbServer(config.connectionConfig) + db = iniYouTrackDb(config.connectionConfig) provider = ODatabaseProviderImpl(config, db) provider.withSession { session -> session.executeInTx { @@ -103,7 +102,7 @@ class EncryptedDBTest(val number: Int) { db.close() Thread.sleep(1000) logger.info("Connect to db one more time without encryption") - db = initOrientDbServer(config.connectionConfig) + db = iniYouTrackDb(config.connectionConfig) try { ODatabaseProviderImpl(noEncryptionConfig, db).apply { withSession { session -> @@ -114,7 +113,7 @@ class EncryptedDBTest(val number: Int) { } Assert.fail("Should not open") } - } catch (_: OStorageException) { + } catch (_: StorageException) { logger.info("As expected DB failed to initialize without key") } catch (e: AssertionError) { logger.info("As expected DB failed to initialize without key") @@ -128,11 +127,11 @@ class EncryptedDBTest(val number: Int) { fun close() { db.close() try { - if (!Orient.instance().isActive) { - Orient.instance().startup() + if (!YouTrackDBEnginesManager.instance().isActive) { + YouTrackDBEnginesManager.instance().startup() } } catch (_: Throwable) { - logger.error("CANNOT REINIT OXIGENDB") + logger.error("CANNOT REINIT YouTrackDB") } } } diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseProviderTest.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseProviderTest.kt index feccf4337..65988387a 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseProviderTest.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/ODatabaseProviderTest.kt @@ -15,9 +15,8 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.common.concur.lock.OModificationOperationProhibitedException -import com.orientechnologies.orient.core.record.OVertex -import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryOrientDB +import com.jetbrains.youtrack.db.api.exception.ModificationOperationProhibitedException +import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryYouTrackDB import jetbrains.exodus.entitystore.orientdb.testutil.OTestMixin import org.junit.Rule import org.junit.Test @@ -27,35 +26,35 @@ class ODatabaseProviderTest: OTestMixin { @Rule @JvmField - val orientDbRule = InMemoryOrientDB() + val orientDbRule = InMemoryYouTrackDB() - override val orientDb = orientDbRule + override val youTrackDb = orientDbRule @Test fun `read-only mode works`() { // by default it is read-write withTxSession { session -> val v = session.newVertex() - v.save() + v.save() } - orientDb.provider.readOnly = true - assertFailsWith { + youTrackDb.provider.readOnly = true + assertFailsWith { withTxSession { session -> val v = session.newVertex() - v.save() + v.save() } } - orientDb.provider.readOnly = false + youTrackDb.provider.readOnly = false withTxSession { session -> val v = session.newVertex() - v.save() + v.save() } - orientDb.provider.readOnly = true + youTrackDb.provider.readOnly = true // close() releases the read-only mode before closing the database (otherwise it throws exceptions) - orientDb.provider.close() + youTrackDb.provider.close() } } \ No newline at end of file diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OEntityTest.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OEntityTest.kt index a95536bdb..19a63c537 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OEntityTest.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OEntityTest.kt @@ -15,12 +15,11 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.metadata.schema.OType +import com.jetbrains.youtrack.db.api.schema.PropertyType import jetbrains.exodus.entitystore.EntityRemovedInDatabaseException import jetbrains.exodus.entitystore.PersistentEntityId import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.linkTargetEntityIdPropertyName import jetbrains.exodus.entitystore.orientdb.testutil.* -import jetbrains.exodus.entitystore.orientdb.testutil.createIssueImpl import org.junit.Rule import org.junit.Test import java.io.ByteArrayInputStream @@ -28,17 +27,16 @@ import java.util.* import kotlin.random.Random import kotlin.test.* -class OEntityTest: OTestMixin { - +class OEntityTest : OTestMixin { @Rule @JvmField - val orientDbRule = InMemoryOrientDB() + val youTrackDbRule = InMemoryYouTrackDB() - override val orientDb = orientDbRule + override val youTrackDb = youTrackDbRule @Test fun `create entities`() { - val (e1, e2) = orientDb.withStoreTx { tx -> + val (e1, e2) = youTrackDb.withStoreTx { tx -> val e1 = tx.newEntity(Issues.CLASS) val e2 = tx.newEntity(Issues.CLASS) assertEquals(tx.getTypeId(Issues.CLASS), e1.id.typeId) @@ -53,11 +51,11 @@ class OEntityTest: OTestMixin { Pair(e1, e2) } - orientDb.withStoreTx { tx -> + youTrackDb.withStoreTx { tx -> assertTrue(e1.delete()) } - orientDb.withStoreTx { tx -> + youTrackDb.withStoreTx { tx -> tx.getEntity(e2.id) assertFailsWith { tx.getEntity(e1.id) } assertEquals(1, tx.getAll(Issues.CLASS).size()) @@ -67,14 +65,14 @@ class OEntityTest: OTestMixin { @Test fun `an entity sees changes made to it in another part of the application`() { // your entity - val e1 = orientDb.withStoreTx { tx -> + val e1 = youTrackDb.withStoreTx { tx -> val e1 = tx.newEntity(Issues.CLASS) e1.setProperty("name", "Pumba") e1 } // this changes happen in another part of the application - val e1Again = orientDb.withStoreTx { tx -> + val e1Again = youTrackDb.withStoreTx { tx -> val e1Again = tx.getEntity(e1.id) e1Again.setProperty("name", "Bampu") e1Again @@ -84,7 +82,7 @@ class OEntityTest: OTestMixin { assertNotSame(e1, e1Again) // your entity sees changes made in another part of the application - orientDb.withStoreTx { tx -> + youTrackDb.withStoreTx { tx -> assertEquals("Bampu", e1.getProperty("name")) } } @@ -92,36 +90,36 @@ class OEntityTest: OTestMixin { @Test fun `rename entity type`() { - orientDb.withStoreTx { tx -> + youTrackDb.withStoreTx { tx -> for (i in 0..9) { tx.newEntity("Issue") } assertEquals(10, tx.getAll("Issue").size()) } - orientDb.withStoreTx { - orientDb.store.renameEntityType("Issue", "Comment") + youTrackDb.withStoreTx { + youTrackDb.store.renameEntityType("Issue", "Comment") } - orientDb.withStoreTx { tx -> + youTrackDb.withStoreTx { tx -> assertEquals(10, tx.getAll("Comment").size()) } } @Test fun `multiple links should work`() { - val issueA = orientDb.createIssue("A") - val issueB = orientDb.createIssue("B") - val issueC = orientDb.createIssue("C") + val issueA = youTrackDb.createIssue("A") + val issueB = youTrackDb.createIssue("B") + val issueC = youTrackDb.createIssue("C") val linkName = "link" - orientDb.withSession { session -> + youTrackDb.withSession { session -> session.createEdgeClass(OVertexEntity.edgeClassName(linkName)) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { issueA.addLink(linkName, issueB) issueA.addLink(linkName, issueC) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { val links = issueA.getLinks(linkName) assertTrue(links.contains(issueB)) assertTrue(links.contains(issueC)) @@ -139,13 +137,13 @@ class OEntityTest: OTestMixin { @Test fun `set a hard blob`() { val hardBlob = Random.nextBytes(1023) - val id = orientDb.withStoreTx { tx -> + val id = youTrackDb.withStoreTx { tx -> val issue = tx.createIssueImpl("iss") issue.setBlob("blob1", ByteArrayInputStream(hardBlob)) issue.id } - orientDb.withStoreTx { tx -> + youTrackDb.withStoreTx { tx -> val issue = tx.getEntity(id) val gotBlob = issue.getBlob("blob1")!!.readAllBytes() assertContentEquals(hardBlob, gotBlob) @@ -154,16 +152,16 @@ class OEntityTest: OTestMixin { @Test fun `set, change and delete blobs`() { - val issue = orientDb.createIssue("iss") + val issue = youTrackDb.createIssue("iss") // set val expectedBlob1 = byteArrayOf(0x01, 0x02) val expectedBlob2 = byteArrayOf(0x04, 0x05, 0x06) - orientDb.withStoreTx { + youTrackDb.withStoreTx { issue.setBlob("blob1", ByteArrayInputStream(expectedBlob1)) issue.setBlob("blob2", ByteArrayInputStream(expectedBlob2)) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertContentEquals(expectedBlob1, issue.getBlob("blob1")!!.readAllBytes()) assertContentEquals(expectedBlob2, issue.getBlob("blob2")!!.readAllBytes()) assertEquals(expectedBlob1.size.toLong(), issue.getBlobSize("blob1")) @@ -172,20 +170,20 @@ class OEntityTest: OTestMixin { // change val expectedResetBlob1 = byteArrayOf(0x01, 0x03, 0x04, 0x05) - orientDb.withStoreTx { + youTrackDb.withStoreTx { issue.setBlob("blob1", ByteArrayInputStream(expectedResetBlob1)) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { val resetBlob1 = issue.getBlob("blob1")!!.readAllBytes() assertContentEquals(expectedResetBlob1, resetBlob1) assertEquals(expectedResetBlob1.size.toLong(), issue.getBlobSize("blob1")) } // delete - orientDb.withStoreTx { + youTrackDb.withStoreTx { issue.deleteBlob("blob1") } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertNull(issue.getBlob("blob1")) assertEquals(-1, issue.getBlobSize("blob1")) // another blob is still here @@ -196,16 +194,16 @@ class OEntityTest: OTestMixin { @Test fun `set, change and delete string blobs`() { - val issue = orientDb.createIssue("iss") + val issue = youTrackDb.createIssue("iss") // set val expectedBlob1 = "Abc" val expectedBlob2 = "dxYz" - orientDb.withStoreTx { + youTrackDb.withStoreTx { issue.setBlobString("blob1", expectedBlob1) issue.setBlobString("blob2", expectedBlob2) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertEquals(expectedBlob1, issue.getBlobString("blob1")) assertEquals(expectedBlob2, issue.getBlobString("blob2")) assertEquals(expectedBlob1.length.toLong() + 2, issue.getBlobSize("blob1")) @@ -214,20 +212,20 @@ class OEntityTest: OTestMixin { // change val expectedResetBlob1 = "Caramba" - orientDb.withStoreTx { + youTrackDb.withStoreTx { issue.setBlobString("blob1", expectedResetBlob1) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { val resetBlob1 = issue.getBlobString("blob1") assertEquals(expectedResetBlob1, resetBlob1) assertEquals(expectedResetBlob1.length.toLong() + 2, issue.getBlobSize("blob1")) } // delete - orientDb.withStoreTx { + youTrackDb.withStoreTx { issue.deleteBlob("blob1") } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertNull(issue.getBlobString("blob1")) assertEquals(-1, issue.getBlobSize("blob1")) // another blob is still here @@ -238,18 +236,18 @@ class OEntityTest: OTestMixin { @Test fun `string blobs size`() { - val issue = orientDb.createIssue("iss") + val issue = youTrackDb.createIssue("iss") // set val englishStr = "mamba, mamba, caramba" val notEnglishStr = "вы хотите песен, их есть у меня" val mixedStr = "magic пипл woodoo пипл" - orientDb.withStoreTx { + youTrackDb.withStoreTx { issue.setBlobString("blob1", englishStr) issue.setBlobString("blob2", notEnglishStr) issue.setBlobString("blob3", mixedStr) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertEquals(englishStr, issue.getBlobString("blob1")) assertEquals(notEnglishStr, issue.getBlobString("blob2")) assertEquals(mixedStr, issue.getBlobString("blob3")) @@ -267,16 +265,16 @@ class OEntityTest: OTestMixin { @Test fun `add blob should be reflected in get blob names`() { - val issue = orientDb.createIssue("TestBlobs") + val issue = youTrackDb.createIssue("TestBlobs") - orientDb.withStoreTx { + youTrackDb.withStoreTx { issue.setBlob("blob1", ByteArrayInputStream(byteArrayOf(0x01, 0x02, 0x03))) issue.setBlob("blob2", ByteArrayInputStream(byteArrayOf(0x04, 0x05, 0x06))) issue.setBlob("blob3", ByteArrayInputStream(byteArrayOf(0x07, 0x08, 0x09))) issue.setProperty("version", 99) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { val blobNames = issue.getBlobNames() assertTrue(blobNames.contains("blob1")) assertTrue(blobNames.contains("blob2")) @@ -287,14 +285,14 @@ class OEntityTest: OTestMixin { @Test fun `set the same string blob should return false`() { - val issue = orientDb.createIssue("GetPropertyTest") + val issue = youTrackDb.createIssue("GetPropertyTest") val propertyName = "SampleProperty" val propertyValue = "SampleValue" - orientDb.withStoreTx { + youTrackDb.withStoreTx { issue.setBlobString(propertyName, propertyValue) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertEquals(false, issue.setBlobString(propertyName, propertyValue)) } } @@ -302,25 +300,29 @@ class OEntityTest: OTestMixin { @Test fun `delete links`() { val linkName = "link" - orientDb.withSession { session -> + youTrackDb.withSession { session -> session.createEdgeClass(OVertexEntity.edgeClassName(linkName)) val oClass = session.getClass(Issues.CLASS)!! // pretend that the link is indexed - oClass.createProperty(linkTargetEntityIdPropertyName(linkName), OType.LINKBAG) + oClass.createProperty( + session, + linkTargetEntityIdPropertyName(linkName), + PropertyType.LINKBAG + ) } - val issueA = orientDb.createIssue("A") - val issueB = orientDb.createIssue("B") - val issueC = orientDb.createIssue("C") - val issueD = orientDb.createIssue("D") + val issueA = youTrackDb.createIssue("A") + val issueB = youTrackDb.createIssue("B") + val issueC = youTrackDb.createIssue("C") + val issueD = youTrackDb.createIssue("D") - orientDb.withStoreTx { + youTrackDb.withStoreTx { issueA.addLink(linkName, issueB) issueA.addLink(linkName, issueC) issueA.addLink(linkName, issueD) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { issueA.deleteLink(linkName, issueB) issueA.deleteLink(linkName, issueC.id) @@ -337,18 +339,22 @@ class OEntityTest: OTestMixin { @Test fun `set links`() { val linkName = "link" - orientDb.withSession { session -> + youTrackDb.withSession { session -> session.createEdgeClass(OVertexEntity.edgeClassName(linkName)) val oClass = session.getClass(Issues.CLASS)!! // pretend that the link is indexed - oClass.createProperty(linkTargetEntityIdPropertyName(linkName), OType.LINKBAG) + oClass.createProperty( + session, + linkTargetEntityIdPropertyName(linkName), + PropertyType.LINKBAG + ) } - val issueA = orientDb.createIssue("A") - val issueB = orientDb.createIssue("B") - val issueC = orientDb.createIssue("C") + val issueA = youTrackDb.createIssue("A") + val issueB = youTrackDb.createIssue("B") + val issueC = youTrackDb.createIssue("C") - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertTrue(issueA.setLink(linkName, issueB)) assertFalse(issueA.setLink(linkName, issueB)) @@ -358,7 +364,7 @@ class OEntityTest: OTestMixin { assertTrue(bag.contains(issueB.vertex.identity)) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertTrue(issueA.setLink(linkName, issueC)) assertEquals(issueC, issueA.getLink(linkName)) @@ -367,7 +373,7 @@ class OEntityTest: OTestMixin { assertTrue(bag.contains(issueC.vertex.identity)) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertTrue(issueA.setLink(linkName, issueB.id)) assertFalse(issueA.setLink(linkName, issueB.id)) @@ -381,23 +387,27 @@ class OEntityTest: OTestMixin { @Test fun `should delete all links`() { val linkName = "link" - orientDb.withSession { session -> + youTrackDb.withSession { session -> session.createEdgeClass(OVertexEntity.edgeClassName(linkName)) val oClass = session.getClass(Issues.CLASS)!! // pretend that the link is indexed - oClass.createProperty(linkTargetEntityIdPropertyName(linkName), OType.LINKBAG) + oClass.createProperty( + session, + linkTargetEntityIdPropertyName(linkName), + PropertyType.LINKBAG + ) } - val issueA = orientDb.createIssue("A") - val issueB = orientDb.createIssue("B") - val issueC = orientDb.createIssue("C") + val issueA = youTrackDb.createIssue("A") + val issueB = youTrackDb.createIssue("B") + val issueC = youTrackDb.createIssue("C") - orientDb.withStoreTx { + youTrackDb.withStoreTx { issueA.addLink(linkName, issueB) issueA.addLink(linkName, issueC) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { issueA.deleteLinks(linkName) val links = issueA.getLinks(linkName) assertEquals(0, links.size()) @@ -410,24 +420,24 @@ class OEntityTest: OTestMixin { @Test fun `should replace a link correctly`() { val linkName = "link" - orientDb.withSession { session -> + youTrackDb.withSession { session -> session.createEdgeClass(OVertexEntity.edgeClassName(linkName)) } - val issueA = orientDb.createIssue("A") - val issueB = orientDb.createIssue("B") - val issueC = orientDb.createIssue("C") + val issueA = youTrackDb.createIssue("A") + val issueB = youTrackDb.createIssue("B") + val issueC = youTrackDb.createIssue("C") - orientDb.withStoreTx { + youTrackDb.withStoreTx { issueA.setLink(linkName, issueB.id) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertEquals(issueB, issueA.getLink(linkName)) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { issueA.setLink(linkName, issueC.id) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertEquals(issueC, issueA.getLink(linkName)) } } @@ -435,26 +445,26 @@ class OEntityTest: OTestMixin { @Test fun `setLink() and addLink() should work correctly with PersistentEntityId`() { val linkName = "link" - orientDb.withSession { session -> + youTrackDb.withSession { session -> session.createEdgeClass(OVertexEntity.edgeClassName(linkName)) } - val issueA = orientDb.createIssue("A") - val issueB = orientDb.createIssue("B") - val issueC = orientDb.createIssue("C") + val issueA = youTrackDb.createIssue("A") + val issueB = youTrackDb.createIssue("B") + val issueC = youTrackDb.createIssue("C") - orientDb.withStoreTx { + youTrackDb.withStoreTx { val legacyId = PersistentEntityId(issueB.id.typeId, issueB.id.localId) issueA.setLink(linkName, legacyId) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertEquals(issueB, issueA.getLink(linkName)) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { val legacyId = PersistentEntityId(issueC.id.typeId, issueC.id.localId) issueB.addLink(linkName, legacyId) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertEquals(issueB, issueA.getLink(linkName)) } } @@ -462,24 +472,24 @@ class OEntityTest: OTestMixin { @Test fun `setLink() and addLink() return false if the target entity is not found`() { val linkName = "link" - orientDb.withSession { session -> + youTrackDb.withSession { session -> session.createEdgeClass(OVertexEntity.edgeClassName(linkName)) } - val issueB = orientDb.createIssue("A") + val issueB = youTrackDb.createIssue("A") - orientDb.withStoreTx { tx -> + youTrackDb.withStoreTx { tx -> issueB.delete() } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertFalse(issueB.addLink(linkName, issueB.id)) assertFalse(issueB.addLink(linkName, ORIDEntityId.EMPTY_ID)) assertFalse(issueB.addLink(linkName, PersistentEntityId.EMPTY_ID)) assertFalse(issueB.addLink(linkName, PersistentEntityId(300, 300))) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertFalse(issueB.setLink(linkName, issueB.id)) assertFalse(issueB.setLink(linkName, ORIDEntityId.EMPTY_ID)) assertFalse(issueB.setLink(linkName, PersistentEntityId.EMPTY_ID)) @@ -489,14 +499,14 @@ class OEntityTest: OTestMixin { @Test fun `should get property`() { - val issue = orientDb.createIssue("GetPropertyTest") + val issue = youTrackDb.createIssue("GetPropertyTest") val propertyName = "SampleProperty" val propertyValue = "SampleValue" - orientDb.withStoreTx { + youTrackDb.withStoreTx { issue.setProperty(propertyName, propertyValue) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { val value = issue.getProperty(propertyName) assertEquals(propertyValue, value) } @@ -504,14 +514,14 @@ class OEntityTest: OTestMixin { @Test fun `should delete property`() { - val issue = orientDb.createIssue("DeletePropertyTest") + val issue = youTrackDb.createIssue("DeletePropertyTest") val propertyName = "SampleProperty" val propertyValue = "SampleValue" - orientDb.withStoreTx { + youTrackDb.withStoreTx { issue.setProperty(propertyName, propertyValue) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { issue.deleteProperty(propertyName) val value = issue.getProperty(propertyName) assertNull(value) @@ -520,9 +530,9 @@ class OEntityTest: OTestMixin { @Test fun `set, read, change and delete properties`() { - val issue = orientDb.createIssue("Test1") + val issue = youTrackDb.createIssue("Test1") - orientDb.withStoreTx { + youTrackDb.withStoreTx { issue.setProperty("hello", "world") issue.setProperty("june", 6) issue.setProperty("year", 44L) @@ -532,7 +542,7 @@ class OEntityTest: OTestMixin { issue.setProperty("boolProp", true) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertEquals("world", issue.getProperty("hello")) assertEquals(6, issue.getProperty("june")) assertEquals(44L, issue.getProperty("year")) @@ -542,7 +552,7 @@ class OEntityTest: OTestMixin { assertEquals(true, issue.getProperty("boolProp")) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertEquals(false, issue.setProperty("hello", "world")) assertEquals(false, issue.setProperty("june", 6)) assertEquals(false, issue.setProperty("year", 44L)) @@ -552,7 +562,7 @@ class OEntityTest: OTestMixin { assertEquals(false, issue.setProperty("boolProp", true)) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertEquals(true, issue.setProperty("hello", "xodus")) assertEquals(true, issue.setProperty("june", 8)) assertEquals(true, issue.setProperty("year", 34L)) @@ -562,7 +572,7 @@ class OEntityTest: OTestMixin { assertEquals(true, issue.setProperty("boolProp", false)) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertEquals("xodus", issue.getProperty("hello")) assertEquals(8, issue.getProperty("june")) assertEquals(34L, issue.getProperty("year")) @@ -573,16 +583,24 @@ class OEntityTest: OTestMixin { } - orientDb.withStoreTx { + youTrackDb.withStoreTx { issue.deleteProperty("dateProp") assertNull(issue.getProperty("dateProp")) // check that other properties are still there assertEquals("xodus", issue.getProperty("hello")) } - orientDb.withStoreTx { + youTrackDb.withStoreTx { assertEquals( - listOf("hello", "name", "june", "year", "floatProp", "doubleProp", "boolProp").sorted(), + listOf( + "hello", + "name", + "june", + "year", + "floatProp", + "doubleProp", + "boolProp" + ).sorted(), issue.propertyNames.sorted() ) } @@ -590,8 +608,8 @@ class OEntityTest: OTestMixin { @Test fun `it is forbidden to use entities outside transactions, except for id`() { - val iss = orientDb.createIssue("trista") - val anotherIss = orientDb.createIssue("sto") + val iss = youTrackDb.createIssue("trista") + val anotherIss = youTrackDb.createIssue("sto") // no properties assertFailsWith { iss.getProperty("name") } @@ -604,7 +622,12 @@ class OEntityTest: OTestMixin { assertFailsWith { iss.getBlob("blob1") } assertFailsWith { iss.getBlobSize("blob1") } assertFailsWith { iss.getBlobString("blob1") } - assertFailsWith { iss.setBlob("blob1", ByteArrayInputStream(byteArrayOf(100))) } + assertFailsWith { + iss.setBlob( + "blob1", + ByteArrayInputStream(byteArrayOf(100)) + ) + } assertFailsWith { iss.setBlobString("blob1", "opca") } assertFailsWith { iss.deleteBlob("blob1") } @@ -612,7 +635,7 @@ class OEntityTest: OTestMixin { assertFailsWith { iss.getLink("link1") } assertFailsWith { iss.linkNames } assertFailsWith { iss.setLink("link1", anotherIss) } - assertFailsWith { iss.deleteLink("link1",anotherIss) } + assertFailsWith { iss.deleteLink("link1", anotherIss) } assertFailsWith { iss.deleteLinks("link1") } assertFailsWith { iss.getLinks("link1") } assertFailsWith { iss.getLinks(listOf("link1")); } @@ -626,7 +649,7 @@ class OEntityTest: OTestMixin { val localIdSet = hashSetOf() val typeIdSet = hashSetOf() (0..1000).map { - val issue = orientDb.createIssue("Issue$it") + val issue = youTrackDb.createIssue("Issue$it") typeIdSet.add(issue.id.typeId) localIdSet.add(issue.id.localId) } @@ -635,8 +658,8 @@ class OEntityTest: OTestMixin { } @Test - fun `setProperty and setBlobString returns false in case of equal values`(){ - val iss = orientDb.createIssue("trista") + fun `setProperty and setBlobString returns false in case of equal values`() { + val iss = youTrackDb.createIssue("trista") withStoreTx { tx -> iss.setProperty("test", 1) iss.setBlobString("blobString", "hello") diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OPersistentStoreTest.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OPersistentStoreTest.kt index 8e776355f..d51d39a96 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OPersistentStoreTest.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OPersistentStoreTest.kt @@ -15,14 +15,14 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.id.OEmptyRecordId -import com.orientechnologies.orient.core.metadata.schema.OClass -import com.orientechnologies.orient.core.metadata.schema.OType -import com.orientechnologies.orient.core.storage.ORecordDuplicatedException +import com.jetbrains.youtrack.db.api.exception.RecordDuplicatedException +import com.jetbrains.youtrack.db.api.schema.PropertyType +import com.jetbrains.youtrack.db.api.schema.SchemaClass +import com.jetbrains.youtrack.db.internal.core.id.ChangeableRecordId import jetbrains.exodus.entitystore.EntityRemovedInDatabaseException import jetbrains.exodus.entitystore.PersistentEntityId import jetbrains.exodus.entitystore.StoreTransaction -import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryOrientDB +import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryYouTrackDB import jetbrains.exodus.entitystore.orientdb.testutil.Issues import jetbrains.exodus.entitystore.orientdb.testutil.Issues.CLASS import jetbrains.exodus.entitystore.orientdb.testutil.OTestMixin @@ -33,31 +33,36 @@ import org.junit.Rule import org.junit.Test import kotlin.test.assertFailsWith -class OPersistentStoreTest: OTestMixin { +class OPersistentStoreTest : OTestMixin { @Rule @JvmField - val orientDbRule = InMemoryOrientDB() + val orientDbRule = InMemoryYouTrackDB() - override val orientDb = orientDbRule + override val youTrackDb = orientDbRule @Test fun `renameEntityType() works only inside a transaction`() { // make sure the schema class is created - orientDb.createIssue("trista") + youTrackDb.createIssue("trista") - assertFailsWith { orientDb.store.renameEntityType(CLASS, "NewName") } + assertFailsWith { + youTrackDb.store.renameEntityType( + CLASS, + "NewName" + ) + } - orientDb.store.executeInTransaction { - orientDb.store.renameEntityType(CLASS, "NewName") + youTrackDb.store.executeInTransaction { + youTrackDb.store.renameEntityType(CLASS, "NewName") } } @Test fun renameClassTest() { val summary = "Hello, your product does not work" - orientDb.createIssue(summary) - val store = orientDb.store + youTrackDb.createIssue(summary) + val store = youTrackDb.store val newClassName = "Other${CLASS}" store.executeInTransaction { @@ -74,8 +79,8 @@ class OPersistentStoreTest: OTestMixin { @Test fun transactionPropertiesTest() { - val issue = orientDb.createIssue("Hello, nothing works") - val store = orientDb.store + val issue = youTrackDb.createIssue("Hello, nothing works") + val store = youTrackDb.store store.computeInTransaction { Assert.assertTrue(it.isIdempotent) issue.setProperty("version", "22") @@ -85,7 +90,7 @@ class OPersistentStoreTest: OTestMixin { @Test fun `create and increment sequence`() { - val store = orientDb.store + val store = youTrackDb.store val sequence = store.computeInTransaction { it.getSequence("first") } @@ -102,7 +107,7 @@ class OPersistentStoreTest: OTestMixin { @Test fun `create sequence with starting from`() { - val store = orientDb.store + val store = youTrackDb.store val sequence = store.computeInTransaction { it.getSequence("first", 99) } @@ -113,7 +118,7 @@ class OPersistentStoreTest: OTestMixin { @Test fun `can set actual value to sequence`() { - val store = orientDb.store + val store = youTrackDb.store val sequence = store.computeInTransaction { it.getSequence("first", 99) } @@ -127,12 +132,12 @@ class OPersistentStoreTest: OTestMixin { @Test fun `getEntity() works with both ORIDEntityId and PersistentEntityId`() { - val aId = orientDb.createIssue("A").id - val bId = orientDb.createIssue("B").id - val store = orientDb.store + val aId = youTrackDb.createIssue("A").id + val bId = youTrackDb.createIssue("B").id + val store = youTrackDb.store // use default ids - orientDb.store.executeInTransaction { + youTrackDb.store.executeInTransaction { val a = store.getEntity(aId) val b = store.getEntity(bId) @@ -141,7 +146,7 @@ class OPersistentStoreTest: OTestMixin { } // use legacy ids - orientDb.store.executeInTransaction { + youTrackDb.store.executeInTransaction { val legacyIdA = PersistentEntityId(aId.typeId, aId.localId) val legacyIdB = PersistentEntityId(bId.typeId, bId.localId) val a = store.getEntity(legacyIdA) @@ -155,73 +160,79 @@ class OPersistentStoreTest: OTestMixin { @Test fun `getEntity() throw exception the entity is not found`() { - val aId = orientDb.createIssue("A").id + val aId = youTrackDb.createIssue("A").id // delete the issue - orientDb.withTxSession { oSession -> + youTrackDb.withTxSession { oSession -> oSession.delete(aId.asOId()) } // entity not found - orientDb.store.executeInTransaction { tx -> + youTrackDb.store.executeInTransaction { tx -> assertFailsWith { - orientDb.store.getEntity(aId) + youTrackDb.store.getEntity(aId) } assertFailsWith { - orientDb.store.getEntity(PersistentEntityId(300, 300)) + youTrackDb.store.getEntity(PersistentEntityId(300, 300)) } assertFailsWith { - orientDb.store.getEntity(PersistentEntityId.EMPTY_ID) + youTrackDb.store.getEntity(PersistentEntityId.EMPTY_ID) } assertFailsWith { - orientDb.store.getEntity(ORIDEntityId.EMPTY_ID) + youTrackDb.store.getEntity(ORIDEntityId.EMPTY_ID) } } } @Test fun `getting OEntityId for not existing EntityId gives EMPTY_ID`() { - val issueId = orientDb.createIssue("trista").id + val issueId = youTrackDb.createIssue("trista").id val notExistingEntityId = PersistentEntityId(300, 301) val partiallyExistingEntityId1 = PersistentEntityId(issueId.typeId, 301) val partiallyExistingEntityId2 = PersistentEntityId(300, issueId.localId) val totallyExistingEntityId = PersistentEntityId(issueId.typeId, issueId.localId) - orientDb.store.executeInTransaction { - assertEquals(ORIDEntityId.EMPTY_ID, orientDb.store.getOEntityId(notExistingEntityId)) - assertEquals(ORIDEntityId.EMPTY_ID, orientDb.store.getOEntityId(partiallyExistingEntityId1)) - assertEquals(ORIDEntityId.EMPTY_ID, orientDb.store.getOEntityId(partiallyExistingEntityId2)) - assertEquals(issueId, orientDb.store.getOEntityId(totallyExistingEntityId)) + youTrackDb.store.executeInTransaction { + assertEquals(ORIDEntityId.EMPTY_ID, youTrackDb.store.getOEntityId(notExistingEntityId)) + assertEquals( + ORIDEntityId.EMPTY_ID, + youTrackDb.store.getOEntityId(partiallyExistingEntityId1) + ) + assertEquals( + ORIDEntityId.EMPTY_ID, + youTrackDb.store.getOEntityId(partiallyExistingEntityId2) + ) + assertEquals(issueId, youTrackDb.store.getOEntityId(totallyExistingEntityId)) } } @Test fun `toEntityId(presentation) from not existent idString will return OEntityId with correct xodus part and empty orient`() { - val issueId = orientDb.createIssue("trista").id + val issueId = youTrackDb.createIssue("trista").id val notExistingEntityId = PersistentEntityId(300, 301) val partiallyExistingEntityId1 = PersistentEntityId(issueId.typeId, 301) val partiallyExistingEntityId2 = PersistentEntityId(300, issueId.localId) val totallyExistingEntityId = PersistentEntityId(issueId.typeId, issueId.localId) - val empty = OEmptyRecordId() - orientDb.store.executeInTransaction { txn-> - with(txn.toEntityId(notExistingEntityId.toString()) as OEntityId){ + val empty = ChangeableRecordId() + youTrackDb.store.executeInTransaction { txn -> + with(txn.toEntityId(notExistingEntityId.toString()) as OEntityId) { assertEquals(notExistingEntityId.localId, localId) assertEquals(notExistingEntityId.typeId, typeId) assertEquals(empty.clusterId, asOId().clusterId) assertEquals(empty.clusterPosition, asOId().clusterPosition) } - with(txn.toEntityId(partiallyExistingEntityId1.toString()) as OEntityId){ + with(txn.toEntityId(partiallyExistingEntityId1.toString()) as OEntityId) { assertEquals(partiallyExistingEntityId1.localId, localId) assertEquals(partiallyExistingEntityId1.typeId, typeId) assertEquals(empty.clusterId, asOId().clusterId) assertEquals(empty.clusterPosition, asOId().clusterPosition) } - with(txn.toEntityId(partiallyExistingEntityId2.toString()) as OEntityId){ + with(txn.toEntityId(partiallyExistingEntityId2.toString()) as OEntityId) { assertEquals(partiallyExistingEntityId2.localId, localId) assertEquals(partiallyExistingEntityId2.typeId, typeId) assertEquals(empty.clusterId, asOId().clusterId) assertEquals(empty.clusterPosition, asOId().clusterPosition) } - with(txn.toEntityId(totallyExistingEntityId.toString()) as OEntityId){ + with(txn.toEntityId(totallyExistingEntityId.toString()) as OEntityId) { assertEquals(totallyExistingEntityId.localId, localId) assertEquals(totallyExistingEntityId.typeId, typeId) assertEquals(issueId.asOId(), asOId()) @@ -231,7 +242,7 @@ class OPersistentStoreTest: OTestMixin { @Test fun `propertyNames does not count internal properties`() { - val issue = orientDb.store.computeInTransaction { txn -> + val issue = youTrackDb.store.computeInTransaction { txn -> txn as OStoreTransaction val issue = txn.createIssue("Hello", "Critical") val project = txn.createProject("World") @@ -241,19 +252,33 @@ class OPersistentStoreTest: OTestMixin { issue.setProperty("hello", 1995) issue } - orientDb.store.executeInTransaction { - assertEquals(listOf(Issues.Props.PRIORITY, "name", "hello").sorted(), issue.propertyNames.sorted()) + youTrackDb.store.executeInTransaction { + assertEquals( + listOf(Issues.Props.PRIORITY, "name", "hello").sorted(), + issue.propertyNames.sorted() + ) } } @Test fun `requireOEntityId works correctly with different types of EntityId`() { - val issueId = orientDb.createIssue("trista").id + val issueId = youTrackDb.createIssue("trista").id - orientDb.store.executeInTransaction { - assertEquals(issueId, orientDb.store.requireOEntityId(issueId)) - assertEquals(issueId, orientDb.store.requireOEntityId(PersistentEntityId(issueId.typeId, issueId.localId))) - assertEquals(ORIDEntityId.EMPTY_ID, orientDb.store.requireOEntityId(PersistentEntityId.EMPTY_ID)) + youTrackDb.store.executeInTransaction { + assertEquals(issueId, youTrackDb.store.requireOEntityId(issueId)) + assertEquals( + issueId, + youTrackDb.store.requireOEntityId( + PersistentEntityId( + issueId.typeId, + issueId.localId + ) + ) + ) + assertEquals( + ORIDEntityId.EMPTY_ID, + youTrackDb.store.requireOEntityId(PersistentEntityId.EMPTY_ID) + ) } } @@ -261,8 +286,8 @@ class OPersistentStoreTest: OTestMixin { fun `computeInTransaction and Co handle exceptions properly`() { withSession { session -> val t1 = session.getOrCreateVertexClass("type1") - t1.createProperty("name", OType.STRING) - t1.createIndex("opca_index", OClass.INDEX_TYPE.UNIQUE, "name") + t1.createProperty(session, "name", PropertyType.STRING) + t1.createIndex(session, "opca_index", SchemaClass.INDEX_TYPE.UNIQUE, "name") } fun StoreTransaction.violateIndexRestriction() { val e1 = this.newEntity("type1") @@ -270,22 +295,23 @@ class OPersistentStoreTest: OTestMixin { e1.setProperty("name", "trista") e2.setProperty("name", "trista") } + /** * Here we check that nothing happens with the exception on the way up. * Our code that finishes the transaction must work correctly if there is no active session. */ - val store = orientDb.store - assertFailsWith { + val store = youTrackDb.store + assertFailsWith { store.computeInTransaction { tx -> tx.violateIndexRestriction() } } - assertFailsWith { + assertFailsWith { store.executeInTransaction { tx -> tx.violateIndexRestriction() } } - assertFailsWith { + assertFailsWith { withStoreTx { tx -> tx.violateIndexRestriction() } diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/ORIDEntityIdTest.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/ORIDEntityIdTest.kt index d3e10b405..e06c9a0d1 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/ORIDEntityIdTest.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/ORIDEntityIdTest.kt @@ -15,9 +15,9 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.record.OVertex +import com.jetbrains.youtrack.db.api.record.Vertex import jetbrains.exodus.entitystore.PersistentEntityId -import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryOrientDB +import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryYouTrackDB import jetbrains.exodus.entitystore.orientdb.testutil.createIssue import org.junit.Assert.assertEquals import org.junit.Rule @@ -27,40 +27,42 @@ import kotlin.test.assertFailsWith class ORIDEntityIdTest { @Rule @JvmField - val orientDb = InMemoryOrientDB() + val youTrackDb = InMemoryYouTrackDB() @Test fun `require both classId and localEntityId to create an instance`() { - val oClass = orientDb.provider.withSession { oSession -> - oSession.createVertexClass("type1") + val oClass = youTrackDb.provider.withSession { oSession -> + oSession.createVertexClass("type1") } - val vertex:OVertex = orientDb.withSession { oSession -> - oSession.newVertex(oClass) + var vertex: Vertex = youTrackDb.withTxSession { oSession -> + val v = oSession.newVertex(oClass) + v.save() + v } - orientDb.withSession { + youTrackDb.withTxSession { assertFailsWith { + vertex = it.bindToSession(vertex) ORIDEntityId.fromVertex(vertex) } } - orientDb.provider.withSession { - oClass.setCustom(OVertexEntity.CLASS_ID_CUSTOM_PROPERTY_NAME, 300.toString()) + youTrackDb.provider.withSession { + oClass.setCustom(it, OVertexEntity.CLASS_ID_CUSTOM_PROPERTY_NAME, 300.toString()) } - orientDb.withSession { - + youTrackDb.withTxSession { + vertex = it.bindToSession(vertex) assertFailsWith { ORIDEntityId.fromVertex(vertex) } vertex.setProperty(OVertexEntity.LOCAL_ENTITY_ID_PROPERTY_NAME, 200L) - ORIDEntityId.fromVertex(vertex) } } @Test fun `id representation is the same as for PersistentEntityId`() { - val id = orientDb.createIssue("trista").id + val id = youTrackDb.createIssue("trista").id val legacyId = PersistentEntityId(id.typeId, id.localId) val idRepresentation = id.toString() val legacyIdRepresentation = legacyId.toString() diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OSchemaBuddyTest.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OSchemaBuddyTest.kt index 0ff142a96..17a79231c 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OSchemaBuddyTest.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OSchemaBuddyTest.kt @@ -15,10 +15,10 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.metadata.sequence.OSequence -import com.orientechnologies.orient.core.record.OVertex +import com.jetbrains.youtrack.db.internal.core.db.DatabaseSessionInternal +import com.jetbrains.youtrack.db.internal.core.metadata.sequence.DBSequence import jetbrains.exodus.entitystore.PersistentEntityId -import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryOrientDB +import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryYouTrackDB import jetbrains.exodus.entitystore.orientdb.testutil.Issues import jetbrains.exodus.entitystore.orientdb.testutil.OTestMixin import org.junit.Assert.assertEquals @@ -29,13 +29,13 @@ import kotlin.test.assertFailsWith import kotlin.test.assertNotNull import kotlin.test.assertTrue -class OSchemaBuddyTest: OTestMixin { +class OSchemaBuddyTest : OTestMixin { @Rule @JvmField - val orientDbRule = InMemoryOrientDB(initializeIssueSchema = false) + val orientDbRule = InMemoryYouTrackDB(initializeIssueSchema = false) - override val orientDb = orientDbRule + override val youTrackDb = orientDbRule @Test fun `if autoInitialize is false, explicit initialization is required`() { @@ -45,7 +45,7 @@ class OSchemaBuddyTest: OTestMixin { val issueId = withStoreTx { tx -> tx.createIssue("trista").id } - val buddy = OSchemaBuddyImpl(orientDb.provider, autoInitialize = false) + val buddy = OSchemaBuddyImpl(youTrackDb.provider, autoInitialize = false) val totallyExistingEntityId = PersistentEntityId(issueId.typeId, issueId.localId) withSession { @@ -63,7 +63,7 @@ class OSchemaBuddyTest: OTestMixin { @Test fun `requireTypeExists() fails if the class is absent`() { - val buddy = OSchemaBuddyImpl(orientDb.provider) + val buddy = OSchemaBuddyImpl(youTrackDb.provider) val className = "trista" withSession { session -> assertNull(session.getClass(className)) @@ -79,7 +79,7 @@ class OSchemaBuddyTest: OTestMixin { val issueId = withStoreTx { tx -> tx.createIssue("trista").id } - val buddy = OSchemaBuddyImpl(orientDb.provider, autoInitialize = true) + val buddy = OSchemaBuddyImpl(youTrackDb.provider, autoInitialize = true) val notExistingEntityId = PersistentEntityId(300, 301) val partiallyExistingEntityId1 = PersistentEntityId(issueId.typeId, 301) @@ -99,26 +99,34 @@ class OSchemaBuddyTest: OTestMixin { @Test fun `sequence does not roll back already generated values if the transaction is rolled back`() { withSession { session -> - val params = OSequence.CreateParams() + val params = DBSequence.CreateParams() params.start = 0 - session.metadata.sequenceLibrary.createSequence("seq", OSequence.SEQUENCE_TYPE.ORDERED, params) + (session as DatabaseSessionInternal).metadata.sequenceLibrary.createSequence( + "seq", + DBSequence.SEQUENCE_TYPE.ORDERED, + params + ) } - orientDb.withTxSession { session -> - val res = session.metadata.sequenceLibrary.getSequence("seq").next() + youTrackDb.withTxSession { session -> + val res = + (session as DatabaseSessionInternal).metadata.sequenceLibrary.getSequence("seq") + .next() assertEquals(1, res) session.rollback() } - orientDb.withTxSession { session -> - val res = session.metadata.sequenceLibrary.getSequence("seq").next() + youTrackDb.withTxSession { session -> + val res = + (session as DatabaseSessionInternal).metadata.sequenceLibrary.getSequence("seq") + .next() assertEquals(2, res) } } @Test fun `can create an edge class in a transaction`() { - val buddy = OSchemaBuddyImpl(orientDb.provider) + val buddy = OSchemaBuddyImpl(youTrackDb.provider) val edgeClassName = OVertexEntity.edgeClassName("trista") // the edge class is not there @@ -131,7 +139,7 @@ class OSchemaBuddyTest: OTestMixin { val issId = withSession { session -> session.begin() val iss = session.newVertex("issue") - iss.save() + iss.save() val edgeClass = buddy.getOrCreateEdgeClass(session, "trista", "issue", "issue") assertNotNull(edgeClass) @@ -149,12 +157,12 @@ class OSchemaBuddyTest: OTestMixin { @Test fun `require both classId and localEntityId to create an instance`() { - val oClass = orientDb.provider.withSession { oSession -> + val oClass = youTrackDb.provider.withSession { oSession -> oSession.createVertexClassWithClassId("type1") } val typeID = oClass.requireClassId() - orientDb.provider.withSession { oSession -> - assertEquals("type1", orientDb.schemaBuddy.getType(oSession, typeID)) + youTrackDb.provider.withSession { oSession -> + assertEquals("type1", youTrackDb.schemaBuddy.getType(oSession, typeID)) } } diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OSequenceImplTest.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OSequenceImplTest.kt index cb75a2761..abd76a029 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OSequenceImplTest.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OSequenceImplTest.kt @@ -15,10 +15,11 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.exception.ORecordNotFoundException -import com.orientechnologies.orient.core.metadata.sequence.OSequence +import com.jetbrains.youtrack.db.api.exception.RecordNotFoundException +import com.jetbrains.youtrack.db.internal.core.db.DatabaseSessionInternal +import com.jetbrains.youtrack.db.internal.core.metadata.sequence.DBSequence import jetbrains.exodus.entitystore.Sequence -import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryOrientDB +import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryYouTrackDB import jetbrains.exodus.entitystore.orientdb.testutil.OTestMixin import org.junit.Assert import org.junit.Rule @@ -28,18 +29,18 @@ import kotlin.test.assertFailsWith class OSequenceImplTest : OTestMixin { @Rule @JvmField - val orientDbRule = InMemoryOrientDB() + val orientDbRule = InMemoryYouTrackDB() - override val orientDb = orientDbRule + override val youTrackDb = orientDbRule @Test fun `if session has no active transaction, create the sequence on the current session`() { - orientDb.store.executeInTransaction { tx -> + youTrackDb.store.executeInTransaction { tx -> val seq = tx.getSequence("s1", 300L) Assert.assertEquals(300, seq.get()) Assert.assertEquals(301, seq.increment()) } - orientDb.store.executeInTransaction { tx -> + youTrackDb.store.executeInTransaction { tx -> val seq = tx.getSequence("s1") Assert.assertEquals(301, seq.get()) } @@ -47,14 +48,14 @@ class OSequenceImplTest : OTestMixin { @Test fun `sequence may be created in one session and used in another`() { - val seq: Sequence = orientDb.store.computeInTransaction { tx -> + val seq: Sequence = youTrackDb.store.computeInTransaction { tx -> tx.getSequence("s1", 300) } - orientDb.store.executeInTransaction { + youTrackDb.store.executeInTransaction { Assert.assertEquals(300, seq.get()) Assert.assertEquals(301, seq.increment()) } - orientDb.store.executeInTransaction { + youTrackDb.store.executeInTransaction { Assert.assertEquals(301, seq.get()) Assert.assertEquals(302, seq.increment()) } @@ -62,13 +63,13 @@ class OSequenceImplTest : OTestMixin { @Test fun `sequence_set() resets the current value`() { - val seq = orientDb.store.computeInTransaction { tx -> + val seq = youTrackDb.store.computeInTransaction { tx -> val seq = tx.getSequence("s1", 300) Assert.assertEquals(300, seq.get()) Assert.assertEquals(301, seq.increment()) seq } - orientDb.store.executeInTransaction { + youTrackDb.store.executeInTransaction { seq.set(200) Assert.assertEquals(200, seq.get()) Assert.assertEquals(201, seq.increment()) @@ -77,12 +78,12 @@ class OSequenceImplTest : OTestMixin { @Test fun `if you create a sequence in a transaction, nothing works`() { - orientDb.withTxSession { session -> - val seq = session.metadata.sequenceLibrary.createSequence( + youTrackDb.withTxSession { session -> + val seq = (session as DatabaseSessionInternal).metadata.sequenceLibrary.createSequence( "s1", - OSequence.SEQUENCE_TYPE.ORDERED, OSequence.CreateParams().setStart(300).setIncrement(1) + DBSequence.SEQUENCE_TYPE.ORDERED, DBSequence.CreateParams().setStart(300).setIncrement(1) ) - assertFailsWith { seq.current() } + assertFailsWith { seq.current() } } } } \ No newline at end of file diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OStoreTransactionLifecycleTest.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OStoreTransactionLifecycleTest.kt index 9ae7dd0b3..d94717830 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OStoreTransactionLifecycleTest.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OStoreTransactionLifecycleTest.kt @@ -15,12 +15,11 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.orient.core.metadata.schema.OClass -import com.orientechnologies.orient.core.metadata.schema.OType -import com.orientechnologies.orient.core.record.OVertex -import com.orientechnologies.orient.core.storage.ORecordDuplicatedException +import com.jetbrains.youtrack.db.api.exception.RecordDuplicatedException +import com.jetbrains.youtrack.db.api.schema.PropertyType +import com.jetbrains.youtrack.db.api.schema.SchemaClass import jetbrains.exodus.entitystore.StoreTransaction -import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryOrientDB +import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryYouTrackDB import jetbrains.exodus.entitystore.orientdb.testutil.OTestMixin import junit.framework.TestCase.assertFalse import junit.framework.TestCase.assertTrue @@ -34,9 +33,9 @@ class OStoreTransactionLifecycleTest : OTestMixin { @Rule @JvmField - val orientDbRule = InMemoryOrientDB(true) + val orientDbRule = InMemoryYouTrackDB(true) - override val orientDb = orientDbRule + override val youTrackDb = orientDbRule private val allTxActions: Map = mapOf( "commit" to { tx -> tx.commit() }, @@ -59,12 +58,16 @@ class OStoreTransactionLifecycleTest : OTestMixin { fun `commit() and abort() finish transaction`() { commitAbort.forEach { (terminalTxActionName, terminalTxAction) -> allTxActions.forEach { (txActionName, txAction) -> - val tx = beginTransaction() - assertFalse(tx.isFinished) + val tx = beginTransaction() + assertFalse(tx.isFinished) - terminalTxAction(tx) - assertTrue(tx.isFinished) - assertFailsWith("$txActionName after $terminalTxActionName must lead to an IllegalStateException") { txAction(tx) } + terminalTxAction(tx) + assertTrue(tx.isFinished) + assertFailsWith("$txActionName after $terminalTxActionName must lead to an IllegalStateException") { + txAction( + tx + ) + } } } } @@ -77,7 +80,10 @@ class OStoreTransactionLifecycleTest : OTestMixin { assertFalse(tx.isFinished) notTerminalTxAction(tx) - assertFalse("$notTerminalTxActionName finished the transaction but it should not have", tx.isFinished) + assertFalse( + "$notTerminalTxActionName finished the transaction but it should not have", + tx.isFinished + ) txAction(tx) if (!tx.isFinished) { @@ -89,32 +95,36 @@ class OStoreTransactionLifecycleTest : OTestMixin { @Test fun `commit() and flush() finish the transaction if error happens`() { - val session = orientDb.openSession() + val session = youTrackDb.openSession() val oClass = session.getOrCreateVertexClass("trista") - oClass.createProperty("name", OType.STRING) - oClass.createIndex("idx_name", OClass.INDEX_TYPE.UNIQUE, "name") + oClass.createProperty(session, "name", PropertyType.STRING) + oClass.createIndex(session, "idx_name", SchemaClass.INDEX_TYPE.UNIQUE, "name") session.close() + allTxActions.forEach { (actionName, txAction) -> val tx = beginTransaction() - val trista1 = session.newVertex("trista") + + val trista1 = tx.session.newVertex("trista") trista1.setProperty("name", "dvesti") - trista1.save() - val trista2 = session.newVertex("trista") + trista1.save() + val trista2 = tx.session.newVertex("trista") trista2.setProperty("name", "dvesti") - trista2.save() + trista2.save() when (actionName) { "commit", - "flush" -> assertFailsWith { txAction(tx) } + "flush" -> assertFailsWith { txAction(tx) } "revert" -> { txAction(tx) tx.abort() } + "abort" -> txAction(tx) } } + } } \ No newline at end of file diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OStoreTransactionTest.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OStoreTransactionTest.kt index 3ab0e77b2..05b5afa64 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OStoreTransactionTest.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OStoreTransactionTest.kt @@ -16,12 +16,11 @@ package jetbrains.exodus.entitystore.orientdb import com.google.common.truth.Truth.assertThat -import com.orientechnologies.orient.core.db.ODatabaseSession -import com.orientechnologies.orient.core.db.ODatabaseSessionInternal -import com.orientechnologies.orient.core.exception.ODatabaseException -import com.orientechnologies.orient.core.record.ODirection -import com.orientechnologies.orient.core.record.ORecord -import com.orientechnologies.orient.core.record.OVertex +import com.jetbrains.youtrack.db.api.exception.DatabaseException +import com.jetbrains.youtrack.db.api.record.DBRecord +import com.jetbrains.youtrack.db.api.record.Direction +import com.jetbrains.youtrack.db.api.record.Vertex +import com.jetbrains.youtrack.db.internal.core.db.DatabaseRecordThreadLocal import jetbrains.exodus.entitystore.EntityRemovedInDatabaseException import jetbrains.exodus.entitystore.PersistentEntityId import jetbrains.exodus.entitystore.orientdb.iterate.OEntityIterableBase @@ -41,9 +40,9 @@ class OStoreTransactionTest : OTestMixin { @Rule @JvmField - val orientDbRule = InMemoryOrientDB(true) + val orientDbRule = InMemoryYouTrackDB(true) - override val orientDb = orientDbRule + override val youTrackDb = orientDbRule @Test fun `should find all`() { @@ -94,7 +93,7 @@ class OStoreTransactionTest : OTestMixin { Assert.assertEquals( 2, test.issue1.vertex.getEdges( - ODirection.IN, + Direction.IN, OVertexEntity.edgeClassName(Boards.Links.HAS_ISSUE) ) .toList().size @@ -184,7 +183,7 @@ class OStoreTransactionTest : OTestMixin { // Given val test = givenTestCase() - orientDb.withStoreTx { + youTrackDb.withStoreTx { //correct blob (can be found) test.issue1.setBlob("myBlob", "Hello".toByteArray().inputStream()) @@ -196,7 +195,7 @@ class OStoreTransactionTest : OTestMixin { } // When - orientDb.withStoreTx { tx -> + youTrackDb.withStoreTx { tx -> val issues = tx.findWithBlob(Issues.CLASS, "myBlob") // Then @@ -229,7 +228,7 @@ class OStoreTransactionTest : OTestMixin { @Test fun `single entity iterable test`() { val test = givenTestCase() - orientDb.store.executeInTransaction { + youTrackDb.store.executeInTransaction { val issue3 = it.getSingletonIterable(test.issue3).iterator().next() Assert.assertEquals(test.issue3, issue3) } @@ -571,11 +570,11 @@ class OStoreTransactionTest : OTestMixin { @Test fun `tx lets search for an entity using PersistentEntityId`() { - val aId = orientDb.createIssue("A").id - val bId = orientDb.createIssue("B").id + val aId = youTrackDb.createIssue("A").id + val bId = youTrackDb.createIssue("B").id // use default ids - orientDb.store.executeInTransaction { tx -> + youTrackDb.store.executeInTransaction { tx -> val a = tx.getEntity(aId) val b = tx.getEntity(bId) @@ -586,7 +585,7 @@ class OStoreTransactionTest : OTestMixin { // use legacy ids val legacyIdA = PersistentEntityId(aId.typeId, aId.localId) val legacyIdB = PersistentEntityId(bId.typeId, bId.localId) - orientDb.store.executeInTransaction { tx -> + youTrackDb.store.executeInTransaction { tx -> val a = tx.getEntity(legacyIdA) val b = tx.getEntity(legacyIdB) @@ -597,15 +596,15 @@ class OStoreTransactionTest : OTestMixin { @Test fun `getEntity() throws an exception if the entity not found`() { - val aId = orientDb.createIssue("A").id + val aId = youTrackDb.createIssue("A").id // delete the issue - orientDb.withTxSession { oSession -> + youTrackDb.withTxSession { oSession -> oSession.delete(aId.asOId()) } // entity not found - orientDb.store.executeInTransaction { tx -> + youTrackDb.store.executeInTransaction { tx -> assertFailsWith { tx.getEntity(aId) } @@ -623,8 +622,8 @@ class OStoreTransactionTest : OTestMixin { @Test fun `tx works with both ORIDEntityId and PersistentEntityId representations`() { - val aId = orientDb.createIssue("A").id - val bId = orientDb.createIssue("B").id + val aId = youTrackDb.createIssue("A").id + val bId = youTrackDb.createIssue("B").id val aIdRepresentation = aId.toString() val bIdRepresentation = bId.toString() val aLegacyId = PersistentEntityId(aId.typeId, aId.localId) @@ -632,7 +631,7 @@ class OStoreTransactionTest : OTestMixin { val aLegacyIdRepresentation = aLegacyId.toString() val bLegacyIdRepresentation = bLegacyId.toString() - orientDb.store.executeInTransaction { tx -> + youTrackDb.store.executeInTransaction { tx -> assertEquals(aId, tx.toEntityId(aIdRepresentation)) assertEquals(bId, tx.toEntityId(bIdRepresentation)) @@ -644,7 +643,7 @@ class OStoreTransactionTest : OTestMixin { @Test fun `entity id should be valid and accessible just after creation`() { - orientDb.store.executeInTransaction { tx -> + youTrackDb.store.executeInTransaction { tx -> val entity = tx.newEntity(Issues.CLASS) val orid = (entity.id as OEntityId).asOId() Assert.assertTrue(orid.clusterId > 0) @@ -653,7 +652,7 @@ class OStoreTransactionTest : OTestMixin { @Test fun `newEntity sets localEntityId`() { - orientDb.store.executeInTransaction { tx -> + youTrackDb.store.executeInTransaction { tx -> val issue = tx.newEntity(Issues.CLASS) assertEquals(issue.id.localId, 0) } @@ -673,8 +672,8 @@ class OStoreTransactionTest : OTestMixin { @Test fun `read-only transaction forbids changing data in it`() { - val issue = orientDb.createIssue("trista") - val tx = orientDb.store.beginReadonlyTransaction() + val issue = youTrackDb.createIssue("trista") + val tx = youTrackDb.store.beginReadonlyTransaction() assertFailsWith { tx.newEntity(Issues.CLASS) } assertFailsWith { tx.saveEntity(issue) } } @@ -686,7 +685,7 @@ class OStoreTransactionTest : OTestMixin { test.createManyIssues(1000) // When - orientDb.store.executeInTransaction { transaction -> + youTrackDb.store.executeInTransaction { transaction -> transaction.queryCancellingPolicy = OQueryCancellingPolicy.timeout(0) val exception = Assert.assertThrows(OQueryTimeoutException::class.java) { @@ -740,11 +739,11 @@ class OStoreTransactionTest : OTestMixin { @Test fun `active session still has an active transaction after flush`() { - assertFailsWith { ODatabaseSession.getActiveSession() } + assertFailsWith { DatabaseRecordThreadLocal.instance().get() } withStoreTx { tx -> - (ODatabaseSession.getActiveSession() as ODatabaseSession).requireActiveTransaction() + DatabaseRecordThreadLocal.instance().get().requireActiveTransaction() tx.flush() - (ODatabaseSession.getActiveSession() as ODatabaseSession).requireActiveTransaction() + DatabaseRecordThreadLocal.instance().get().requireActiveTransaction() } } @@ -752,7 +751,7 @@ class OStoreTransactionTest : OTestMixin { fun `transactionId does not get changed on flush()`() { withStoreTx { tx -> val oTransactionId = - (ODatabaseSession.getActiveSession() as ODatabaseSessionInternal).transaction.id.toLong() + DatabaseRecordThreadLocal.instance().get().transaction.id assertEquals(oTransactionId, tx.getTransactionId()) tx.flush() assertEquals(oTransactionId, tx.getTransactionId()) @@ -830,7 +829,7 @@ class OStoreTransactionTest : OTestMixin { } withStoreTx { tx -> - val vertex: OVertex = tx.getRecord(id)!! + val vertex: Vertex = tx.getRecord(id) assertEquals("caramba", vertex.getProperty("mamba")) val e1 = tx.getEntity(id) e1.delete() @@ -838,7 +837,7 @@ class OStoreTransactionTest : OTestMixin { withStoreTx { tx -> try { - tx.getRecord(id) + tx.getRecord(id) Assert.fail() } catch (e: EntityRemovedInDatabaseException) { // expected diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OTransactionLifecycleTest.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OTransactionLifecycleTest.kt index d8042a0b4..a28f48bf1 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OTransactionLifecycleTest.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/OTransactionLifecycleTest.kt @@ -15,19 +15,19 @@ */ package jetbrains.exodus.entitystore.orientdb -import com.orientechnologies.common.concur.lock.OModificationOperationProhibitedException -import com.orientechnologies.orient.core.db.ODatabaseSession.STATUS -import com.orientechnologies.orient.core.db.ODatabaseSessionInternal -import com.orientechnologies.orient.core.exception.ODatabaseException -import com.orientechnologies.orient.core.exception.ORecordNotFoundException -import com.orientechnologies.orient.core.metadata.schema.OClass -import com.orientechnologies.orient.core.metadata.schema.OType -import com.orientechnologies.orient.core.record.ORecord -import com.orientechnologies.orient.core.record.OVertex -import com.orientechnologies.orient.core.storage.ORecordDuplicatedException -import com.orientechnologies.orient.core.tx.OTransaction.TXSTATUS -import com.orientechnologies.orient.core.tx.OTransactionNoTx -import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryOrientDB +import com.jetbrains.youtrack.db.api.DatabaseSession +import com.jetbrains.youtrack.db.api.exception.DatabaseException +import com.jetbrains.youtrack.db.api.exception.ModificationOperationProhibitedException +import com.jetbrains.youtrack.db.api.exception.RecordDuplicatedException +import com.jetbrains.youtrack.db.api.exception.RecordNotFoundException +import com.jetbrains.youtrack.db.api.record.DBRecord +import com.jetbrains.youtrack.db.api.record.Vertex +import com.jetbrains.youtrack.db.api.schema.PropertyType +import com.jetbrains.youtrack.db.api.schema.SchemaClass +import com.jetbrains.youtrack.db.internal.core.db.DatabaseSessionInternal +import com.jetbrains.youtrack.db.internal.core.tx.FrontendTransaction.TXSTATUS +import com.jetbrains.youtrack.db.internal.core.tx.FrontendTransactionNoTx +import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryYouTrackDB import jetbrains.exodus.entitystore.orientdb.testutil.OTestMixin import junit.framework.TestCase.assertFalse import org.junit.Assert @@ -39,26 +39,26 @@ class OTransactionLifecycleTest : OTestMixin { @Rule @JvmField - val orientDbRule = InMemoryOrientDB(true) + val orientDbRule = InMemoryYouTrackDB(true) - override val orientDb = orientDbRule + override val youTrackDb = orientDbRule @Test fun `session-freeze(true) makes the session read-only`() { - val session = orientDb.openSession() + val session = youTrackDb.openSession() session.freeze(true) session.begin() val v = session.newVertex() - v.save() - assertFailsWith { session.commit() } + v.save() + assertFailsWith { session.commit() } // after release() we can write again session.release() session.begin() val v2 = session.newVertex() - v2.save() + v2.save() session.commit() session.close() @@ -66,10 +66,10 @@ class OTransactionLifecycleTest : OTestMixin { @Test fun `open, begin, commit, close - no changes`() { - val session = orientDb.openSession() as ODatabaseSessionInternal + val session = youTrackDb.openSession() as DatabaseSessionInternal assertFalse(session.transaction == null) - assertEquals(STATUS.OPEN, session.status) + assertEquals(DatabaseSession.STATUS.OPEN, session.status) session.begin() assertEquals(session.transaction.status, TXSTATUS.BEGUN) @@ -77,19 +77,19 @@ class OTransactionLifecycleTest : OTestMixin { session.commit() assertEquals(session.transaction.status, TXSTATUS.INVALID) - assertEquals(STATUS.OPEN, session.status) + assertEquals(DatabaseSession.STATUS.OPEN, session.status) assertFalse(session.hasActiveTransaction()) assertTrue(session.isActiveOnCurrentThread) session.close() - assertEquals(STATUS.CLOSED, session.status) + assertEquals(DatabaseSession.STATUS.CLOSED, session.status) assertFalse(session.isActiveOnCurrentThread) assertTrue(session.isClosed) } @Test fun `open, begin, commit, close - changes`() { - val session = orientDb.openSession() as ODatabaseSessionInternal + val session = youTrackDb.openSession() as DatabaseSessionInternal val oClass = session.getOrCreateVertexClass("trista") assertFalse(session.transaction == null) @@ -100,7 +100,7 @@ class OTransactionLifecycleTest : OTestMixin { val trista = session.newVertex(oClass) trista.setProperty("name", "opca trista") - trista.save() + trista.save() session.commit() assertTrue(session.transaction.status == TXSTATUS.INVALID) @@ -108,7 +108,7 @@ class OTransactionLifecycleTest : OTestMixin { assertTrue(session.isActiveOnCurrentThread) session.begin() - session.load(trista.identity) + session.load(trista.identity) session.commit() session.close() @@ -118,7 +118,7 @@ class OTransactionLifecycleTest : OTestMixin { @Test fun `open, begin, rollback, close - no changes`() { - val session = orientDb.openSession() as ODatabaseSessionInternal + val session = youTrackDb.openSession() as DatabaseSessionInternal assertFalse(session.transaction == null) @@ -139,7 +139,7 @@ class OTransactionLifecycleTest : OTestMixin { @Test fun `open, begin, rollback, close - changes`() { - val session = orientDb.openSession() as ODatabaseSessionInternal + val session = youTrackDb.openSession() as DatabaseSessionInternal val oClass = session.getOrCreateVertexClass("trista") assertFalse(session.transaction == null) @@ -150,7 +150,7 @@ class OTransactionLifecycleTest : OTestMixin { val trista = session.newVertex(oClass) trista.setProperty("name", "opca trista") - trista.save() + trista.save() session.rollback() assertEquals(TXSTATUS.INVALID, session.transaction.status) @@ -159,9 +159,9 @@ class OTransactionLifecycleTest : OTestMixin { session.begin() try { - session.load(trista.identity) + session.load(trista.identity) Assert.fail() - } catch (e: ORecordNotFoundException) { + } catch (e: RecordNotFoundException) { // expected } @@ -174,10 +174,10 @@ class OTransactionLifecycleTest : OTestMixin { @Test fun `commit() throws an exception if there is no active transaction`() { - val session = orientDb.openSession() + val session = youTrackDb.openSession() assertFalse(session.hasActiveTransaction()) - assertFailsWith { session.commit() } + assertFailsWith { session.commit() } session.begin() @@ -188,10 +188,10 @@ class OTransactionLifecycleTest : OTestMixin { assertFalse(session.hasActiveTransaction()) assertTrue(session.isActiveOnCurrentThread) - assertFailsWith { session.commit() } + assertFailsWith { session.commit() } assertTrue(session.isActiveOnCurrentThread) - assertEquals(STATUS.OPEN, session.status) + assertEquals(DatabaseSession.STATUS.OPEN, session.status) // the session is still usable session.begin() @@ -204,7 +204,7 @@ class OTransactionLifecycleTest : OTestMixin { @Test fun `rollback() does NOT throw an exception if there is no active transaction`() { - val session = orientDb.openSession() as ODatabaseSessionInternal + val session = youTrackDb.openSession() as DatabaseSessionInternal assertFalse(session.hasActiveTransaction()) @@ -229,23 +229,23 @@ class OTransactionLifecycleTest : OTestMixin { @Test fun `if commit() fails, changes get rolled back`() { - val session = orientDb.openSession() as ODatabaseSessionInternal + val session = youTrackDb.openSession() as DatabaseSessionInternal val oClass = session.getOrCreateVertexClass("trista") - oClass.createProperty("name", OType.STRING) - oClass.createIndex("idx_name", OClass.INDEX_TYPE.UNIQUE, "name") + oClass.createProperty(session, "name", PropertyType.STRING) + oClass.createIndex(session, "idx_name", SchemaClass.INDEX_TYPE.UNIQUE, "name") session.begin() // tx1 assertTrue(session.hasActiveTransaction()) val trista1 = session.newVertex("trista") trista1.setProperty("name", "dvesti") - trista1.save() + trista1.save() val trista2 = session.newVertex("trista") trista2.setProperty("name", "dvesti") - trista2.save() + trista2.save() // if commit - assertFailsWith { session.commit() } + assertFailsWith { session.commit() } assertEquals(TXSTATUS.INVALID, session.transaction.status) assertFalse(session.hasActiveTransaction()) @@ -253,17 +253,16 @@ class OTransactionLifecycleTest : OTestMixin { session.begin() try { - session.load(trista1.identity) + session.load(trista1.identity) Assert.fail() - } catch (_: ORecordNotFoundException) { + } catch (_: RecordNotFoundException) { } try { - session.load(trista2.identity) + session.load(trista2.identity) Assert.fail() - } catch (_: ORecordNotFoundException) { - + } catch (_: RecordNotFoundException) { } session.commit() session.close() @@ -271,7 +270,7 @@ class OTransactionLifecycleTest : OTestMixin { @Test fun `embedded transactions successful case`() { - val session = orientDb.openSession() as ODatabaseSessionInternal + val session = youTrackDb.openSession() as DatabaseSessionInternal session.getOrCreateVertexClass("trista") assertEquals(TXSTATUS.INVALID, session.transaction.status) assertEquals(0, session.transaction.amountOfNestedTxs()) @@ -284,7 +283,7 @@ class OTransactionLifecycleTest : OTestMixin { val trista1 = session.newVertex("trista") trista1.setProperty("name", "dvesti") - trista1.save() + trista1.save() session.begin() // tx2 val tx2 = session.transaction @@ -296,7 +295,7 @@ class OTransactionLifecycleTest : OTestMixin { val trista2 = session.newVertex("trista") trista2.setProperty("name", "sth") - trista2.save() + trista2.save() session.commit() // tx2 @@ -308,7 +307,7 @@ class OTransactionLifecycleTest : OTestMixin { session.commit() // tx1 assertNotEquals(tx1, session.transaction) - assertIs(session.transaction) + assertIs(session.transaction) assertEquals(TXSTATUS.COMPLETED, tx1.status) assertEquals(TXSTATUS.INVALID, session.transaction.status) assertEquals(0, session.transaction.amountOfNestedTxs()) @@ -316,15 +315,15 @@ class OTransactionLifecycleTest : OTestMixin { session.begin() assertNotEquals(tx1, session.transaction) - session.load(trista1.identity) - session.load(trista2.identity) + session.load(trista1.identity) + session.load(trista2.identity) session.commit() session.close() } @Test fun `rollback(force = true) on an embedded transaction rolls back all the transactions`() { - val session = orientDb.openSession() as ODatabaseSessionInternal + val session = youTrackDb.openSession() as DatabaseSessionInternal session.getOrCreateVertexClass("trista") session.begin() // tx1 @@ -334,7 +333,7 @@ class OTransactionLifecycleTest : OTestMixin { val trista1 = session.newVertex("trista") trista1.setProperty("name", "dvesti") - trista1.save() + trista1.save() session.begin() // tx2 assertEquals(TXSTATUS.BEGUN, session.transaction.status) @@ -343,7 +342,7 @@ class OTransactionLifecycleTest : OTestMixin { val trista2 = session.newVertex("trista") trista2.setProperty("name", "sth") - trista2.save() + trista2.save() session.rollback(true) @@ -354,13 +353,13 @@ class OTransactionLifecycleTest : OTestMixin { try { session.loadVertex(trista1.identity) Assert.fail() - } catch (e: ORecordNotFoundException) { + } catch (e: RecordNotFoundException) { // expected } try { session.loadVertex(trista2.identity) Assert.fail() - } catch (e: ORecordNotFoundException) { + } catch (e: RecordNotFoundException) { // expected } session.commit() @@ -369,7 +368,7 @@ class OTransactionLifecycleTest : OTestMixin { @Test fun `rollback() on an embedded transaction decreases amountOfNestedTxs by 1`() { - val session = orientDb.openSession() as ODatabaseSessionInternal + val session = youTrackDb.openSession() as DatabaseSessionInternal session.getOrCreateVertexClass("trista") session.begin() // tx1 @@ -379,7 +378,7 @@ class OTransactionLifecycleTest : OTestMixin { val trista1 = session.newVertex("trista") trista1.setProperty("name", "dvesti") - trista1.save() + trista1.save() session.begin() // tx2 assertEquals(TXSTATUS.BEGUN, session.transaction.status) @@ -388,7 +387,7 @@ class OTransactionLifecycleTest : OTestMixin { val trista2 = session.newVertex("trista") trista2.setProperty("name", "sth") - trista2.save() + trista2.save() session.rollback() @@ -404,13 +403,13 @@ class OTransactionLifecycleTest : OTestMixin { try { session.loadVertex(trista1.identity) Assert.fail() - } catch (e: ORecordNotFoundException) { + } catch (e: RecordNotFoundException) { // expected } try { session.loadVertex(trista2.identity) Assert.fail() - } catch (e: ORecordNotFoundException) { + } catch (e: RecordNotFoundException) { // expected } session.commit() @@ -419,10 +418,10 @@ class OTransactionLifecycleTest : OTestMixin { @Test fun `commit() on an embedded transaction does not validate constraints, only top level transaction validates`() { - val session = orientDb.openSession() + val session = youTrackDb.openSession() val oClass = session.getOrCreateVertexClass("trista") - oClass.createProperty("name", OType.STRING) - oClass.createIndex("idx_name", OClass.INDEX_TYPE.UNIQUE, "name") + oClass.createProperty(session, "name", PropertyType.STRING) + oClass.createIndex(session, "idx_name", SchemaClass.INDEX_TYPE.UNIQUE, "name") session.begin() // tx1 assertTrue(session.hasActiveTransaction()) @@ -432,28 +431,28 @@ class OTransactionLifecycleTest : OTestMixin { val trista1 = session.newVertex("trista") trista1.setProperty("name", "dvesti") - trista1.save() + trista1.save() val trista2 = session.newVertex("trista") trista2.setProperty("name", "dvesti") - trista2.save() + trista2.save() session.commit() // tx2 - assertFailsWith { session.commit() } // tx1 + assertFailsWith { session.commit() } // tx1 assertFalse(session.hasActiveTransaction()) session.begin() try { - session.load(trista1.identity) + session.load(trista1.identity) Assert.fail() - } catch (e: ORecordNotFoundException) { + } catch (e: RecordNotFoundException) { // expected } try { - session.load(trista2.identity) + session.load(trista2.identity) Assert.fail() - } catch (e: ORecordNotFoundException) { + } catch (e: RecordNotFoundException) { // expected } session.commit() diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/iterate/OEntityIterableTest.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/iterate/OEntityIterableTest.kt index 795f3defe..2054d122c 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/iterate/OEntityIterableTest.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/iterate/OEntityIterableTest.kt @@ -40,9 +40,9 @@ class OEntityIterableTest : OTestMixin { @Rule @JvmField - val orientDbRule = InMemoryOrientDB() + val orientDbRule = InMemoryYouTrackDB() - override val orientDb = orientDbRule + override val youTrackDb = orientDbRule @Test fun `property is null`() { @@ -150,7 +150,7 @@ class OEntityIterableTest : OTestMixin { tx.checkSql( issues, expectedSql = "SELECT FROM Issue WHERE (name = :name0 OR name = :name1)", - expectedParams = mapOf("name0" to "issue1", "name1" to "issue1" ) + expectedParams = mapOf("name0" to "issue1", "name1" to "issue1") ) // Union operation can distinct result set if query is optimized to OR conditions assertNamesExactlyInOrder(issues, "issue1") @@ -224,7 +224,11 @@ class OEntityIterableTest : OTestMixin { // When withStoreTx { tx -> - val issues = tx.findLinks(Issues.CLASS, test.board1, Issues.Links.ON_BOARD) as OLinkOfTypeToEntityIterable + val issues = tx.findLinks( + Issues.CLASS, + test.board1, + Issues.Links.ON_BOARD + ) as OLinkOfTypeToEntityIterable println(test.board1.id.asOId()) // Then @@ -438,7 +442,8 @@ class OEntityIterableTest : OTestMixin { // When withStoreTx { tx -> - val reversedByName = tx.sort(Issues.CLASS, "name", true).reverse() as OReversedEntityIterable + val reversedByName = + tx.sort(Issues.CLASS, "name", true).reverse() as OReversedEntityIterable // Then tx.checkSql( @@ -467,7 +472,8 @@ class OEntityIterableTest : OTestMixin { val boards = tx.find(Boards.CLASS, "name", test.board1.name()) .union(tx.find(Boards.CLASS, "name", test.board2.name())) val allIssues = tx.getAll(Issues.CLASS) as OEntityIterableBase - val issuesOnBoards = allIssues.findLinks(boards, Issues.Links.ON_BOARD) as OEntityIterable + val issuesOnBoards = + allIssues.findLinks(boards, Issues.Links.ON_BOARD) as OEntityIterable // Then tx.checkSql( @@ -583,13 +589,13 @@ class OEntityIterableTest : OTestMixin { @Test fun `instance of should work`() { // Create 10 Issue and 1 SubIssue and their classes - orientDb.provider.acquireSession().use { session -> + youTrackDb.provider.acquireSession().use { session -> val subIssue = session.getOrCreateVertexClass("ChildIssue") val issueClass = session.getOrCreateVertexClass(Issues.CLASS) - subIssue.setSuperClasses(listOf(issueClass)) + subIssue.setSuperClasses(session, listOf(issueClass)) } (1..10).forEach { - orientDb.createIssue("issue$it") + youTrackDb.createIssue("issue$it") } withStoreTx { tx -> tx.newEntity("ChildIssue") diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/InMemoryOrientDB.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/InMemoryYouTrackDB.kt similarity index 81% rename from entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/InMemoryOrientDB.kt rename to entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/InMemoryYouTrackDB.kt index 0ca09c5b7..1dd778ed6 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/InMemoryOrientDB.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/InMemoryYouTrackDB.kt @@ -15,11 +15,11 @@ */ package jetbrains.exodus.entitystore.orientdb.testutil -import com.orientechnologies.orient.core.config.OGlobalConfiguration -import com.orientechnologies.orient.core.db.ODatabaseSession -import com.orientechnologies.orient.core.db.ODatabaseType -import com.orientechnologies.orient.core.db.OrientDB -import com.orientechnologies.orient.core.db.OrientDBConfig +import com.jetbrains.youtrack.db.api.DatabaseSession +import com.jetbrains.youtrack.db.api.DatabaseType +import com.jetbrains.youtrack.db.api.YouTrackDB +import com.jetbrains.youtrack.db.api.config.GlobalConfiguration +import com.jetbrains.youtrack.db.api.config.YouTrackDBConfig import jetbrains.exodus.entitystore.orientdb.* import jetbrains.exodus.entitystore.orientdb.testutil.Issues.Links.IN_PROJECT import jetbrains.exodus.entitystore.orientdb.testutil.Issues.Links.ON_BOARD @@ -28,12 +28,12 @@ import org.junit.rules.ExternalResource import java.nio.file.Files import kotlin.io.path.absolutePathString -class InMemoryOrientDB( +class InMemoryYouTrackDB( private val initializeIssueSchema: Boolean = true, private val autoInitializeSchemaBuddy: Boolean = true ) : ExternalResource() { - private lateinit var db: OrientDB + private lateinit var db: YouTrackDB lateinit var store: OPersistentEntityStore private set @@ -46,8 +46,8 @@ class InMemoryOrientDB( override fun before() { val connConfig = ODatabaseConnectionConfig.builder() - .withDatabaseType(ODatabaseType.MEMORY) - .withDatabaseRoot(Files.createTempDirectory("oxigenDB_test").absolutePathString()) + .withDatabaseType(DatabaseType.MEMORY) + .withDatabaseRoot(Files.createTempDirectory("youTrackDB_test").absolutePathString()) .withPassword(password) .withUserName(username) .build() @@ -56,9 +56,9 @@ class InMemoryOrientDB( .withConnectionConfig(connConfig) .withDatabaseName(dbName) .build() - val builder = OrientDBConfig.builder() - builder.addConfig(OGlobalConfiguration.NON_TX_READS_WARNING_MODE, "SILENT") - db = initOrientDbServer(connConfig) + val builder = YouTrackDBConfig.builder() + builder.addGlobalConfigurationParameter(GlobalConfiguration.NON_TX_READS_WARNING_MODE, "SILENT") + db = iniYouTrackDb(connConfig) provider = ODatabaseProviderImpl(config, db) if (initializeIssueSchema) { @@ -91,7 +91,7 @@ class InMemoryOrientDB( } } - fun withTxSession(block: (ODatabaseSession) -> R): R { + fun withTxSession(block: (DatabaseSession) -> R): R { val session = provider.acquireSession() try { session.begin() @@ -110,7 +110,7 @@ class InMemoryOrientDB( } } - fun withSession(block: (ODatabaseSession) -> R): R { + fun withSession(block: (DatabaseSession) -> R): R { val session = provider.acquireSession() try { return block(session) @@ -121,7 +121,7 @@ class InMemoryOrientDB( } } - fun openSession(): ODatabaseSession { + fun openSession(): DatabaseSession { return db.cachedPool(dbName, username, password).acquire() } } diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/OTaskTrackerTestCase.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/OTaskTrackerTestCase.kt index 92c0eded7..ff449e462 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/OTaskTrackerTestCase.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/OTaskTrackerTestCase.kt @@ -18,7 +18,7 @@ package jetbrains.exodus.entitystore.orientdb.testutil import jetbrains.exodus.entitystore.orientdb.OStoreTransaction import jetbrains.exodus.entitystore.orientdb.OVertexEntity -class OTaskTrackerTestCase(val orientDB: InMemoryOrientDB) { +class OTaskTrackerTestCase(val orientDB: InMemoryYouTrackDB) { val project1: OVertexEntity val project2: OVertexEntity diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/OTestMixin.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/OTestMixin.kt index 810d4009f..cd3bd8131 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/OTestMixin.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/OTestMixin.kt @@ -17,7 +17,7 @@ package jetbrains.exodus.entitystore.orientdb.testutil import com.google.common.truth.Ordered import com.google.common.truth.Truth.assertThat -import com.orientechnologies.orient.core.db.ODatabaseSession +import com.jetbrains.youtrack.db.api.DatabaseSession import jetbrains.exodus.entitystore.Entity import jetbrains.exodus.entitystore.orientdb.OEntity import jetbrains.exodus.entitystore.orientdb.OStoreTransaction @@ -26,7 +26,7 @@ import jetbrains.exodus.entitystore.orientdb.OVertexEntity interface OTestMixin { - val orientDb: InMemoryOrientDB + val youTrackDb: InMemoryYouTrackDB fun assertNamesExactly(result: Iterable, vararg names: String): Ordered { return assertThat(result.map { it.getProperty("name") }).containsExactly(*names) @@ -37,36 +37,31 @@ interface OTestMixin { } fun beginTransaction(): OStoreTransactionImpl { - val store = orientDb.store + val store = youTrackDb.store return store.beginTransaction() as OStoreTransactionImpl } - fun beginReadonlyTransaction(): OStoreTransactionImpl { - val store = orientDb.store - return store.beginReadonlyTransaction() as OStoreTransactionImpl - } - fun withStoreTx(block: (OStoreTransaction) -> R): R { - return orientDb.withStoreTx(block) + return youTrackDb.withStoreTx(block) } - fun withSession(block: (ODatabaseSession) -> R): R { - return orientDb.withSession(block) + fun withSession(block: (DatabaseSession) -> R): R { + return youTrackDb.withSession(block) } - fun withTxSession(block: (ODatabaseSession) -> R): R { - return orientDb.withTxSession(block) + fun withTxSession(block: (DatabaseSession) -> R): R { + return youTrackDb.withTxSession(block) } - fun givenTestCase() = OTaskTrackerTestCase(orientDb) + fun givenTestCase() = OTaskTrackerTestCase(youTrackDb) fun OStoreTransaction.createIssue(name: String, priority: String? = null): OVertexEntity = createIssueImpl(name, priority) fun OStoreTransaction.createProject(name: String): OVertexEntity = createProjectImpl(name) - fun OStoreTransaction.createBoard(name: String): OVertexEntity = createBoardImpl(name) - - fun OStoreTransaction.addIssueToProject(issue: OEntity, project: OEntity) = addIssueToProjectImpl(issue, project) + fun OStoreTransaction.addIssueToProject(issue: OEntity, project: OEntity) = + addIssueToProjectImpl(issue, project) - fun OStoreTransaction.addIssueToBoard(issue: OEntity, board: OEntity) = addIssueToBoardImpl(issue, board) + fun OStoreTransaction.addIssueToBoard(issue: OEntity, board: OEntity) = + addIssueToBoardImpl(issue, board) } \ No newline at end of file diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/OUsersWithInheritanceTestCase.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/OUsersWithInheritanceTestCase.kt index 1eca82275..0623bb585 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/OUsersWithInheritanceTestCase.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/OUsersWithInheritanceTestCase.kt @@ -19,7 +19,7 @@ import jetbrains.exodus.entitystore.orientdb.OStoreTransactionImpl import jetbrains.exodus.entitystore.orientdb.OVertexEntity import jetbrains.exodus.entitystore.orientdb.getOrCreateVertexClass -class OUsersWithInheritanceTestCase(val orientDB: InMemoryOrientDB) { +class OUsersWithInheritanceTestCase(youTrackDB: InMemoryYouTrackDB) { val user1: OVertexEntity val user2: OVertexEntity @@ -36,7 +36,7 @@ class OUsersWithInheritanceTestCase(val orientDB: InMemoryOrientDB) { init { - orientDB.withSession { session -> + youTrackDB.withSession { session -> val baseClass = session.getOrCreateVertexClass(BaseUser.CLASS) val subclasses = listOf( session.getOrCreateVertexClass(Guest.CLASS), @@ -45,11 +45,11 @@ class OUsersWithInheritanceTestCase(val orientDB: InMemoryOrientDB) { session.getOrCreateVertexClass(Agent.CLASS), ) subclasses.forEach { - it.addSuperClass(baseClass) + it.addSuperClass(session, baseClass) } } - val tx = orientDB.store.beginTransaction() as OStoreTransactionImpl + val tx = youTrackDB.store.beginTransaction() as OStoreTransactionImpl user1 = tx.createUser(User.CLASS, "u1") user2 = tx.createUser(User.CLASS, "u2") diff --git a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/OrientDBIssues.kt b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/OrientDBIssues.kt index 7e76a928f..b7fcb7123 100644 --- a/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/OrientDBIssues.kt +++ b/entity-store/src/test/kotlin/jetbrains/exodus/entitystore/orientdb/testutil/OrientDBIssues.kt @@ -15,10 +15,10 @@ */ package jetbrains.exodus.entitystore.orientdb.testutil -import com.orientechnologies.orient.core.db.ODatabaseSession -import com.orientechnologies.orient.core.metadata.schema.OType -import com.orientechnologies.orient.core.record.ODirection -import com.orientechnologies.orient.core.record.OVertex +import com.jetbrains.youtrack.db.api.DatabaseSession +import com.jetbrains.youtrack.db.api.record.Direction +import com.jetbrains.youtrack.db.api.record.Vertex +import com.jetbrains.youtrack.db.api.schema.PropertyType import jetbrains.exodus.entitystore.Entity import jetbrains.exodus.entitystore.orientdb.OEntity import jetbrains.exodus.entitystore.orientdb.OStoreTransaction @@ -58,7 +58,7 @@ object Boards { } } -fun InMemoryOrientDB.createIssue(name: String, priority: String? = null): OVertexEntity { +fun InMemoryYouTrackDB.createIssue(name: String, priority: String? = null): OVertexEntity { return withStoreTx { tx -> tx.createIssueImpl(name, priority) } @@ -78,7 +78,10 @@ private fun Entity.setName(name: String) { setProperty("name", name) } -internal fun OStoreTransaction.createIssueImpl(name: String, priority: String? = null): OVertexEntity { +internal fun OStoreTransaction.createIssueImpl( + name: String, + priority: String? = null +): OVertexEntity { val issue = newEntity(CLASS) as OVertexEntity issue.setName(name) priority?.let { issue.setProperty(PRIORITY, it) } @@ -91,31 +94,32 @@ internal fun OStoreTransaction.createBoardImpl(name: String): OVertexEntity { return e } -internal fun OStoreTransaction.addIssueToProjectImpl(issue: OEntity, project: OEntity) { +internal fun addIssueToProjectImpl(issue: OEntity, project: OEntity) { issue.addLink(IN_PROJECT, project) project.addLink(HAS_ISSUE, issue) } -internal fun OStoreTransaction.addIssueToBoardImpl(issue: OEntity, board: OEntity) { +internal fun addIssueToBoardImpl(issue: OEntity, board: OEntity) { issue.addLink(ON_BOARD, board) board.addLink(Boards.Links.HAS_ISSUE, issue) } -internal fun ODatabaseSession.addAssociation( +internal fun DatabaseSession.addAssociation( fromClassName: String, toClassName: String, outPropName: String, inPropName: String ) { - val fromClass = getClass(fromClassName) ?: throw IllegalStateException("$fromClassName not found") + val fromClass = + getClass(fromClassName) ?: throw IllegalStateException("$fromClassName not found") val toClass = getClass(toClassName) ?: throw IllegalStateException("$toClassName not found") val inEdgeName = OVertexEntity.edgeClassName(inPropName) val outEdgeName = OVertexEntity.edgeClassName(outPropName) getClass(inEdgeName) ?: this.createEdgeClass(inEdgeName) getClass(outEdgeName) ?: this.createEdgeClass(outEdgeName) - val linkInPropName = OVertex.getEdgeLinkFieldName(ODirection.IN, inEdgeName) - val linkOutPropName = OVertex.getEdgeLinkFieldName(ODirection.OUT, outEdgeName) - fromClass.createProperty(linkOutPropName, OType.LINKBAG) - toClass.createProperty(linkInPropName, OType.LINKBAG) + val linkInPropName = Vertex.getEdgeLinkFieldName(Direction.IN, inEdgeName) + val linkOutPropName = Vertex.getEdgeLinkFieldName(Direction.OUT, outEdgeName) + fromClass.createProperty(this, linkOutPropName, PropertyType.LINKBAG) + toClass.createProperty(this, linkInPropName, PropertyType.LINKBAG) } \ No newline at end of file diff --git a/environment/src/main/kotlin/jetbrains/exodus/env/StuckTransactionMonitor.kt b/environment/src/main/kotlin/jetbrains/exodus/env/StuckTransactionMonitor.kt index c24fa2769..91c9b00d7 100644 --- a/environment/src/main/kotlin/jetbrains/exodus/env/StuckTransactionMonitor.kt +++ b/environment/src/main/kotlin/jetbrains/exodus/env/StuckTransactionMonitor.kt @@ -44,7 +44,7 @@ internal class StuckTransactionMonitor(env: EnvironmentImpl) : Job() { txn.trace?.let { trace -> val creatingThread = txn.creatingThread val msg = "Transaction timed out: created at ${Date(txn.startTime)}, " + - "thread = $creatingThread(${creatingThread.id})\n$trace" + "thread = $creatingThread(${creatingThread.threadId()})\n$trace" logger.info(msg) ++stuckTxnCount } diff --git a/environment/src/main/kotlin/jetbrains/exodus/gc/BackgroundCleaner.kt b/environment/src/main/kotlin/jetbrains/exodus/gc/BackgroundCleaner.kt index d5b537b5c..79f7f9b9a 100644 --- a/environment/src/main/kotlin/jetbrains/exodus/gc/BackgroundCleaner.kt +++ b/environment/src/main/kotlin/jetbrains/exodus/gc/BackgroundCleaner.kt @@ -68,7 +68,7 @@ internal class BackgroundCleaner(private val gc: GarbageCollector) { val isFinished: Boolean get() = processor.isFinished - val isCurrentThread: Boolean get() = threadId == Thread.currentThread().id + val isCurrentThread: Boolean get() = threadId == Thread.currentThread().threadId() fun finish() { (processor.currentJob as? GcJob)?.cancel() diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917707c1f8861d8cb53dd15194d4248596..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch delta 34592 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 delta 34555 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e0930..cea7a793a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a426..f3b75f3b0 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f1..9d21a2183 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/query/build.gradle.kts b/query/build.gradle.kts index 09d1eac69..a4eb1cb0f 100644 --- a/query/build.gradle.kts +++ b/query/build.gradle.kts @@ -1,7 +1,6 @@ dependencies { implementation(project(":xodus-entity-store")) implementation(project(":xodus-utils")) - implementation("com.orientechnologies:orientdb-core:4.0.0-20241126.153402-203") api(project(":xodus-openAPI")) implementation("com.github.penemue:keap:0.3.0") implementation(project(":xodus-environment")) diff --git a/query/src/main/kotlin/jetbrains/exodus/query/metadata/DataAfterMigrationChecker.kt b/query/src/main/kotlin/jetbrains/exodus/query/metadata/DataAfterMigrationChecker.kt index 3215a8c16..99070efb0 100644 --- a/query/src/main/kotlin/jetbrains/exodus/query/metadata/DataAfterMigrationChecker.kt +++ b/query/src/main/kotlin/jetbrains/exodus/query/metadata/DataAfterMigrationChecker.kt @@ -288,4 +288,5 @@ private val oEntityStoreExtraEntityTypes = setOf( "OSequence", "OSecurityPolicy", "OUser", + "O" ) 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 c91b543e1..c6afe9416 100644 --- a/query/src/main/kotlin/jetbrains/exodus/query/metadata/IndicesCreator.kt +++ b/query/src/main/kotlin/jetbrains/exodus/query/metadata/IndicesCreator.kt @@ -15,10 +15,10 @@ */ package jetbrains.exodus.query.metadata -import com.orientechnologies.orient.core.db.ODatabaseSession -import com.orientechnologies.orient.core.metadata.schema.OClass -import com.orientechnologies.orient.core.record.ODirection -import com.orientechnologies.orient.core.record.OVertex +import com.jetbrains.youtrack.db.api.DatabaseSession +import com.jetbrains.youtrack.db.api.record.Direction +import com.jetbrains.youtrack.db.api.record.Vertex +import com.jetbrains.youtrack.db.api.schema.SchemaClass import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.edgeClassName import jetbrains.exodus.entitystore.orientdb.getTargetLocalEntityIds import jetbrains.exodus.entitystore.orientdb.setTargetLocalEntityIds @@ -26,7 +26,7 @@ import mu.KotlinLogging private val log = KotlinLogging.logger {} -internal fun ODatabaseSession.applyIndices(indices: Map>) { +internal fun DatabaseSession.applyIndices(indices: Map>) { IndicesCreator(indices).createIndices(this) } @@ -35,7 +35,7 @@ internal class IndicesCreator( ) { private val logger = PaddedLogger.logger(log) - fun createIndices(oSession: ODatabaseSession) { + fun createIndices(oSession: DatabaseSession) { try { with (logger) { appendLine("applying indices to OrientDB") @@ -47,9 +47,9 @@ internal class IndicesCreator( withPadding { for ((_, indexName, properties, unique) in indices) { append(indexName) - if (oClass.getClassIndex(indexName) == null) { - val indexType = if (unique) OClass.INDEX_TYPE.UNIQUE else OClass.INDEX_TYPE.NOTUNIQUE - oClass.createIndex(indexName, indexType, *properties.toTypedArray()) + if (!oClass.areIndexed(oSession, *properties.toTypedArray())) { + val indexType = if (unique) SchemaClass.INDEX_TYPE.UNIQUE else SchemaClass.INDEX_TYPE.NOTUNIQUE + oClass.createIndex(oSession, indexName, indexType, *properties.toTypedArray()) appendLine(", created") } else { appendLine(", already created") @@ -67,7 +67,7 @@ internal class IndicesCreator( } } -internal fun ODatabaseSession.initializeIndices(schemaApplicationResult: SchemaApplicationResult) { +internal fun DatabaseSession.initializeIndices(schemaApplicationResult: SchemaApplicationResult) { /* * The order of operations matter. * We want to initialize complementary properties before creating indices, @@ -78,7 +78,7 @@ internal fun ODatabaseSession.initializeIndices(schemaApplicationResult: SchemaA } -internal fun ODatabaseSession.initializeComplementaryPropertiesForNewIndexedLinks( +internal fun DatabaseSession.initializeComplementaryPropertiesForNewIndexedLinks( newIndexedLinks: Map>, // ClassName -> set of link names commitEvery: Int = 50 ) { @@ -87,15 +87,15 @@ internal fun ODatabaseSession.initializeComplementaryPropertiesForNewIndexedLink var counter = 0 withTx { for ((className, indexedLinks) in newIndexedLinks) { - for (vertex in query("select from $className").vertexStream().map { it as OVertex }) { + for (vertex in query("select from $className").vertexStream().map { it as Vertex }) { for (indexedLink in indexedLinks) { val edgeClassName = edgeClassName(indexedLink) val targetLocalEntityIds = vertex.getTargetLocalEntityIds(indexedLink) - for (target in vertex.getVertices(ODirection.OUT, edgeClassName)) { + for (target in vertex.getVertices(Direction.OUT, edgeClassName)) { targetLocalEntityIds.add(target) } vertex.setTargetLocalEntityIds(indexedLink, targetLocalEntityIds) - vertex.save() + vertex.save() counter++ if (counter == commitEvery) { @@ -123,7 +123,7 @@ internal data class DeferredIndex( ) } -internal fun OClass.makeDeferredIndexForEmbeddedSet(propertyName: String): DeferredIndex { +internal fun SchemaClass.makeDeferredIndexForEmbeddedSet(propertyName: String): DeferredIndex { return DeferredIndex( ownerVertexName = this.name, setOf(propertyName), diff --git a/query/src/main/kotlin/jetbrains/exodus/query/metadata/MigrateXodusToOrient.kt b/query/src/main/kotlin/jetbrains/exodus/query/metadata/MigrateXodusToOrient.kt index 16afabe72..c2551206d 100644 --- a/query/src/main/kotlin/jetbrains/exodus/query/metadata/MigrateXodusToOrient.kt +++ b/query/src/main/kotlin/jetbrains/exodus/query/metadata/MigrateXodusToOrient.kt @@ -15,11 +15,12 @@ */ package jetbrains.exodus.query.metadata -import com.orientechnologies.orient.core.db.ODatabaseType + +import com.jetbrains.youtrack.db.api.DatabaseType import jetbrains.exodus.entitystore.orientdb.ODatabaseConfig import jetbrains.exodus.entitystore.orientdb.ODatabaseConnectionConfig import jetbrains.exodus.entitystore.orientdb.ODatabaseProviderImpl -import jetbrains.exodus.entitystore.orientdb.initOrientDbServer +import jetbrains.exodus.entitystore.orientdb.iniYouTrackDb fun main() { val xodusDatabaseDirectory = requireParam("xodusDatabaseDirectory") @@ -57,11 +58,11 @@ fun main() { val xodusCypherIV = xodusCipherIVStr.toLongOrNull() ?: 0L val xodusMemoryUsagePercentage = xodusMemoryUsagePercentageStr.toIntOrNull() ?: 10 val orientDatabaseType = if (orientDatabaseTypeStr.isNullOrBlank() || orientDatabaseTypeStr.lowercase() == "memory") { - ODatabaseType.MEMORY + DatabaseType.MEMORY } else { - ODatabaseType.PLOCAL + DatabaseType.PLOCAL } - val orientDatabaseDirectory = if (orientDatabaseType == ODatabaseType.MEMORY) { + val orientDatabaseDirectory = if (orientDatabaseType == DatabaseType.MEMORY) { "memory" } else { require(!orientDatabaseDirectoryStr.isNullOrBlank()) { "For not in-memory OrientDB, the orientDatabaseDirectory param is required" } @@ -103,7 +104,7 @@ fun main() { .withDatabaseName(orientDatabaseName) .build() - val db = initOrientDbServer(connectionConfig) + val db = iniYouTrackDb(connectionConfig) // create a provider val dbProvider = ODatabaseProviderImpl(config, db) diff --git a/query/src/main/kotlin/jetbrains/exodus/query/metadata/OModelMetaData.kt b/query/src/main/kotlin/jetbrains/exodus/query/metadata/OModelMetaData.kt index e24fb9ecc..7d44162e0 100644 --- a/query/src/main/kotlin/jetbrains/exodus/query/metadata/OModelMetaData.kt +++ b/query/src/main/kotlin/jetbrains/exodus/query/metadata/OModelMetaData.kt @@ -15,8 +15,8 @@ */ package jetbrains.exodus.query.metadata -import com.orientechnologies.orient.core.db.ODatabaseSession -import com.orientechnologies.orient.core.metadata.schema.OClass +import com.jetbrains.youtrack.db.api.DatabaseSession +import com.jetbrains.youtrack.db.api.schema.SchemaClass import jetbrains.exodus.entitystore.orientdb.* class OModelMetaData( @@ -46,11 +46,11 @@ class OModelMetaData( } override fun getOrCreateEdgeClass( - session: ODatabaseSession, + session: DatabaseSession, linkName: String, outClassName: String, inClassName: String - ): OClass { + ): SchemaClass { /** * It is enough to check the existence of the edge class. * We reuse the same edge class for all the links with the same name. diff --git a/query/src/main/kotlin/jetbrains/exodus/query/metadata/Utils.kt b/query/src/main/kotlin/jetbrains/exodus/query/metadata/Utils.kt index 3c774409b..22a064a5c 100644 --- a/query/src/main/kotlin/jetbrains/exodus/query/metadata/Utils.kt +++ b/query/src/main/kotlin/jetbrains/exodus/query/metadata/Utils.kt @@ -15,7 +15,7 @@ */ package jetbrains.exodus.query.metadata -import com.orientechnologies.orient.core.db.ODatabaseSession +import com.jetbrains.youtrack.db.api.DatabaseSession import jetbrains.exodus.entitystore.PersistentEntityStore import jetbrains.exodus.entitystore.StoreTransaction @@ -33,7 +33,7 @@ fun PersistentEntityStore.withReadonlyTx(block: (StoreTransaction) -> R): R } } -fun ODatabaseSession.withTx(block: (ODatabaseSession) -> R): R { +fun DatabaseSession.withTx(block: (DatabaseSession) -> R): R { this.begin() try { val result = block(this) diff --git a/query/src/main/kotlin/jetbrains/exodus/query/metadata/XodusToOrientDataMigrator.kt b/query/src/main/kotlin/jetbrains/exodus/query/metadata/XodusToOrientDataMigrator.kt index 4f6596bc2..245a82fdd 100644 --- a/query/src/main/kotlin/jetbrains/exodus/query/metadata/XodusToOrientDataMigrator.kt +++ b/query/src/main/kotlin/jetbrains/exodus/query/metadata/XodusToOrientDataMigrator.kt @@ -15,7 +15,8 @@ */ package jetbrains.exodus.query.metadata -import com.orientechnologies.orient.core.metadata.schema.OType + +import com.jetbrains.youtrack.db.api.schema.PropertyType import jetbrains.exodus.bindings.ComparableSet import jetbrains.exodus.entitystore.EntityId import jetbrains.exodus.entitystore.EntityRemovedInDatabaseException @@ -42,7 +43,13 @@ fun migrateDataFromXodusToOrientDb( * */ entitiesPerTransaction: Int = 10 ): XodusToOrientMigrationStats { - val migrator = XodusToOrientDataMigrator(xodus, orient, orientProvider, schemaBuddy, entitiesPerTransaction) + val migrator = XodusToOrientDataMigrator( + xodus, + orient, + orientProvider, + schemaBuddy, + entitiesPerTransaction + ) return migrator.migrate() } @@ -167,19 +174,24 @@ internal class XodusToOrientDataMigrator( val oClass = oSession.getClass(type) ?: oSession.createVertexClass(type) val classId = xodus.getEntityTypeId(type) - oClass.setCustom(CLASS_ID_CUSTOM_PROPERTY_NAME, classId.toString()) + oClass.setCustom(oSession, CLASS_ID_CUSTOM_PROPERTY_NAME, classId.toString()) maxClassId = maxOf(maxClassId, classId) // create localEntityId property if absent if (oClass.getProperty(LOCAL_ENTITY_ID_PROPERTY_NAME) == null) { - oClass.createProperty(LOCAL_ENTITY_ID_PROPERTY_NAME, OType.LONG) + oClass.createProperty(oSession, LOCAL_ENTITY_ID_PROPERTY_NAME,PropertyType.LONG) } } entityClassesCount = entityTypes.size } // create a sequence to generate classIds - check(schemaBuddy.getSequenceOrNull(oSession, CLASS_ID_SEQUENCE_NAME) == null) { "$CLASS_ID_SEQUENCE_NAME is already created. It means that some data migration has happened to the target database before. Such a scenario is not supported." } + check( + schemaBuddy.getSequenceOrNull( + oSession, + CLASS_ID_SEQUENCE_NAME + ) == null + ) { "$CLASS_ID_SEQUENCE_NAME is already created. It means that some data migration has happened to the target database before. Such a scenario is not supported." } oSession.createClassIdSequenceIfAbsent(maxClassId.toLong()) log.info { "All the types have been copied" } @@ -189,7 +201,8 @@ internal class XodusToOrientDataMigrator( private fun copyPropertiesAndBlobs(): Set { log.info { "2. Copy entities, their simple properties and blobs" } val edgeClassesToCreate = HashSet() - val sequencesToCreate = mutableListOf>() // sequenceName, largestExistingId + val sequencesToCreate = + mutableListOf>() // sequenceName, largestExistingId xodus.withReadonlyTx { xTx -> orient.withCountingTx(entitiesPerTransaction) { countingTx -> val entityTypes = xTx.entityTypes.toSet() @@ -308,7 +321,8 @@ internal class XodusToOrientDataMigrator( try { xTx.getEntity(xTargetEntity.id) - val oTargetId = xEntityIdToOEntityId.getValue(xTargetEntity.id) + val oTargetId = + xEntityIdToOEntityId.getValue(xTargetEntity.id) oEntity.addLink(linkName, oTargetId) copiedLinks.add(xTargetEntity.id) linksCopied++ diff --git a/query/src/main/kotlin/jetbrains/exodus/query/metadata/XodusToOrientDataMigratorLauncher.kt b/query/src/main/kotlin/jetbrains/exodus/query/metadata/XodusToOrientDataMigratorLauncher.kt index a69d7a839..f34760894 100644 --- a/query/src/main/kotlin/jetbrains/exodus/query/metadata/XodusToOrientDataMigratorLauncher.kt +++ b/query/src/main/kotlin/jetbrains/exodus/query/metadata/XodusToOrientDataMigratorLauncher.kt @@ -15,7 +15,7 @@ */ package jetbrains.exodus.query.metadata -import com.orientechnologies.orient.core.db.OrientDB +import com.jetbrains.youtrack.db.api.YouTrackDB import jetbrains.exodus.entitystore.PersistentEntityStores import jetbrains.exodus.entitystore.orientdb.* import jetbrains.exodus.env.Environments @@ -42,7 +42,7 @@ val VERTEX_CLASSES_TO_SKIP_MIGRATION = 10 */ data class MigrateToOrientConfig( val databaseProvider: ODatabaseProvider, - val db: OrientDB, + val db: YouTrackDB, val orientConfig: ODatabaseConfig, val closeOnFinish: Boolean = false ) @@ -88,7 +88,7 @@ class XodusToOrientDataMigratorLauncher( val dbName = orient.orientConfig.databaseName val classesCount = dbProvider.withSession { - it.metadata.schema.classes.filter { !it.name.startsWith("O") }.size + it.schema.classes.filter { !it.name.startsWith("O") }.size } if (classesCount > VERTEX_CLASSES_TO_SKIP_MIGRATION) { log.info { "There are already $classesCount classes in the database so it's considered as migrated" } diff --git a/query/src/main/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializer.kt b/query/src/main/kotlin/jetbrains/exodus/query/metadata/YouTrackDbSchemaInitializer.kt similarity index 79% rename from query/src/main/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializer.kt rename to query/src/main/kotlin/jetbrains/exodus/query/metadata/YouTrackDbSchemaInitializer.kt index 85b11d218..410d77965 100644 --- a/query/src/main/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializer.kt +++ b/query/src/main/kotlin/jetbrains/exodus/query/metadata/YouTrackDbSchemaInitializer.kt @@ -15,14 +15,14 @@ */ package jetbrains.exodus.query.metadata -import com.orientechnologies.orient.core.collate.OCaseInsensitiveCollate -import com.orientechnologies.orient.core.db.ODatabaseSession -import com.orientechnologies.orient.core.metadata.schema.OClass -import com.orientechnologies.orient.core.metadata.schema.OProperty -import com.orientechnologies.orient.core.metadata.schema.OType -import com.orientechnologies.orient.core.record.ODirection -import com.orientechnologies.orient.core.record.OEdge -import com.orientechnologies.orient.core.record.OVertex +import com.jetbrains.youtrack.db.api.DatabaseSession +import com.jetbrains.youtrack.db.api.record.Direction +import com.jetbrains.youtrack.db.api.record.Edge +import com.jetbrains.youtrack.db.api.record.Vertex +import com.jetbrains.youtrack.db.api.schema.Property +import com.jetbrains.youtrack.db.api.schema.PropertyType +import com.jetbrains.youtrack.db.api.schema.SchemaClass +import com.jetbrains.youtrack.db.internal.core.collate.CaseInsensitiveCollate import jetbrains.exodus.entitystore.orientdb.OVertexEntity import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.LOCAL_ENTITY_ID_PROPERTY_NAME import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.linkTargetEntityIdPropertyName @@ -38,38 +38,47 @@ internal data class SchemaApplicationResult( val newIndexedLinks: Map> // ClassName -> set of link names ) -internal fun ODatabaseSession.applySchema( +internal fun DatabaseSession.applySchema( metaData: ModelMetaData, indexForEverySimpleProperty: Boolean = false, applyLinkCardinality: Boolean = true ): SchemaApplicationResult = applySchema(metaData.entitiesMetaData, indexForEverySimpleProperty, applyLinkCardinality) -internal fun ODatabaseSession.applySchema( +internal fun DatabaseSession.applySchema( entitiesMetaData: Iterable, indexForEverySimpleProperty: Boolean = false, applyLinkCardinality: Boolean = true ): SchemaApplicationResult { val initializer = - OrientDbSchemaInitializer(entitiesMetaData, this, indexForEverySimpleProperty, applyLinkCardinality) + YouTrackDbSchemaInitializer( + entitiesMetaData, + this, + indexForEverySimpleProperty, + applyLinkCardinality + ) return initializer.apply() } -internal fun ODatabaseSession.addAssociation( +internal fun DatabaseSession.addAssociation( outEntityMetadata: EntityMetaData, association: AssociationEndMetaData, applyLinkCardinality: Boolean = true ): SchemaApplicationResult { val link = association.toLinkMetadata(outEntityMetadata.type) - return addAssociation(link, outEntityMetadata.getIndicesContainingLink(link.name), applyLinkCardinality) + return addAssociation( + link, + outEntityMetadata.getIndicesContainingLink(link.name), + applyLinkCardinality + ) } -internal fun ODatabaseSession.addAssociation( +internal fun DatabaseSession.addAssociation( link: LinkMetadata, indicesContainingLink: List, applyLinkCardinality: Boolean = true ): SchemaApplicationResult { - val initializer = OrientDbSchemaInitializer( + val initializer = YouTrackDbSchemaInitializer( listOf(), this, indexForEverySimpleProperty = false, @@ -78,7 +87,7 @@ internal fun ODatabaseSession.addAssociation( return initializer.addAssociation(link, indicesContainingLink) } -internal fun ODatabaseSession.removeAssociation( +internal fun DatabaseSession.removeAssociation( sourceClassName: String, targetClassName: String, associationName: String @@ -94,11 +103,16 @@ internal fun ODatabaseSession.removeAssociation( ) } -internal fun ODatabaseSession.removeAssociation( +internal fun DatabaseSession.removeAssociation( association: LinkMetadata ) { val initializer = - OrientDbSchemaInitializer(listOf(), this, indexForEverySimpleProperty = false, applyLinkCardinality = false) + YouTrackDbSchemaInitializer( + listOf(), + this, + indexForEverySimpleProperty = false, + applyLinkCardinality = false + ) initializer.removeAssociation(association) } @@ -109,20 +123,21 @@ internal data class LinkMetadata( val cardinality: AssociationEndCardinality ) -internal fun AssociationEndMetaData.toLinkMetadata(outClassName: String): LinkMetadata = LinkMetadata( - name = name, - outClassName = outClassName, - inClassName = oppositeEntityMetaData.type, - cardinality = cardinality -) +internal fun AssociationEndMetaData.toLinkMetadata(outClassName: String): LinkMetadata = + LinkMetadata( + name = name, + outClassName = outClassName, + inClassName = oppositeEntityMetaData.type, + cardinality = cardinality + ) private fun EntityMetaData.getIndicesContainingLink(linkName: String): List { return indexes.filter { index -> index.fields.any { field -> field.name == linkName } } } -internal class OrientDbSchemaInitializer( +internal class YouTrackDbSchemaInitializer( private val entitiesMetaData: Iterable, - private val oSession: ODatabaseSession, + private val oSession: DatabaseSession, private val indexForEverySimpleProperty: Boolean, private val applyLinkCardinality: Boolean ) { @@ -148,7 +163,11 @@ internal class OrientDbSchemaInitializer( } private fun linkUniqueIndex(edgeClassName: String): DeferredIndex { - return DeferredIndex(edgeClassName, setOf(OEdge.DIRECTION_IN, OEdge.DIRECTION_OUT), unique = true) + return DeferredIndex( + edgeClassName, + setOf(Edge.DIRECTION_IN, Edge.DIRECTION_OUT), + unique = true + ) } fun apply(): SchemaApplicationResult { @@ -226,7 +245,10 @@ internal class OrientDbSchemaInitializer( } } - fun addAssociation(association: LinkMetadata, indicesContainingLink: List): SchemaApplicationResult { + fun addAssociation( + association: LinkMetadata, + indicesContainingLink: List + ): SchemaApplicationResult { try { appendLine("create association [${association.outClassName} -> ${association.name} -> ${association.inClassName}] if absent:") this.addLinkImpl( @@ -299,8 +321,8 @@ internal class OrientDbSchemaInitializer( * */ } - private fun ODatabaseSession.createVertexClassIfAbsent(name: String): OClass { - var oClass: OClass? = getClass(name) + private fun DatabaseSession.createVertexClassIfAbsent(name: String): SchemaClass { + var oClass: SchemaClass? = getClass(name) if (oClass == null) { oClass = oSession.createVertexClass(name)!! append(", created") @@ -310,9 +332,9 @@ internal class OrientDbSchemaInitializer( return oClass } - private fun ODatabaseSession.createEdgeClassIfAbsent(name: String): OClass { + private fun DatabaseSession.createEdgeClassIfAbsent(name: String): SchemaClass { val className = OVertexEntity.edgeClassName(name) - var oClass: OClass? = getClass(className) + var oClass: SchemaClass? = getClass(className) if (oClass == null) { oClass = oSession.createEdgeClass(className)!! append(", edge class created") @@ -322,7 +344,7 @@ internal class OrientDbSchemaInitializer( return oClass } - private fun OClass.applySuperClass(superClassName: String?) { + private fun SchemaClass.applySuperClass(superClassName: String?) { if (superClassName == null) { append(", no super type") } else { @@ -331,7 +353,7 @@ internal class OrientDbSchemaInitializer( if (superClasses.contains(superClass)) { append(", already set") } else { - addSuperClass(superClass) + addSuperClass(oSession, superClass) append(", set") } } @@ -373,19 +395,19 @@ internal class OrientDbSchemaInitializer( } private fun applyLinkCardinality( - edgeClass: OClass, - outClass: OClass, + edgeClass: SchemaClass, + outClass: SchemaClass, outCardinality: AssociationEndCardinality, - inClass: OClass, + inClass: SchemaClass, ) { - val linkOutPropName = OVertex.getEdgeLinkFieldName(ODirection.OUT, edgeClass.name) + val linkOutPropName = Vertex.getEdgeLinkFieldName(Direction.OUT, edgeClass.name) append("outProp: ${outClass.name}.$linkOutPropName") val outProp = outClass.createLinkPropertyIfAbsent(linkOutPropName) // applying cardinality only to out direct property outProp.applyCardinality(outCardinality) appendLine() - val linkInPropName = OVertex.getEdgeLinkFieldName(ODirection.IN, edgeClass.name) + val linkInPropName = Vertex.getEdgeLinkFieldName(Direction.IN, edgeClass.name) append("inProp: ${inClass.name}.$linkInPropName") inClass.createLinkPropertyIfAbsent(linkInPropName) appendLine() @@ -398,8 +420,8 @@ internal class OrientDbSchemaInitializer( private fun applyIndices( linkName: String, - edgeClass: OClass, - outClass: OClass, + edgeClass: SchemaClass, + outClass: SchemaClass, indicesContainingLink: List ) { addIndex(linkUniqueIndex(edgeClass.name)) @@ -411,12 +433,13 @@ internal class OrientDbSchemaInitializer( if (!outClass.existsProperty(indexedPropName)) { newIndexedLinks.getOrPut(outClass.name) { HashSet() }.add(linkName) } - outClass.createPropertyIfAbsent(indexedPropName, OType.LINKBAG) + outClass.createPropertyIfAbsent(indexedPropName, PropertyType.LINKBAG) } for (index in indicesContainingLink) { val simpleProperties = index.fields.filter { it.isProperty }.map { it.name }.toSet() - val linkComplementaryProperties = index.fields.filter { !it.isProperty }.map { linkTargetEntityIdPropertyName(it.name) }.toSet() + val linkComplementaryProperties = index.fields.filter { !it.isProperty } + .map { linkTargetEntityIdPropertyName(it.name) }.toSet() val allIndexedProperties = simpleProperties + linkComplementaryProperties // create the index only if all the containing properties are already initialized if (allIndexedProperties.all { outClass.existsProperty(it) }) { @@ -433,7 +456,7 @@ internal class OrientDbSchemaInitializer( appendLine() } - private fun OProperty.applyCardinality(cardinality: AssociationEndCardinality) { + private fun Property.applyCardinality(cardinality: AssociationEndCardinality) { when (cardinality) { AssociationEndCardinality._0_1 -> { setRequirement(false) @@ -461,40 +484,40 @@ internal class OrientDbSchemaInitializer( } } - private fun OProperty.setMaxIfDifferent(max: String?) { + private fun Property.setMaxIfDifferent(max: String?) { append(", max $max") if (this.max == max) { append(" already set") } else { - setMax(max) + setMax(oSession, max) append(" set") } } - private fun OProperty.setMinIfDifferent(min: String?) { + private fun Property.setMinIfDifferent(min: String?) { append(", min $min") if (this.min == min) { append(" already set") } else { - setMin(min) + setMin(oSession, min) append(" set") } } private fun removeAssociationImpl(association: LinkMetadata) { - removeEdge(association.outClassName, association.name, ODirection.OUT) - removeEdge(association.inClassName, association.name, ODirection.IN) + removeEdge(association.outClassName, association.name, Direction.OUT) + removeEdge(association.inClassName, association.name, Direction.IN) } - private fun removeEdge(className: String, associationName: String, direction: ODirection) { + private fun removeEdge(className: String, associationName: String, direction: Direction) { append(className) val sourceClass = oSession.getClass(className) val edgeClassName = OVertexEntity.edgeClassName(associationName) if (sourceClass != null) { - val propOutName = OVertex.getEdgeLinkFieldName(direction, edgeClassName) + val propOutName = Vertex.getEdgeLinkFieldName(direction, edgeClassName) append(".$propOutName") if (sourceClass.existsProperty(propOutName)) { - sourceClass.dropProperty(propOutName) + sourceClass.dropProperty(oSession, propOutName) append(" deleted") } else { append(" not found") @@ -538,7 +561,7 @@ internal class OrientDbSchemaInitializer( } } - private fun OClass.applySimpleProperty( + private fun SchemaClass.applySimpleProperty( simpleProp: PropertyMetaDataImpl, required: Boolean ) { @@ -546,10 +569,11 @@ internal class OrientDbSchemaInitializer( append(propertyName) when (simpleProp.type) { - PropertyType.PRIMITIVE -> { + jetbrains.exodus.query.metadata.PropertyType.PRIMITIVE -> { require(simpleProp is SimplePropertyMetaDataImpl) { "$propertyName is a primitive property but it is not an instance of SimplePropertyMetaDataImpl. Happy fixing!" } val primitiveTypeName = - simpleProp.primitiveTypeName ?: throw IllegalArgumentException("primitiveTypeName is null") + simpleProp.primitiveTypeName + ?: throw IllegalArgumentException("primitiveTypeName is null") if (primitiveTypeName.lowercase() == "set") { append(", is not supported yet") @@ -563,7 +587,8 @@ internal class OrientDbSchemaInitializer( * */ val typeParameter = simpleProp.typeParameterNames?.firstOrNull() ?: throw IllegalStateException("$propertyName is Set but does not contain information about the type parameter") - val oProperty = createEmbeddedSetPropertyIfAbsent(propertyName, getOType(typeParameter)) + val oProperty = + createEmbeddedSetPropertyIfAbsent(propertyName, getOType(typeParameter)) /* * If the value is not defined, the property returns true. @@ -582,13 +607,14 @@ internal class OrientDbSchemaInitializer( val index = makeDeferredIndexForEmbeddedSet(propertyName) addIndex(index) } else { // primitive types - val oProperty = createPropertyIfAbsent(propertyName, getOType(primitiveTypeName)) + val oProperty = + createPropertyIfAbsent(propertyName, getOType(primitiveTypeName)) oProperty.setRequirement(required) if (indexForEverySimpleProperty) { addIndex(simplePropertyIndex(name, propertyName)) } - if (primitiveTypeName.lowercase() == "boolean" && oProperty.defaultValue == null){ - oProperty.defaultValue = "false" + if (primitiveTypeName.lowercase() == "boolean" && oProperty.defaultValue == null) { + oProperty.setDefaultValue(oSession, "false") } } @@ -629,62 +655,65 @@ internal class OrientDbSchemaInitializer( * */ } - PropertyType.TEXT -> { - val oProperty = createPropertyIfAbsent(propertyName, OType.LINK) + jetbrains.exodus.query.metadata.PropertyType.TEXT -> { + val oProperty = createPropertyIfAbsent(propertyName, PropertyType.LINK) oProperty.setRequirement(required) } - PropertyType.BLOB -> { - val oProperty = createPropertyIfAbsent(propertyName, OType.LINK) + jetbrains.exodus.query.metadata.PropertyType.BLOB -> { + val oProperty = createPropertyIfAbsent(propertyName, PropertyType.LINK) oProperty.setRequirement(required) } } appendLine() } - private fun OProperty.setRequirement(required: Boolean) { + private fun Property.setRequirement(required: Boolean) { if (required) { append(", required") if (!isMandatory) { - isMandatory = true + setMandatory(oSession, true) } setNotNullIfDifferent(true) } else { append(", optional") if (isMandatory) { - isMandatory = false + setMandatory(oSession, false) } } } - private fun OProperty.setNotNullIfDifferent(notNull: Boolean) { + private fun Property.setNotNullIfDifferent(notNull: Boolean) { if (notNull) { append(", not nullable") if (!isNotNull) { - setNotNull(true) + setNotNull(oSession, true) } } else { append(", nullable") if (isNotNull) { - setNotNull(false) + setNotNull(oSession, false) } } } - private fun OClass.createPropertyIfAbsent(propertyName: String, oType: OType): OProperty { + private fun SchemaClass.createPropertyIfAbsent( + propertyName: String, + oType: PropertyType + ): Property { append(", type is $oType") val oProperty = if (existsProperty(propertyName)) { append(", already created") getProperty(propertyName) } else { append(", created") - createProperty(propertyName, oType) + createProperty(oSession, propertyName, oType) } - if (oType == OType.STRING) { - if (oProperty.collate.name == OCaseInsensitiveCollate.NAME) { + if (oType == PropertyType.STRING) { + if (oProperty.collate.name == CaseInsensitiveCollate.NAME) { append(", case-insensitive collate already set") } else { - oProperty.setCollate(OCaseInsensitiveCollate.NAME) + oProperty.setCollate(oSession, CaseInsensitiveCollate.NAME) append(", set case-insensitive collate") } } @@ -705,56 +734,61 @@ internal class OrientDbSchemaInitializer( * * But we still can set linkedClassType for direct link out-properties. * */ - private fun OClass.createLinkPropertyIfAbsent(propertyName: String): OProperty { + private fun SchemaClass.createLinkPropertyIfAbsent(propertyName: String): Property { val oProperty = if (existsProperty(propertyName)) { append(", already created") getProperty(propertyName) } else { append(", created") - createProperty(propertyName, OType.LINKBAG) + createProperty(oSession, propertyName, PropertyType.LINKBAG) + } + require(oProperty.type == PropertyType.LINKBAG) { + "$propertyName type is ${oProperty.type} but ${PropertyType.LINKBAG} was expected instead. Types migration is not supported." } - require(oProperty.type == OType.LINKBAG) { "$propertyName type is ${oProperty.type} but ${OType.LINKBAG} was expected instead. Types migration is not supported." } return oProperty } - private fun OClass.createEmbeddedSetPropertyIfAbsent(propertyName: String, oType: OType): OProperty { + private fun SchemaClass.createEmbeddedSetPropertyIfAbsent( + propertyName: String, + oType: PropertyType + ): Property { append(", type of the set is $oType") val oProperty = if (existsProperty(propertyName)) { append(", already created") getProperty(propertyName) } else { append(", created") - createProperty(propertyName, OType.EMBEDDEDSET, oType) + createProperty(oSession, propertyName, PropertyType.EMBEDDEDSET, oType) } - if (oType == OType.STRING) { - if (oProperty.collate.name == OCaseInsensitiveCollate.NAME) { + if (oType == PropertyType.STRING) { + if (oProperty.collate.name == CaseInsensitiveCollate.NAME) { append(", case-insensitive collate already set") } else { - oProperty.setCollate(OCaseInsensitiveCollate.NAME) + oProperty.setCollate(oSession, CaseInsensitiveCollate.NAME) append(", set case-insensitive collate") } } - require(oProperty.type == OType.EMBEDDEDSET) { "$propertyName type is ${oProperty.type} but ${OType.EMBEDDEDSET} was expected instead. Types migration is not supported." } + require(oProperty.type == PropertyType.EMBEDDEDSET) { "$propertyName type is ${oProperty.type} but ${PropertyType.EMBEDDEDSET} was expected instead. Types migration is not supported." } require(oProperty.linkedType == oType) { "$propertyName type of the set is ${oProperty.linkedType} but $oType was expected instead. Types migration is not supported." } return oProperty } - private fun getOType(jvmTypeName: String): OType { + private fun getOType(jvmTypeName: String): PropertyType { return when (jvmTypeName.lowercase()) { - "boolean" -> OType.BOOLEAN - "string" -> OType.STRING + "boolean" -> PropertyType.BOOLEAN + "string" -> PropertyType.STRING - "byte" -> OType.BYTE - "short" -> OType.SHORT + "byte" -> PropertyType.BYTE + "short" -> PropertyType.SHORT "int", - "integer" -> OType.INTEGER + "integer" -> PropertyType.INTEGER - "long" -> OType.LONG + "long" -> PropertyType.LONG - "float" -> OType.FLOAT - "double" -> OType.DOUBLE + "float" -> PropertyType.FLOAT + "double" -> PropertyType.DOUBLE - "datetime" -> OType.LONG + "datetime" -> PropertyType.LONG else -> throw IllegalArgumentException("$jvmTypeName is not supported. Feel free to support it.") } diff --git a/query/src/test/kotlin/jetbrains/exodus/query/OBinaryOperationsWithSortTest.kt b/query/src/test/kotlin/jetbrains/exodus/query/OBinaryOperationsWithSortTest.kt index 2c08d2c5b..cdf10fb4a 100644 --- a/query/src/test/kotlin/jetbrains/exodus/query/OBinaryOperationsWithSortTest.kt +++ b/query/src/test/kotlin/jetbrains/exodus/query/OBinaryOperationsWithSortTest.kt @@ -15,7 +15,6 @@ */ package jetbrains.exodus.query -import jetbrains.exodus.entitystore.EntityIterable import jetbrains.exodus.entitystore.orientdb.iterate.OEntityOfTypeIterable import jetbrains.exodus.entitystore.orientdb.testutil.* import jetbrains.exodus.query.metadata.entity @@ -29,28 +28,28 @@ import kotlin.test.assertContentEquals class OBinaryOperationsWithSortTest : OTestMixin { @Rule @JvmField - val orientDbRule = InMemoryOrientDB() + val orientDbRule = InMemoryYouTrackDB() - override val orientDb = orientDbRule + override val youTrackDb = orientDbRule lateinit var testCase: OUsersWithInheritanceTestCase @Test fun union() { - testCase = OUsersWithInheritanceTestCase(orientDb) + testCase = OUsersWithInheritanceTestCase(youTrackDb) val model = givenModel() - val engine = QueryEngine(model, orientDb.store) + val engine = QueryEngine(model, youTrackDb.store) engine.sortEngine = SortEngine() - orientDb.withStoreTx { txn -> + youTrackDb.withStoreTx { txn -> val users = OEntityOfTypeIterable(txn, User.CLASS) val agents = OEntityOfTypeIterable(txn, Agent.CLASS) val union = engine.union(users, agents) val sorted = engine.query(union, BaseUser.CLASS, SortByProperty(null, "name", true)) assertContentEquals( listOf("u1", "u2"), - (sorted as EntityIterable).skip(2).take(2).toList().map { it.getProperty("name") }) + sorted.skip(2).take(2).toList().map { it.getProperty("name") }) } } @@ -58,16 +57,16 @@ class OBinaryOperationsWithSortTest : OTestMixin { @Test @Ignore fun intersect() { - testCase = OUsersWithInheritanceTestCase(orientDb) + testCase = OUsersWithInheritanceTestCase(youTrackDb) val model = givenModel() - val engine = QueryEngine(model, orientDb.store) + val engine = QueryEngine(model, youTrackDb.store) engine.sortEngine = SortEngine() - orientDb.withStoreTx { txn -> + youTrackDb.withStoreTx { txn -> txn.createUser(Agent.CLASS, "u1") } - orientDb.withStoreTx { txn -> + youTrackDb.withStoreTx { txn -> val users = engine.query(User.CLASS, PropertyEqual("name", "u1")) val all = engine.query(BaseUser.CLASS, PropertyEqual("name", "u1")) val intersect = engine.intersect(users, all) @@ -75,20 +74,20 @@ class OBinaryOperationsWithSortTest : OTestMixin { Assert.assertEquals(2, all.size()) assertContentEquals( listOf("u1"), - (sorted as EntityIterable).toList().map { it.getProperty("name") }) + sorted.toList().map { it.getProperty("name") }) } } @Test @Ignore fun minus() { - testCase = OUsersWithInheritanceTestCase(orientDb) + testCase = OUsersWithInheritanceTestCase(youTrackDb) val model = givenModel() - val engine = QueryEngine(model, orientDb.store) + val engine = QueryEngine(model, youTrackDb.store) engine.sortEngine = SortEngine() - orientDb.withStoreTx { txn -> + youTrackDb.withStoreTx { txn -> val users = OEntityOfTypeIterable(txn, User.CLASS) val u1 = engine.query(BaseUser.CLASS, PropertyEqual("name", "u1")) val minus = engine.exclude(users, u1) @@ -96,12 +95,12 @@ class OBinaryOperationsWithSortTest : OTestMixin { Assert.assertEquals(1, minus.count()) assertContentEquals( listOf("u2"), - (sorted as EntityIterable).toList().map { it.getProperty("name") }) + sorted.toList().map { it.getProperty("name") }) } } - private fun givenModel() = oModel(orientDb.provider) { + private fun givenModel() = oModel(youTrackDb.provider) { entity(BaseUser.CLASS) entity(User.CLASS, BaseUser.CLASS) entity(Admin.CLASS, BaseUser.CLASS) diff --git a/query/src/test/kotlin/jetbrains/exodus/query/OQueryEngineTest.kt b/query/src/test/kotlin/jetbrains/exodus/query/OQueryEngineTest.kt index bf6797416..0afbd65f3 100644 --- a/query/src/test/kotlin/jetbrains/exodus/query/OQueryEngineTest.kt +++ b/query/src/test/kotlin/jetbrains/exodus/query/OQueryEngineTest.kt @@ -61,9 +61,9 @@ class OQueryEngineTest( @Rule @JvmField - val orientDbRule = InMemoryOrientDB() + val orientDbRule = InMemoryYouTrackDB() - override val orientDb = orientDbRule + override val youTrackDb = orientDbRule @Test fun `should query all`() { @@ -674,7 +674,7 @@ class OQueryEngineTest( private fun givenOQueryEngine(metadataOrNull: ModelMetaData? = null): QueryEngine { // ToDo: return orientdb compatible query engine - return QueryEngine(metadataOrNull, orientDb.store).apply { + return QueryEngine(metadataOrNull, youTrackDb.store).apply { sortEngine = SortEngine(this) } } diff --git a/query/src/test/kotlin/jetbrains/exodus/query/metadata/MigrateDataTest.kt b/query/src/test/kotlin/jetbrains/exodus/query/metadata/MigrateDataTest.kt index f27894d91..822bd67f8 100644 --- a/query/src/test/kotlin/jetbrains/exodus/query/metadata/MigrateDataTest.kt +++ b/query/src/test/kotlin/jetbrains/exodus/query/metadata/MigrateDataTest.kt @@ -17,9 +17,9 @@ package jetbrains.exodus.query.metadata -import com.orientechnologies.orient.core.record.OVertex -import com.orientechnologies.orient.core.record.impl.ORecordBytes -import com.orientechnologies.orient.core.record.impl.OVertexDocument +import com.jetbrains.youtrack.db.api.record.Vertex +import com.jetbrains.youtrack.db.internal.core.db.DatabaseSessionInternal +import com.jetbrains.youtrack.db.internal.core.record.impl.RecordBytes import jetbrains.exodus.bindings.ComparableSet import jetbrains.exodus.bindings.StringBinding import jetbrains.exodus.entitystore.PersistentEntityStore @@ -31,7 +31,7 @@ import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.localEntity import jetbrains.exodus.entitystore.orientdb.createVertexClassWithClassId import jetbrains.exodus.entitystore.orientdb.requireClassId import jetbrains.exodus.entitystore.orientdb.requireLocalEntityId -import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryOrientDB +import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryYouTrackDB import jetbrains.exodus.util.ByteArraySizedInputStream import jetbrains.exodus.util.LightOutputStream import jetbrains.exodus.util.UTFUtil @@ -48,7 +48,8 @@ class MigrateDataTest { @Rule @JvmField - val orientDb = InMemoryOrientDB(initializeIssueSchema = false, autoInitializeSchemaBuddy = false) + val youTrackDB = + InMemoryYouTrackDB(initializeIssueSchema = false, autoInitializeSchemaBuddy = false) @Rule @JvmField @@ -68,14 +69,16 @@ class MigrateDataTest { val strNormal = bytes.toString(Charsets.UTF_8) val hexXodusStyle = bytesXodusStyle.toHexString() val hex = bytes.toHexString() - println(""" + println( + """ $i problematic string original: '$str' normal : '$strNormal' xStyle : '$strXodusStyle' bytes : '$hex' xBytes : '$hexXodusStyle' - """.trimIndent()) + """.trimIndent() + ) throw Exception() } } @@ -83,7 +86,7 @@ class MigrateDataTest { @Test fun `migrate string blobs`() { - orientDb.withSession { session -> + youTrackDB.withSession { session -> session.createVertexClassWithClassId("type1") } @@ -105,7 +108,7 @@ class MigrateDataTest { val oId = xodus.withTx { xTx -> val xE = xTx.getEntity(xId) val xBlob = xE.getBlob("blob1")!! - orientDb.withStoreTx { oTx -> + youTrackDB.withStoreTx { oTx -> val oE = oTx.newEntity("type1") oE.setBlob("blob1", xBlob) oE.id @@ -115,7 +118,7 @@ class MigrateDataTest { xodus.withTx { xTx -> val xE = xTx.getEntity(xId) val xBytes = xE.getBlob("blob1")!!.readAllBytes() - orientDb.withStoreTx { tx -> + youTrackDB.withStoreTx { tx -> val oE = tx.getEntity(oId) val oBytes = oE.getBlob("blob1")!!.readAllBytes() assertContentEquals(xBytes, oBytes) @@ -135,7 +138,7 @@ class MigrateDataTest { */ @Test fun `orient add extra bytes to blobs once in a while`() { - orientDb.withSession { session -> + youTrackDB.withSession { session -> session.createVertexClass("turbo") } @@ -145,18 +148,18 @@ class MigrateDataTest { val message = StringBuilder() message.append("given $size bytes") - val id = orientDb.withTxSession { session -> + val id = youTrackDB.withTxSession { session -> val e1 = session.newVertex("turbo") - val blob = ORecordBytes() + val blob = RecordBytes() // this thing reads more bytes than necessary val readBytes = blob.fromInputStream(ByteArrayInputStream(bytes)) message.append(", fromInputStream() read $readBytes bytes") e1.setProperty("blob1", blob) - e1.save() + e1.save() e1.identity } - orientDb.withTxSession { session -> + youTrackDB.withTxSession { session -> val e1 = session.loadVertex(id) val blob = e1.getBlobProperty("blob1") val gotBytes = blob!!.toStream() @@ -181,11 +184,14 @@ class MigrateDataTest { fun `broken strings from Xodus`() { // it is how the string is stored in Classic Xodus on the disk // The first byte is the type identifier (82), the last byte is the end of string (00) - val rawBytesGottenFromAProdXodus = "82e197b0cf83c4a7e2b1a2ceb1c59fc4a7ceb96d20e1b9a8c4a7ceb1e1b8adeda080c4a700".hexToByteArray() + val rawBytesGottenFromAProdXodus = + "82e197b0cf83c4a7e2b1a2ceb1c59fc4a7ceb96d20e1b9a8c4a7ceb1e1b8adeda080c4a700".hexToByteArray() // drop the type identifier for the Xodus decoder - val originalBytesForXodus = rawBytesGottenFromAProdXodus.sliceArray(1 until rawBytesGottenFromAProdXodus.size) + val originalBytesForXodus = + rawBytesGottenFromAProdXodus.sliceArray(1 until rawBytesGottenFromAProdXodus.size) // drop the type identifier and the end of string symbol for "normal" decoders - val originalBytes = rawBytesGottenFromAProdXodus.sliceArray(1 until rawBytesGottenFromAProdXodus.size - 1) + val originalBytes = + rawBytesGottenFromAProdXodus.sliceArray(1 until rawBytesGottenFromAProdXodus.size - 1) // Xodus does not parse a string with the type identifier assertFailsWith { @@ -200,7 +206,17 @@ class MigrateDataTest { val originalStrXodusPropStyle = originalBytesForXodus.toStringXodusPropertyStyle() val charsets = with(Charsets) { - listOf(US_ASCII, ISO_8859_1, UTF_16, UTF_32, UTF_8, UTF_16BE, UTF_16LE, UTF_32BE, UTF_32LE) + listOf( + US_ASCII, + ISO_8859_1, + UTF_16, + UTF_32, + UTF_8, + UTF_16BE, + UTF_16LE, + UTF_32BE, + UTF_32LE + ) } // none of the charsets can encode the string to get original bytes for (charset in charsets) { @@ -217,17 +233,21 @@ class MigrateDataTest { } // the original Xodus-property-style string is malformed from the UTF-8 point of view - assertFailsWith { originalStrXodusPropStyle.encodeToByteArray(throwOnInvalidSequence = true) } + assertFailsWith { + originalStrXodusPropStyle.encodeToByteArray( + throwOnInvalidSequence = true + ) + } // let's migrate such a string from Classic Xodus to Orient and see what happens val entities = pileOfEntities( eProps("type1", 1, "prop1" to originalStrXodusPropStyle) ) xodus.withTx { tx -> tx.createEntities(entities) } - migrateAndCheckData(xodus.store, orientDb) + migrateAndCheckData(xodus.store, youTrackDB) xodus.withTx { xTx -> - orientDb.withStoreTx { oTx -> + youTrackDB.withStoreTx { oTx -> val xStr = xTx.getAll("type1").first().getProperty("prop1") as String val oStr = oTx.getAll("type1").first().getProperty("prop1") as String @@ -237,15 +257,18 @@ class MigrateDataTest { assertNotEquals(xStr, oStr) // fix the string from Xodus - val fixedXStr = xStr.encodeToByteArray(throwOnInvalidSequence = false).decodeToString(throwOnInvalidSequence = true) + val fixedXStr = xStr.encodeToByteArray(throwOnInvalidSequence = false) + .decodeToString(throwOnInvalidSequence = true) // now, fixed Xodus string equals to the Orient string assertEquals(fixedXStr, oStr) - println(""" + println( + """ xStr: '$xStr' fixedXStr: '$fixedXStr' oStr: '$oStr' - """.trimIndent()) + """.trimIndent() + ) } } } @@ -266,9 +289,9 @@ class MigrateDataTest { tx.createEntities(entities) } - migrateAndCheckData(xodus.store, orientDb) + migrateAndCheckData(xodus.store, youTrackDB) - orientDb.store.executeInTransaction { tx -> + youTrackDB.store.executeInTransaction { tx -> tx.assertOrientContainsAllTheEntities(entities) } } @@ -276,16 +299,20 @@ class MigrateDataTest { @Test fun `copy sets`() { val entities = pileOfEntities( - eSets("type1", 1, "set1" to setOf(1, 2 ,3), "set2" to setOf(300, 100)), - eSets("type1", 2, "set1" to setOf("ok string", "not a bad string", getBrokenXodusString())), + eSets("type1", 1, "set1" to setOf(1, 2, 3), "set2" to setOf(300, 100)), + eSets( + "type1", + 2, + "set1" to setOf("ok string", "not a bad string", getBrokenXodusString()) + ), ) xodus.withTx { tx -> tx.createEntities(entities) } - migrateAndCheckData(xodus.store, orientDb) + migrateAndCheckData(xodus.store, youTrackDB) - orientDb.store.executeInTransaction { tx -> + youTrackDB.store.executeInTransaction { tx -> tx.assertOrientContainsAllTheEntities(entities) } } @@ -296,13 +323,13 @@ class MigrateDataTest { tx.newEntity("type1").id } - migrateAndCheckData(xodus.store, orientDb) - orientDb.withSession { - orientDb.schemaBuddy.initialize(it) + migrateAndCheckData(xodus.store, youTrackDB) + youTrackDB.withSession { + youTrackDB.schemaBuddy.initialize(it) } - orientDb.store.executeInTransaction { - orientDb.store.getEntity(entityId) + youTrackDB.store.executeInTransaction { + youTrackDB.store.getEntity(entityId) } } @@ -324,9 +351,9 @@ class MigrateDataTest { tx.createEntities(entities) } - migrateAndCheckData(xodus.store, orientDb) + migrateAndCheckData(xodus.store, youTrackDB) - orientDb.store.executeInTransaction { tx -> + youTrackDB.store.executeInTransaction { tx -> tx.assertOrientContainsAllTheEntities(entities) } } @@ -335,8 +362,10 @@ class MigrateDataTest { fun `copy links`() { val entities = pileOfEntities( eLinks( - "type1", 1, - Link("link1", "type1", 2), Link("link1", "type1", 3), // several links with the same name + "type1", + 1, + Link("link1", "type1", 2), + Link("link1", "type1", 3), // several links with the same name Link("link2", "type2", 4) ), eLinks( @@ -364,9 +393,9 @@ class MigrateDataTest { tx.createEntities(entities) } - migrateAndCheckData(xodus.store, orientDb) + migrateAndCheckData(xodus.store, youTrackDB) - orientDb.store.executeInTransaction { tx -> + youTrackDB.store.executeInTransaction { tx -> tx.assertOrientContainsAllTheEntities(entities) } } @@ -385,9 +414,9 @@ class MigrateDataTest { tx.createEntities(entities) } - migrateAndCheckData(xodus.store, orientDb) + migrateAndCheckData(xodus.store, youTrackDB) - orientDb.store.executeInTransaction { tx -> + youTrackDB.store.executeInTransaction { tx -> tx.assertOrientContainsAllTheEntities(entities) } } @@ -407,11 +436,11 @@ class MigrateDataTest { tx.createEntities(entities) } - migrateAndCheckData(xodus.store, orientDb) + migrateAndCheckData(xodus.store, youTrackDB) var maxClassId = 0 xodus.withTx { xTx -> - orientDb.withSession { oSession -> + youTrackDB.withSession { oSession -> for (type in xTx.entityTypes) { val typeId = xodus.store.getEntityTypeId(type) Assert.assertEquals(typeId, oSession.getClass(type).requireClassId()) @@ -419,7 +448,10 @@ class MigrateDataTest { } assertTrue(maxClassId > 0) - val nextGeneratedClassId = oSession.metadata.sequenceLibrary.getSequence(CLASS_ID_SEQUENCE_NAME).next() + val nextGeneratedClassId = + (oSession as DatabaseSessionInternal).metadata.sequenceLibrary.getSequence( + CLASS_ID_SEQUENCE_NAME + ).next() assertEquals(maxClassId.toLong() + 1, nextGeneratedClassId) } } @@ -440,10 +472,10 @@ class MigrateDataTest { tx.createEntities(entities) } - migrateAndCheckData(xodus.store, orientDb) + migrateAndCheckData(xodus.store, youTrackDB) xodus.withTx { xTx -> - orientDb.withSession { oSession -> + youTrackDB.withSession { oSession -> for (type in xTx.entityTypes) { val xTestIdToLocalEntityId = HashMap() val oTestIdToLocalEntityId = HashMap() @@ -455,7 +487,7 @@ class MigrateDataTest { maxLocalEntityId = maxOf(maxLocalEntityId, localEntityId) } - for (oEntity in oSession.query("select from $type").vertexStream().map { it as OVertexDocument }) { + for (oEntity in oSession.query("select from $type").vertexStream()) { val testId = oEntity.getTestId() val localEntityId = oEntity.requireLocalEntityId() oTestIdToLocalEntityId[testId] = localEntityId @@ -463,7 +495,10 @@ class MigrateDataTest { assertTrue(maxLocalEntityId > 0) val nextGeneratedLocalEntityId = - oSession.metadata.sequenceLibrary.getSequence(localEntityIdSequenceName(type)).next() + (oSession as DatabaseSessionInternal).metadata.sequenceLibrary.getSequence( + localEntityIdSequenceName(type) + ) + .next() assertEquals(maxLocalEntityId + 1, nextGeneratedLocalEntityId) assertEquals(xTestIdToLocalEntityId, oTestIdToLocalEntityId) @@ -472,7 +507,7 @@ class MigrateDataTest { } } - private fun migrateAndCheckData(xodus: PersistentEntityStore, orient: InMemoryOrientDB) { + private fun migrateAndCheckData(xodus: PersistentEntityStore, orient: InMemoryYouTrackDB) { val stats = migrateDataFromXodusToOrientDb( xodus, orient.store, @@ -528,7 +563,7 @@ internal fun OVertexEntity.assertEquals(expected: Entity) { internal fun OVertexEntity.getTestId(): Int = getProperty("id") as Int -internal fun OVertexDocument.getTestId(): Int = getProperty("id") as Int +internal fun Vertex.getTestId(): Int = getProperty("id") as Int internal fun StoreTransaction.createEntities(pile: PileOfEntities) { for (type in pile.types) { @@ -564,7 +599,8 @@ internal fun StoreTransaction.createEntity(entity: Entity) { internal fun StoreTransaction.createLinks(entity: Entity) { val xEntity = this.getAll(entity.type).first { it.getProperty("id") == entity.id } for (link in entity.links) { - val targetXEntity = this.getAll(link.targetType).first { it.getProperty("id") == link.targetId } + val targetXEntity = + this.getAll(link.targetType).first { it.getProperty("id") == link.targetId } xEntity.addLink(link.name, targetXEntity) } } @@ -611,7 +647,11 @@ internal fun eProps(type: String, id: Int, vararg props: Pair eSets(type: String, id: Int, vararg sets: Pair>): Entity where T: Comparable { +internal fun eSets( + type: String, + id: Int, + vararg sets: Pair> +): Entity where T : Comparable { val mapOfSets = sets.associate { (name, set) -> Pair(name, ComparableSet(set)) } @@ -650,8 +690,10 @@ private fun ByteArray.toStringXodusPropertyStyle(addEOF: Boolean = false): Strin } private fun getBrokenXodusString(): String { - val rawBytesGottenFromAProdXodus = "82e197b0cf83c4a7e2b1a2ceb1c59fc4a7ceb96d20e1b9a8c4a7ceb1e1b8adeda080c4a700".hexToByteArray() - val originalBytesForXodus = rawBytesGottenFromAProdXodus.sliceArray(1 until rawBytesGottenFromAProdXodus.size) + val rawBytesGottenFromAProdXodus = + "82e197b0cf83c4a7e2b1a2ceb1c59fc4a7ceb96d20e1b9a8c4a7ceb1e1b8adeda080c4a700".hexToByteArray() + val originalBytesForXodus = + rawBytesGottenFromAProdXodus.sliceArray(1 until rawBytesGottenFromAProdXodus.size) return originalBytesForXodus.toStringXodusPropertyStyle() } diff --git a/query/src/test/kotlin/jetbrains/exodus/query/metadata/MigrateXodusToOrientDbSmokeTest.kt b/query/src/test/kotlin/jetbrains/exodus/query/metadata/MigrateXodusToOrientDbSmokeTest.kt index b472539dc..a4f4f19f2 100644 --- a/query/src/test/kotlin/jetbrains/exodus/query/metadata/MigrateXodusToOrientDbSmokeTest.kt +++ b/query/src/test/kotlin/jetbrains/exodus/query/metadata/MigrateXodusToOrientDbSmokeTest.kt @@ -15,11 +15,11 @@ */ package jetbrains.exodus.query.metadata -import com.orientechnologies.orient.core.config.OGlobalConfiguration -import com.orientechnologies.orient.core.db.ODatabaseType -import com.orientechnologies.orient.core.db.OrientDBConfig -import com.orientechnologies.orient.core.record.ODirection -import com.orientechnologies.orient.core.record.OVertex +import com.jetbrains.youtrack.db.api.DatabaseType +import com.jetbrains.youtrack.db.api.config.GlobalConfiguration +import com.jetbrains.youtrack.db.api.config.YouTrackDBConfig +import com.jetbrains.youtrack.db.api.record.Direction +import com.jetbrains.youtrack.db.api.record.Vertex import jetbrains.exodus.TestUtil import jetbrains.exodus.entitystore.PersistentEntityStoreImpl import jetbrains.exodus.entitystore.PersistentEntityStores @@ -41,14 +41,13 @@ class MigrateXodusToOrientDbSmokeTest { val username = "admin" val password = "password" val dbName = "testDB" - val url = "memory" // create the database - val builder = OrientDBConfig.builder() - builder.addConfig(OGlobalConfiguration.NON_TX_READS_WARNING_MODE, "SILENT") + val builder = YouTrackDBConfig.builder() + builder.addGlobalConfigurationParameter(GlobalConfiguration.NON_TX_READS_WARNING_MODE, "SILENT") val connectionConfig = ODatabaseConnectionConfig.builder() .withPassword(password) .withUserName(username) - .withDatabaseType(ODatabaseType.MEMORY) + .withDatabaseType(DatabaseType.MEMORY) .withDatabaseRoot("") .build() @@ -57,7 +56,7 @@ class MigrateXodusToOrientDbSmokeTest { .withDatabaseName("MEMORY") .build() - val db = initOrientDbServer(connectionConfig) + val db = iniYouTrackDb(connectionConfig) db.execute("create database $dbName MEMORY users ( $username identified by '$password' role admin )") // create a provider val dbProvider = ODatabaseProviderImpl(config, db) @@ -119,11 +118,11 @@ class MigrateXodusToOrientDbSmokeTest { val type2 = session.getClass("type2")!! assertTrue(type1.existsProperty("prop4")) - assertTrue(type1.existsProperty(OVertex.getEdgeLinkFieldName(ODirection.IN, "link1".asEdgeClass))) - assertTrue(type1.existsProperty(OVertex.getEdgeLinkFieldName(ODirection.OUT, "link1".asEdgeClass))) + assertTrue(type1.existsProperty(Vertex.getEdgeLinkFieldName(Direction.IN, "link1".asEdgeClass))) + assertTrue(type1.existsProperty(Vertex.getEdgeLinkFieldName(Direction.OUT, "link1".asEdgeClass))) assertTrue(type2.existsProperty("pop4")) - assertTrue(type2.existsProperty(OVertex.getEdgeLinkFieldName(ODirection.OUT, "link1".asEdgeClass))) + assertTrue(type2.existsProperty(Vertex.getEdgeLinkFieldName(Direction.OUT, "link1".asEdgeClass))) } } diff --git a/query/src/test/kotlin/jetbrains/exodus/query/metadata/MigrateYourDatabaseTest.kt b/query/src/test/kotlin/jetbrains/exodus/query/metadata/MigrateYourDatabaseTest.kt index c83f6542c..99ee4a716 100644 --- a/query/src/test/kotlin/jetbrains/exodus/query/metadata/MigrateYourDatabaseTest.kt +++ b/query/src/test/kotlin/jetbrains/exodus/query/metadata/MigrateYourDatabaseTest.kt @@ -15,11 +15,11 @@ */ package jetbrains.exodus.query.metadata -import com.orientechnologies.orient.core.db.ODatabaseType +import com.jetbrains.youtrack.db.api.DatabaseType import jetbrains.exodus.entitystore.orientdb.ODatabaseConfig import jetbrains.exodus.entitystore.orientdb.ODatabaseConnectionConfig import jetbrains.exodus.entitystore.orientdb.ODatabaseProviderImpl -import jetbrains.exodus.entitystore.orientdb.initOrientDbServer +import jetbrains.exodus.entitystore.orientdb.iniYouTrackDb import org.junit.Test import kotlin.test.Ignore @@ -44,14 +44,14 @@ class MigrateYourDatabaseTest { .withPassword("password") .withUserName("admin") .withDatabaseRoot("") - .withDatabaseType(ODatabaseType.MEMORY) + .withDatabaseType(DatabaseType.MEMORY) .build() val config = ODatabaseConfig.builder() .withDatabaseName("testDB") .build() - val db = initOrientDbServer(connectionConfig) + val db = iniYouTrackDb(connectionConfig) val provider = ODatabaseProviderImpl(config, db) val launcher = XodusToOrientDataMigratorLauncher( orient = MigrateToOrientConfig( diff --git a/query/src/test/kotlin/jetbrains/exodus/query/metadata/OModelMetaDataTest.kt b/query/src/test/kotlin/jetbrains/exodus/query/metadata/OModelMetaDataTest.kt index 977d84e47..a11f21c15 100644 --- a/query/src/test/kotlin/jetbrains/exodus/query/metadata/OModelMetaDataTest.kt +++ b/query/src/test/kotlin/jetbrains/exodus/query/metadata/OModelMetaDataTest.kt @@ -15,11 +15,10 @@ */ package jetbrains.exodus.query.metadata -import com.orientechnologies.orient.core.record.OVertex import jetbrains.exodus.entitystore.PersistentEntityId import jetbrains.exodus.entitystore.orientdb.* import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.linkTargetEntityIdPropertyName -import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryOrientDB +import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryYouTrackDB import jetbrains.exodus.entitystore.orientdb.testutil.OTestMixin import org.junit.Assert import org.junit.Assert.assertEquals @@ -32,25 +31,25 @@ import kotlin.test.assertTrue class OModelMetaDataTest : OTestMixin { @Rule @JvmField - val orientDbRule = InMemoryOrientDB(initializeIssueSchema = false) + val orientDbRule = InMemoryYouTrackDB(initializeIssueSchema = false) - override val orientDb = orientDbRule + override val youTrackDb = orientDbRule @Test fun `prepare() applies the schema to OrientDB`() { - val model = oModel(orientDb.provider) { + val model = oModel(youTrackDb.provider) { entity("type1") entity("type2") } - orientDb.withSession { session -> + youTrackDb.withSession { session -> Assert.assertNull(session.getClass("type1")) Assert.assertNull(session.getClass("type2")) } model.prepare() - orientDb.withSession { session -> + youTrackDb.withSession { session -> session.assertVertexClassExists("type1") session.assertVertexClassExists("type2") } @@ -58,13 +57,13 @@ class OModelMetaDataTest : OTestMixin { @Test fun `addAssociation() implicitly call prepare() and applies the schema to OrientDB`() { - oModel(orientDb.provider) { + oModel(youTrackDb.provider) { entity("type1") entity("type2") association("type2", "ass1", "type1", AssociationEndCardinality._1) } - orientDb.withSession { session -> + youTrackDb.withSession { session -> session.assertVertexClassExists("type1") session.assertVertexClassExists("type2") session.assertAssociationExists("type2", "type1", "ass1", AssociationEndCardinality._1) @@ -73,7 +72,7 @@ class OModelMetaDataTest : OTestMixin { @Test fun addAssociation() { - val model = oModel(orientDb.provider) { + val model = oModel(youTrackDb.provider) { entity("type1") entity("type2") } @@ -86,19 +85,19 @@ class OModelMetaDataTest : OTestMixin { null, false, false, false, false ) - orientDb.withSession { session -> + youTrackDb.withSession { session -> session.assertAssociationExists("type2", "type1", "ass1", AssociationEndCardinality._1) } } @Test fun `if there is an active session on the current thread, the model uses it`() { - val model = oModel(orientDb.provider) { + val model = oModel(youTrackDb.provider) { entity("type1") entity("type2") } - orientDb.provider.acquireSession().use { + youTrackDb.provider.acquireSession().use { model.prepare() model.addAssociation( "type2", "type1", AssociationType.Directed, "ass1", AssociationEndCardinality._1, @@ -111,12 +110,12 @@ class OModelMetaDataTest : OTestMixin { @Test fun `if there is an active transaction, throw an exception`() { - val model = oModel(orientDb.provider) { + val model = oModel(youTrackDb.provider) { entity("type1") entity("type2") } - orientDb.withSession { session -> + youTrackDb.withSession { session -> session.begin() assertFailsWith { model.prepare() @@ -148,21 +147,21 @@ class OModelMetaDataTest : OTestMixin { @Test fun removeAssociation() { - val model = oModel(orientDb.provider) { + val model = oModel(youTrackDb.provider) { entity("type1") entity("type2") association("type2", "ass1", "type1", AssociationEndCardinality._1) } model.removeAssociation("type2", "ass1") - orientDb.withSession { session -> + youTrackDb.withSession { session -> session.assertAssociationNotExist("type2", "type1", "ass1", requireEdgeClass = true) } } @Test fun `prepare() creates indices`() { - val model = oModel(orientDb.provider) { + val model = oModel(youTrackDb.provider) { entity("type1") { property("prop1", "int") property("prop2", "long") @@ -173,7 +172,7 @@ class OModelMetaDataTest : OTestMixin { model.prepare() - orientDb.withSession { session -> + youTrackDb.withSession { session -> session.checkIndex("type1", true, "prop1", "prop2") } } @@ -181,36 +180,36 @@ class OModelMetaDataTest : OTestMixin { @Test fun `prepare() initializes the classId map`() { val model = - oModel(orientDb.provider, OSchemaBuddyImpl(orientDb.provider, autoInitialize = false)) { + oModel(youTrackDb.provider, OSchemaBuddyImpl(youTrackDb.provider, autoInitialize = false)) { entity("type1") } // We have not yet called prepare() for the model, autoInitialize is disabled - orientDb.provider.acquireSession().use { + youTrackDb.provider.acquireSession().use { it.createVertexClassWithClassId("type1") } - val entityId = orientDb.withStoreTx { tx -> + val entityId = youTrackDb.withStoreTx { tx -> tx.newEntity("type1").id } val oldSchoolEntityId = PersistentEntityId(entityId.typeId, entityId.localId) // model does not find the id because internal data structures are not initialized yet - orientDb.withSession { + youTrackDb.withSession { assertEquals(ORIDEntityId.EMPTY_ID, model.getOEntityId(it, oldSchoolEntityId)) } // prepare() must initialize internal data structures in the end model.prepare() - orientDb.withSession { session -> + youTrackDb.withSession { session -> assertEquals(entityId, model.getOEntityId(session, oldSchoolEntityId)) } } @Test fun `addAssociation() initializes complementary properties for indexed links`() { - oModel(orientDb.provider, OSchemaBuddyImpl(orientDb.provider, autoInitialize = false)) { + oModel(youTrackDb.provider, OSchemaBuddyImpl(youTrackDb.provider, autoInitialize = false)) { entity("type2") entity("type1") association("type1", "ass1", "type2", AssociationEndCardinality._0_n) @@ -219,7 +218,7 @@ class OModelMetaDataTest : OTestMixin { // the schema is already initialized because addAssociation implicitly calls prepare() - val (id11, id12, id21) = orientDb.withTxSession { oSession -> + val (id11, id12, id21) = youTrackDb.withTxSession { oSession -> val v11 = oSession.createVertexAndSetLocalEntityId("type1") val v12 = oSession.createVertexAndSetLocalEntityId("type1") val v21 = oSession.createVertexAndSetLocalEntityId("type2") @@ -228,21 +227,21 @@ class OModelMetaDataTest : OTestMixin { v21.addEdge("ass2", v11) v21.addEdge("ass2", v12) - v11.save() - v12.save() - v21.save() + v11.save() + v12.save() + v21.save() Triple(v11.identity, v12.identity, v21.identity) } // links are not indexes, so there are no complementary properties - orientDb.withTxSession { session -> + youTrackDb.withTxSession { session -> val type1 = session.getClass("type1") val type2 = session.getClass("type2") assertFalse(type1.existsProperty(linkTargetEntityIdPropertyName("ass1"))) assertFalse(type2.existsProperty(linkTargetEntityIdPropertyName("ass2"))) } - oModel(orientDb.provider, OSchemaBuddyImpl(orientDb.provider, autoInitialize = false)) { + oModel(youTrackDb.provider, OSchemaBuddyImpl(youTrackDb.provider, autoInitialize = false)) { entity("type2") { index(IndexedField("ass2", isProperty = false)) } @@ -254,7 +253,7 @@ class OModelMetaDataTest : OTestMixin { } // prepare() must have called initializeComplementaryPropertiesForNewIndexedLinks - orientDb.withTxSession { session -> + youTrackDb.withTxSession { session -> val v11 = session.loadVertex(id11) val v12 = session.loadVertex(id12) val v21 = session.loadVertex(id21) @@ -275,13 +274,13 @@ class OModelMetaDataTest : OTestMixin { @Test fun `oModel creates the schema for links if it is absent`() { - val model = oModel(orientDb.provider) { + val model = oModel(youTrackDb.provider) { entity("type1") entity("type2") } // initialize the entity types model.prepare() - val store = OPersistentEntityStore(orientDb.provider, orientDb.dbName, model) + val store = OPersistentEntityStore(youTrackDb.provider, youTrackDb.dbName, model) // check that the links do not exist withSession { session -> @@ -328,13 +327,13 @@ class OModelMetaDataTest : OTestMixin { @Test fun `adding new link type does not cause OConcurrentModificationException`() { - val model = oModel(orientDb.provider) { + val model = oModel(youTrackDb.provider) { entity("type1") entity("type2") } // initialize the entity types model.prepare() - val store = OPersistentEntityStore(orientDb.provider, orientDb.dbName, model) + val store = OPersistentEntityStore(youTrackDb.provider, youTrackDb.dbName, model) // entity1 has already existed for a while val id1 = store.computeInTransaction { tx -> diff --git a/query/src/test/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializerTest.kt b/query/src/test/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializerTest.kt deleted file mode 100644 index 3b363276c..000000000 --- a/query/src/test/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializerTest.kt +++ /dev/null @@ -1,566 +0,0 @@ -/* - * Copyright ${inceptionYear} - ${year} ${owner} - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package jetbrains.exodus.query.metadata - -import com.orientechnologies.orient.core.metadata.schema.OProperty -import com.orientechnologies.orient.core.metadata.schema.OType -import com.orientechnologies.orient.core.record.OVertex -import com.orientechnologies.orient.core.storage.ORecordDuplicatedException -import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.LOCAL_ENTITY_ID_PROPERTY_NAME -import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.localEntityIdSequenceName -import jetbrains.exodus.entitystore.orientdb.requireClassId -import jetbrains.exodus.entitystore.orientdb.requireLocalEntityId -import jetbrains.exodus.entitystore.orientdb.setLocalEntityId -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 - -class OrientDbSchemaInitializerTest { - @Rule - @JvmField - val orientDb = InMemoryOrientDB(initializeIssueSchema = false) - - @Test - fun `create vertex-class for every entity`() = orientDb.provider.acquireSession().use { oSession -> - val model = model { - entity("type1") - entity("type2") - } - - oSession.applySchema(model) - - oSession.assertVertexClassExists("type1") - oSession.assertVertexClassExists("type2") - } - - @Test - fun `set super-classes`() = orientDb.provider.acquireSession().use { oSession -> - val model = model { - entity("type1") - entity("type2", "type1") - entity("type3", "type2") - } - - oSession.applySchema(model) - - oSession.assertHasSuperClass("type2", "type1") - oSession.assertHasSuperClass("type3", "type2") - } - - @Test - fun `simple properties of known types are created`() = orientDb.provider.acquireSession().use { oSession -> - val model = model { - entity("type1") { - for (type in supportedSimplePropertyTypes) { - property("prop$type", type) - property("requiredProp$type", type, required = true) - } - } - } - - oSession.applySchema(model) - - val oClass = oSession.getClass("type1")!! - for (type in supportedSimplePropertyTypes) { - val requiredProp = oClass.getProperty("requiredProp$type")!! - val prop = oClass.getProperty("prop$type")!! - - assertEquals(getOType(type), requiredProp.type) - assertEquals(getOType(type), prop.type) - - requiredProp.check(required = true, notNull = true) - prop.check(required = false, notNull = false) - } - } - - @Test - fun `simple properties of not-known types cause exception`(): Unit = orientDb.provider.acquireSession().use { oSession -> - val model = model { - entity("type1") { - property("prop1", "notSupportedType") - } - } - - assertFailsWith() { - oSession.applySchema(model) - } - } - - @Test - fun `SchemaBuddy impl can correctly initialize StringBlob and Blob`(){ - val model = model { - entity("type1") { - blobProperty("blob1") - stringBlobProperty("strBlob1") - } - } - orientDb.withSession { - it.applySchema(model) - } - orientDb.withSession { - orientDb.schemaBuddy.initialize(it) - } - - orientDb.withSession { session -> - val type = session.getClass("type1")!! - val prop1 = type.getProperty("blob1") - val prop2 = type.getProperty("strBlob1") - assertEquals(OType.LINK, prop1.type) - assertEquals(OType.LINK, prop2.type) - } - } - - @Test - fun `embedded set properties with supported types`() { - val indices = orientDb.provider.acquireSession().use { oSession -> - val model = model { - entity("type1") { - for (type in supportedSimplePropertyTypes) { - setProperty("setProp$type", type) - } - } - } - - val (indices, _) = oSession.applySchema(model) - - val oClass = oSession.getClass("type1")!! - for (type in supportedSimplePropertyTypes) { - val prop = oClass.getProperty("setProp$type")!! - assertEquals(OType.EMBEDDEDSET, prop.type) - assertEquals(getOType(type), prop.linkedType) - - indices.checkIndex("type1", unique = false, "setProp$type") - } - indices - } - - orientDb.provider.acquireSession().use { oSession -> - oSession.applyIndices(indices) - - for (type in supportedSimplePropertyTypes) { - oSession.checkIndex("type1", unique = false, "setProp$type") - } - } - } - - @Test - fun `embedded set properties with not-supported types cause exception`(): Unit = orientDb.provider.acquireSession().use { oSession -> - val model = model { - entity("type1") { - setProperty("setProp$type", "cavaBanga") - } - } - - assertFailsWith { - oSession.applySchema(model) - } - } - - @Test - fun `one-directional associations`(): Unit = orientDb.provider.acquireSession().use { oSession -> - val model = model { - entity("type1") - entity("type2") - for (cardinality in AssociationEndCardinality.entries) { - association("type2", "prop1$cardinality", "type1", cardinality) - } - } - - val result = oSession.applySchema(model) - oSession.initializeIndices(result) - - for (cardinality in AssociationEndCardinality.entries) { - oSession.assertAssociationExists("type2", "type1", "prop1$cardinality", cardinality) - } - } - - @Test - fun `two association with the same name to a single type`(): Unit = orientDb.provider.acquireSession().use { oSession -> - val model = model { - entity("type1") - entity("type2") - entity("type3") - association("type2", "link1", "type1", AssociationEndCardinality._0_n) - association("type3", "link1", "type1", AssociationEndCardinality._0_n) - } - - val result = oSession.applySchema(model) - oSession.initializeIndices(result) - - oSession.assertAssociationExists("type2", "type1", "link1", AssociationEndCardinality._0_n) - oSession.assertAssociationExists("type3", "type1", "link1", AssociationEndCardinality._0_n) - } - - @Test - fun `one-directional associations ignore cardinality`(): Unit = orientDb.provider.acquireSession().use { oSession -> - val model = model { - entity("type1") - entity("type2") - for (cardinality in AssociationEndCardinality.entries) { - association("type2", "prop1$cardinality", "type1", cardinality) - } - } - - val result = oSession.applySchema(model, applyLinkCardinality = false) - oSession.initializeIndices(result) - - for (cardinality in AssociationEndCardinality.entries) { - oSession.assertAssociationExists("type2", "type1", "prop1$cardinality", null) - } - } - - @Test - fun `two-directional associations`(): Unit = orientDb.provider.acquireSession().use { oSession -> - val model = model { - entity("type1") - entity("type2") - - for (cardinality1 in AssociationEndCardinality.entries) { - for (cardinality2 in AssociationEndCardinality.entries) { - twoDirectionalAssociation( - sourceEntity = "type1", - sourceName = "prop1${cardinality1}_${cardinality2}", - sourceCardinality = cardinality1, - targetEntity = "type2", - targetName = "prop2${cardinality2}_${cardinality1}", - targetCardinality = cardinality2 - ) - } - } - } - - val result = oSession.applySchema(model) - oSession.initializeIndices(result) - - for (cardinality1 in AssociationEndCardinality.entries) { - for (cardinality2 in AssociationEndCardinality.entries) { - oSession.assertAssociationExists("type1", "type2", "prop1${cardinality1}_${cardinality2}", cardinality1) - oSession.assertAssociationExists("type2", "type1", "prop2${cardinality2}_${cardinality1}", cardinality2) - } - } - } - - - @Test - fun `own indices`() { - val indices = orientDb.provider.acquireSession().use { oSession -> - val model = model { - entity("type1") { - property("prop1", "int") - property("prop2", "long") - property("prop3", "string") - property("prop4", "string") - - index("prop1", "prop2") - index("prop3") - } - } - - val (indices, _) = oSession.applySchema(model) - - indices.checkIndex("type1", unique = true, "prop1", "prop2") - indices.checkIndex("type1", unique = true, "prop3") - - val entity = oSession.getClass("type1")!! - // indices are not created right away, they are created after data migration - assertTrue(entity.indexes.isEmpty()) - - // indexed properties in Xodus are required and not-nullable - entity.getProperty("prop1").check(required = true, notNull = true) - entity.getProperty("prop2").check(required = true, notNull = true) - entity.getProperty("prop3").check(required = true, notNull = true) - entity.getProperty("prop4").check(required = false, notNull = false) - - indices - } - - orientDb.provider.acquireSession().use { oSession -> - oSession.applyIndices(indices) - - oSession.checkIndex("type1", true, "prop1", "prop2") - oSession.checkIndex("type1", true, "prop3") - } - } - - @Test - fun `unique index forbids to create vertices with the same property value`() { - val model = model { - entity("type1") { - property("prop1", "int") - property("prop2", "long") - index("prop1") - } - } - - orientDb.withSession { oSession -> - val (indices, _) = oSession.applySchema(model, indexForEverySimpleProperty = false) - oSession.applyIndices(indices) - } - - assertFailsWith { - orientDb.withTxSession { oSession -> - val oClass = oSession.getClass("type1")!! - val v1 = oSession.newVertex(oClass) - oSession.setLocalEntityId("type1", v1) - v1.requireLocalEntityId() - v1.setProperty("prop1", 3) - v1.setProperty("prop2", 4) - v1.save() - - val v2 = oSession.newVertex(oClass) - oSession.setLocalEntityId("type1", v2) - v2.setProperty("prop1", 3L) - v2.setProperty("prop2", 4L) - v2.save() - } - } - } - - @Test - fun `index for every simple property if required`() = orientDb.provider.acquireSession().use { oSession -> - val model = model { - entity("type1") { - property("prop1", "int") - property("prop2", "long") - property("prop3", "string") - property("prop4", "string") - } - } - - val (indices, _) = oSession.applySchema(model, indexForEverySimpleProperty = true) - - indices.checkIndex("type1", unique = false, "prop1") - indices.checkIndex("type1", unique = false, "prop2") - indices.checkIndex("type1", unique = false, "prop3") - indices.checkIndex("type1", unique = false, "prop4") - - val entity = oSession.getClass("type1")!! - // indices are not created right away, they are created after data migration - assertTrue(entity.indexes.isEmpty()) - } - - @Test - fun `no indices for simple properties by default`() = orientDb.provider.acquireSession().use { oSession -> - val model = model { - entity("type1") { - property("prop1", "int") - property("prop2", "long") - property("prop3", "string") - property("prop4", "string") - } - } - - val (indices, _) = oSession.applySchema(model) - assertTrue(indices.none { (indexName, _) -> indexName.contains("prop")}) - } - - @Test - fun `addAssociation, removeAssociation`(): Unit = orientDb.provider.acquireSession().use { session -> - val model = model { - entity("type1") - entity("type2") - } - - val result = session.applySchema(model) - session.initializeIndices(result) - - for (cardinality in AssociationEndCardinality.entries) { - val assResult = session.addAssociation( - LinkMetadata( - name = "ass1${cardinality.name}", - outClassName = "type1", - inClassName = "type2", - cardinality = cardinality - ), - listOf() - ) - session.initializeIndices(assResult) - } - - for (cardinality in AssociationEndCardinality.entries) { - session.assertAssociationExists("type1", "type2", "ass1${cardinality.name}", cardinality) - } - - for (cardinality in AssociationEndCardinality.entries) { - session.removeAssociation( - sourceClassName = "type1", - targetClassName = "type2", - associationName = "ass1${cardinality.name}" - ) - } - - for (cardinality in AssociationEndCardinality.entries) { - /* - * We do not delete the edge class when deleting an association because - * it (the edge class) may be used by other associations. - * - * Maybe it is possible to check an edge class if it is not used anywhere, but - * we do not do it at the moment. Maybe some day in the future. - * */ - session.assertAssociationNotExist("type1", "type2", "ass1${cardinality.name}", requireEdgeClass = true) - } - } - - - // Backward compatible EntityId - - @Test - fun `classId is a monotonically increasing long`(): Unit = orientDb.provider.acquireSession().use { oSession -> - val types = mutableListOf("type0", "type1", "type2") - val model = model { - for (type in types) { - entity(type) - } - } - - oSession.applySchema(model) - - val classIds = mutableSetOf() - val classIdToClassName = mutableMapOf() - for (type in types) { - val classId = oSession.getClass(type).requireClassId() - classIdToClassName[classId] = type - classIds.add(classId) - } - assertEquals(setOf(0, 1, 2), classIds) - - - // emulate the next run of the application with new classes in the codebase - types.add("type4") - types.add("type5") - val anotherModel = model { - for (type in types) { - entity(type) - } - } - - oSession.applySchema(anotherModel) - - classIds.clear() - for (type in types) { - val classId = oSession.getClass(type).requireClassId() - // classId is not changed if it has been already assigned - if (classId in classIdToClassName) { - assertEquals(classIdToClassName.getValue(classId), type) - } - classIds.add(classId) - } - assertEquals(setOf(0, 1, 2, 3, 4), classIds) - } - - - @Test - fun `search for boolean == false works by default`() { - val model = oModel(orientDb.provider) { - entity("type1") { - property("prop1", "boolean") - } - } - model.prepare() - orientDb.withTxSession { oSession -> - oSession.newVertex("type1").apply { - setProperty("prop1", true) - oSession.setLocalEntityId("type1", this) - save() - } - oSession.newVertex("type1").apply { - oSession.setLocalEntityId("type1", this) - save() - } - - } - orientDb.withTxSession { oSession -> - val updated = oSession.query("SELECT from type1 where prop1 = true").vertexStream().toList() - val default = oSession.query("SELECT from type1 where prop1 = false").vertexStream().toList() - val all = oSession.query("SELECT from type1").vertexStream().toList() - assertEquals(1, updated.size) - assertEquals(1, default.size) - assertEquals(2, all.size) - } - } - - @Test - fun `every class gets localEntityId property`(): Unit = orientDb.provider.acquireSession().use { oSession -> - val types = mutableListOf("type0", "type1", "type2") - val model = model { - for (type in types) { - entity(type) - } - } - - val (indices, _) = oSession.applySchema(model) - - val sequences = oSession.metadata.sequenceLibrary - for (type in types) { - assertNotNull(oSession.getClass(type).getProperty(LOCAL_ENTITY_ID_PROPERTY_NAME)) - // index for the localEntityId must be created regardless the indexForEverySimpleProperty param - indices.checkIndex(type, false, 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(0, sequence.next()) - } - - // emulate the next run of the application - oSession.applySchema(model) - - for (type in types) { - val sequence = sequences.getSequence(localEntityIdSequenceName(type)) - // sequences are the same - assertEquals(1, sequence.next()) - } - } - - private fun OProperty.check(required: Boolean, notNull: Boolean) { - assertEquals(required, isMandatory) - assertEquals(notNull, isNotNull) - } - - private val supportedSimplePropertyTypes: List = listOf( - "boolean", - "string", - "byte", "short", "int", "integer", "long", - "float", "double", - "datetime", - ) - - private fun getOType(jvmTypeName: String): OType { - return when (jvmTypeName.lowercase()) { - "boolean" -> OType.BOOLEAN - "string" -> OType.STRING - - "byte" -> OType.BYTE - "short" -> OType.SHORT - "int", - "integer" -> OType.INTEGER - "long" -> OType.LONG - - "float" -> OType.FLOAT - "double" -> OType.DOUBLE - - "datetime" -> OType.LONG - - else -> throw IllegalArgumentException("$jvmTypeName is not supported. Feel free to support it.") - } - } -} diff --git a/query/src/test/kotlin/jetbrains/exodus/query/metadata/SchemaTestUtils.kt b/query/src/test/kotlin/jetbrains/exodus/query/metadata/SchemaTestUtils.kt index a809a4db8..7f059676f 100644 --- a/query/src/test/kotlin/jetbrains/exodus/query/metadata/SchemaTestUtils.kt +++ b/query/src/test/kotlin/jetbrains/exodus/query/metadata/SchemaTestUtils.kt @@ -15,19 +15,19 @@ */ package jetbrains.exodus.query.metadata -import com.orientechnologies.orient.core.db.ODatabaseSession -import com.orientechnologies.orient.core.metadata.schema.OClass -import com.orientechnologies.orient.core.metadata.schema.OProperty -import com.orientechnologies.orient.core.metadata.schema.OType -import com.orientechnologies.orient.core.record.ODirection -import com.orientechnologies.orient.core.record.OEdge -import com.orientechnologies.orient.core.record.OVertex +import com.jetbrains.youtrack.db.api.DatabaseSession +import com.jetbrains.youtrack.db.api.record.Direction +import com.jetbrains.youtrack.db.api.record.Edge +import com.jetbrains.youtrack.db.api.record.Vertex +import com.jetbrains.youtrack.db.api.schema.Property +import com.jetbrains.youtrack.db.api.schema.SchemaClass +import com.jetbrains.youtrack.db.internal.core.metadata.schema.SchemaClassInternal import jetbrains.exodus.entitystore.orientdb.* import org.junit.Assert.* // assertions -internal fun ODatabaseSession.assertAssociationNotExist( +internal fun DatabaseSession.assertAssociationNotExist( outClassName: String, inClassName: String, edgeName: String, @@ -36,20 +36,20 @@ internal fun ODatabaseSession.assertAssociationNotExist( val edgeClassName = edgeName.asEdgeClass if (requireEdgeClass) { val edgeClass = requireEdgeClass(edgeClassName) - assertTrue(edgeClass.areIndexed(OEdge.DIRECTION_IN, OEdge.DIRECTION_OUT)) + assertTrue(edgeClass.areIndexed(this, Edge.DIRECTION_IN, Edge.DIRECTION_OUT)) } val inClass = getClass(inClassName)!! val outClass = getClass(outClassName)!! - val outPropName = OVertex.getEdgeLinkFieldName(ODirection.OUT, edgeClassName) + val outPropName = Vertex.getEdgeLinkFieldName(Direction.OUT, edgeClassName) assertNull(outClass.getProperty(outPropName)) - val inPropName = OVertex.getEdgeLinkFieldName(ODirection.IN, edgeClassName) + val inPropName = Vertex.getEdgeLinkFieldName(Direction.IN, edgeClassName) assertNull(inClass.getProperty(inPropName)) } -internal fun ODatabaseSession.assertAssociationExists( +internal fun DatabaseSession.assertAssociationExists( outClassName: String, inClassName: String, edgeName: String, @@ -60,21 +60,21 @@ internal fun ODatabaseSession.assertAssociationExists( val inClass = getClass(inClassName)!! val outClass = getClass(outClassName)!! - assertTrue(edgeClass.areIndexed(OEdge.DIRECTION_IN, OEdge.DIRECTION_OUT)) + assertTrue(edgeClass.areIndexed(this, Edge.DIRECTION_IN, Edge.DIRECTION_OUT)) if (cardinality != null) { - val outPropName = OVertex.getEdgeLinkFieldName(ODirection.OUT, edgeClassName) + val outPropName = Vertex.getEdgeLinkFieldName(Direction.OUT, edgeClassName) val directOutProp = outClass.getProperty(outPropName)!! - assertEquals(OType.LINKBAG, directOutProp.type) + assertEquals(com.jetbrains.youtrack.db.api.schema.PropertyType.LINKBAG, directOutProp.type) directOutProp.assertCardinality(cardinality) - val inPropName = OVertex.getEdgeLinkFieldName(ODirection.IN, edgeClassName) + val inPropName = Vertex.getEdgeLinkFieldName(Direction.IN, edgeClassName) val directInProp = inClass.getProperty(inPropName)!! - assertEquals(OType.LINKBAG, directInProp.type) + assertEquals(com.jetbrains.youtrack.db.api.schema.PropertyType.LINKBAG, directInProp.type) } } -private fun OProperty.assertCardinality(cardinality: AssociationEndCardinality) { +private fun Property.assertCardinality(cardinality: AssociationEndCardinality) { when (cardinality) { AssociationEndCardinality._0_1 -> { assertTrue(!this.isMandatory) @@ -102,24 +102,29 @@ private fun OProperty.assertCardinality(cardinality: AssociationEndCardinality) } } -internal fun ODatabaseSession.assertVertexClassExists(name: String) { +internal fun DatabaseSession.assertVertexClassExists(name: String) { assertHasSuperClass(name, "V") } -internal fun ODatabaseSession.requireEdgeClass(name: String): OClass { +internal fun DatabaseSession.requireEdgeClass(name: String): SchemaClass { val edge = getClass(name)!! assertTrue(edge.superClassesNames.contains("E")) return edge } -internal fun ODatabaseSession.assertHasSuperClass(className: String, superClassName: String) { +internal fun DatabaseSession.assertHasSuperClass(className: String, superClassName: String) { assertTrue(getClass(className)!!.superClassesNames.contains(superClassName)) } -internal fun ODatabaseSession.checkIndex(className: String, unique: Boolean, vararg fieldNames: String) { +internal fun DatabaseSession.checkIndex( + className: String, + unique: Boolean, + vararg fieldNames: String +) { val entity = getClass(className)!! val indexName = indexName(className, unique, *fieldNames) - val index = entity.indexes.first { it.name == indexName } + val index = + (entity as SchemaClassInternal).getIndexesInternal(this).first { it.name == indexName } assertEquals(unique, index.isUnique) assertEquals(fieldNames.size, index.definition.fields.size) @@ -198,7 +203,11 @@ internal fun EntityMetaDataImpl.index(vararg fields: IndexedField) { this.ownIndexes = this.ownIndexes + setOf(index) } -internal fun EntityMetaDataImpl.property(name: String, typeName: String, required: Boolean = false) { +internal fun EntityMetaDataImpl.property( + name: String, + typeName: String, + required: Boolean = false +) { // regardless of the name, this setter actually ADDS new properties to its internal collection this.propertiesMetaData = listOf(SimplePropertyMetaDataImpl(name, typeName)) if (required) { @@ -256,39 +265,39 @@ internal fun ModelMetaData.twoDirectionalAssociation( ) } -internal fun ODatabaseSession.createVertexAndSetLocalEntityId(className: String): OVertex { +internal fun DatabaseSession.createVertexAndSetLocalEntityId(className: String): Vertex { val v = newVertex(className) setLocalEntityId(className, v) - v.save() + v.save() return v } -internal fun OVertex.setPropertyAndSave(propName: String, value: Any) { +internal fun Vertex.setPropertyAndSave(propName: String, value: Any) { setProperty(propName, value) - save() + save() } -internal fun OVertex.addEdge(linkName: String, target: OVertex) { +internal fun Vertex.addEdge(linkName: String, target: Vertex) { val edgeClassName = OVertexEntity.edgeClassName(linkName) addEdge(target, edgeClassName) - save() - target.save() + save() + target.save() } -internal fun OVertex.addIndexedEdge(linkName: String, target: OVertex) { +internal fun Vertex.addIndexedEdge(linkName: String, target: Vertex) { val bag = getTargetLocalEntityIds(linkName) addEdge(target, OVertexEntity.edgeClassName(linkName)) bag.add(target.identity) setTargetLocalEntityIds(linkName, bag) - save() - target.save() + save() + target.save() } -internal fun OVertex.deleteIndexedEdge(linkName: String, target: OVertex) { +internal fun Vertex.deleteIndexedEdge(linkName: String, target: Vertex) { val bag = getTargetLocalEntityIds(linkName) - deleteEdge(target, OVertexEntity.edgeClassName(linkName)) + target.delete() bag.remove(target.identity) setTargetLocalEntityIds(linkName, bag) - save() - target.save() + save() + target.save() } diff --git a/query/src/test/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializerLinkIndicesTest.kt b/query/src/test/kotlin/jetbrains/exodus/query/metadata/YouTrackDbSchemaInitializerLinkIndicesTest.kt similarity index 93% rename from query/src/test/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializerLinkIndicesTest.kt rename to query/src/test/kotlin/jetbrains/exodus/query/metadata/YouTrackDbSchemaInitializerLinkIndicesTest.kt index b20433827..7a22022b0 100644 --- a/query/src/test/kotlin/jetbrains/exodus/query/metadata/OrientDbSchemaInitializerLinkIndicesTest.kt +++ b/query/src/test/kotlin/jetbrains/exodus/query/metadata/YouTrackDbSchemaInitializerLinkIndicesTest.kt @@ -15,25 +15,24 @@ */ package jetbrains.exodus.query.metadata -import com.orientechnologies.orient.core.record.ODirection -import com.orientechnologies.orient.core.record.OVertex -import com.orientechnologies.orient.core.storage.ORecordDuplicatedException +import com.jetbrains.youtrack.db.api.exception.RecordDuplicatedException +import com.jetbrains.youtrack.db.api.record.Direction +import com.jetbrains.youtrack.db.api.record.Vertex import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.edgeClassName import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.linkTargetEntityIdPropertyName import jetbrains.exodus.entitystore.orientdb.getTargetLocalEntityIds -import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryOrientDB +import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryYouTrackDB import org.junit.Assert.assertEquals import org.junit.Rule import org.junit.Test -import kotlin.streams.toList import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertTrue -class OrientDbSchemaInitializerLinkIndicesTest { +class YouTrackDbSchemaInitializerLinkIndicesTest { @Rule @JvmField - val orientDb = InMemoryOrientDB(initializeIssueSchema = false) + val orientDb = InMemoryYouTrackDB(initializeIssueSchema = false) @Test fun `the same DeferredIndices are equal`() { @@ -163,7 +162,7 @@ class OrientDbSchemaInitializerLinkIndicesTest { } // (no links) == (no links) - assertFailsWith { + assertFailsWith { orientDb.withTxSession { oSession -> oSession.createVertexAndSetLocalEntityId("type1") oSession.createVertexAndSetLocalEntityId("type1") @@ -182,7 +181,7 @@ class OrientDbSchemaInitializerLinkIndicesTest { } // ({ v3 }) == ({ v3 }) - assertFailsWith { + assertFailsWith { orientDb.withTxSession { oSession -> val v1 = oSession.loadVertex(id1) val v3 = oSession.loadVertex(id3) @@ -192,7 +191,7 @@ class OrientDbSchemaInitializerLinkIndicesTest { } // ({ v2, v3 }) == ({ v3 }) - assertFailsWith { + assertFailsWith { orientDb.withTxSession { oSession -> val v1 = oSession.loadVertex(id1) val v2 = oSession.loadVertex(id2) @@ -209,8 +208,8 @@ class OrientDbSchemaInitializerLinkIndicesTest { val v2 = oSession.loadVertex(id2) val v3 = oSession.loadVertex(id3) - v1.deleteIndexedEdge("ass1", v3) v2.addIndexedEdge("ass1", v3) + v1.deleteIndexedEdge("ass1", v3) } } @@ -234,7 +233,7 @@ class OrientDbSchemaInitializerLinkIndicesTest { } // (1, no links) == (1, no links) - assertFailsWith { + assertFailsWith { orientDb.withTxSession { oSession -> val v1 = oSession.createVertexAndSetLocalEntityId("type1") val v2 = oSession.createVertexAndSetLocalEntityId("type1") @@ -245,7 +244,7 @@ class OrientDbSchemaInitializerLinkIndicesTest { } // (1, { v3 }) == (1, { v3 }), trying to set in the same transaction - assertFailsWith { + assertFailsWith { orientDb.withTxSession { oSession -> val v1 = oSession.createVertexAndSetLocalEntityId("type1") val v2 = oSession.createVertexAndSetLocalEntityId("type1") @@ -274,7 +273,7 @@ class OrientDbSchemaInitializerLinkIndicesTest { } // (1, { v3 } ) == (1, { v3 } ) - assertFailsWith { + assertFailsWith { orientDb.withTxSession { oSession -> val v2 = oSession.loadVertex(id2) val v3 = oSession.loadVertex(id3) @@ -292,7 +291,7 @@ class OrientDbSchemaInitializerLinkIndicesTest { } // (1, { v2, v3 } ) == (1, { v3 } ), who could think... - assertFailsWith { + assertFailsWith { orientDb.withTxSession { oSession -> val v2 = oSession.loadVertex(id2) val v3 = oSession.loadVertex(id3) @@ -340,8 +339,8 @@ class OrientDbSchemaInitializerLinkIndicesTest { val v2 = oSession.loadVertex(id2) val v3 = oSession.loadVertex(id3) - v1.deleteIndexedEdge("ass1", v3) v2.addIndexedEdge("ass1", v3) + v1.deleteIndexedEdge("ass1", v3) } } @@ -378,7 +377,7 @@ class OrientDbSchemaInitializerLinkIndicesTest { } // (1, { v3 } ) == (1, { v3 } ) - assertFailsWith { + assertFailsWith { orientDb.withStoreTx { tx -> val e2 = tx.getEntity(id2) val e3 = tx.getEntity(id3) @@ -424,8 +423,9 @@ class OrientDbSchemaInitializerLinkIndicesTest { orientDb.withTxSession { oSession -> val v1 = - oSession.query("select from type1").vertexStream().toList().first { it.getProperty("prop1") == 1 } - val links: MutableIterable = v1.getVertices(ODirection.OUT, edgeClassName) + oSession.query("select from type1").vertexStream().toList() + .first { it.getProperty("prop1") == 1 } + val links: MutableIterable = v1.getVertices(Direction.OUT, edgeClassName) assertEquals(2, links.count()) } } @@ -468,7 +468,7 @@ class OrientDbSchemaInitializerLinkIndicesTest { val v1 = oSession.query("select from type1").vertexStream().toList() .first { it.getProperty("prop1") == 1 } - val links: MutableIterable = v1.getVertices(ODirection.OUT, edgeClassName) + val links: MutableIterable = v1.getVertices(Direction.OUT, edgeClassName) assertEquals(1, links.count()) } } diff --git a/query/src/test/kotlin/jetbrains/exodus/query/metadata/YouTrackDbSchemaInitializerTest.kt b/query/src/test/kotlin/jetbrains/exodus/query/metadata/YouTrackDbSchemaInitializerTest.kt new file mode 100644 index 000000000..db0b56e23 --- /dev/null +++ b/query/src/test/kotlin/jetbrains/exodus/query/metadata/YouTrackDbSchemaInitializerTest.kt @@ -0,0 +1,612 @@ +/* + * Copyright ${inceptionYear} - ${year} ${owner} + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package jetbrains.exodus.query.metadata + +import com.jetbrains.youtrack.db.api.exception.RecordDuplicatedException +import com.jetbrains.youtrack.db.api.schema.Property +import com.jetbrains.youtrack.db.api.schema.PropertyType +import com.jetbrains.youtrack.db.internal.core.db.DatabaseSessionInternal +import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.LOCAL_ENTITY_ID_PROPERTY_NAME +import jetbrains.exodus.entitystore.orientdb.OVertexEntity.Companion.localEntityIdSequenceName +import jetbrains.exodus.entitystore.orientdb.requireClassId +import jetbrains.exodus.entitystore.orientdb.requireLocalEntityId +import jetbrains.exodus.entitystore.orientdb.setLocalEntityId +import jetbrains.exodus.entitystore.orientdb.testutil.InMemoryYouTrackDB +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 + +class YouTrackDbSchemaInitializerTest { + @Rule + @JvmField + val orientDb = InMemoryYouTrackDB(initializeIssueSchema = false) + + @Test + fun `create vertex-class for every entity`() = + orientDb.provider.acquireSession().use { oSession -> + val model = model { + entity("type1") + entity("type2") + } + + oSession.applySchema(model) + + oSession.assertVertexClassExists("type1") + oSession.assertVertexClassExists("type2") + } + + @Test + fun `set super-classes`() = orientDb.provider.acquireSession().use { oSession -> + val model = model { + entity("type1") + entity("type2", "type1") + entity("type3", "type2") + } + + oSession.applySchema(model) + + oSession.assertHasSuperClass("type2", "type1") + oSession.assertHasSuperClass("type3", "type2") + } + + @Test + fun `simple properties of known types are created`() = + orientDb.provider.acquireSession().use { oSession -> + val model = model { + entity("type1") { + for (type in supportedSimplePropertyTypes) { + property("prop$type", type) + property("requiredProp$type", type, required = true) + } + } + } + + oSession.applySchema(model) + + val oClass = oSession.getClass("type1")!! + for (type in supportedSimplePropertyTypes) { + val requiredProp = oClass.getProperty("requiredProp$type")!! + val prop = oClass.getProperty("prop$type")!! + + assertEquals(getOType(type), requiredProp.type) + assertEquals(getOType(type), prop.type) + + requiredProp.check(required = true, notNull = true) + prop.check(required = false, notNull = false) + } + } + + @Test + fun `simple properties of not-known types cause exception`(): Unit = + orientDb.provider.acquireSession().use { oSession -> + val model = model { + entity("type1") { + property("prop1", "notSupportedType") + } + } + + assertFailsWith() { + oSession.applySchema(model) + } + } + + @Test + fun `SchemaBuddy impl can correctly initialize StringBlob and Blob`() { + val model = model { + entity("type1") { + blobProperty("blob1") + stringBlobProperty("strBlob1") + } + } + orientDb.withSession { + it.applySchema(model) + } + orientDb.withSession { + orientDb.schemaBuddy.initialize(it) + } + + orientDb.withSession { session -> + val type = session.getClass("type1")!! + val prop1 = type.getProperty("blob1") + val prop2 = type.getProperty("strBlob1") + assertEquals(PropertyType.LINK, prop1.type) + assertEquals(PropertyType.LINK, prop2.type) + } + } + + @Test + fun `embedded set properties with supported types`() { + val indices = orientDb.provider.acquireSession().use { oSession -> + val model = model { + entity("type1") { + for (type in supportedSimplePropertyTypes) { + setProperty("setProp$type", type) + } + } + } + + val (indices, _) = oSession.applySchema(model) + + val oClass = oSession.getClass("type1")!! + for (type in supportedSimplePropertyTypes) { + val prop = oClass.getProperty("setProp$type")!! + assertEquals(PropertyType.EMBEDDEDSET, prop.type) + assertEquals(getOType(type), prop.linkedType) + + indices.checkIndex("type1", unique = false, "setProp$type") + } + indices + } + + orientDb.provider.acquireSession().use { oSession -> + oSession.applyIndices(indices) + + for (type in supportedSimplePropertyTypes) { + oSession.checkIndex("type1", unique = false, "setProp$type") + } + } + } + + @Test + fun `embedded set properties with not-supported types cause exception`(): Unit = + orientDb.provider.acquireSession().use { oSession -> + val model = model { + entity("type1") { + setProperty("setProp$type", "cavaBanga") + } + } + + assertFailsWith { + oSession.applySchema(model) + } + } + + @Test + fun `one-directional associations`(): Unit = + orientDb.provider.acquireSession().use { oSession -> + val model = model { + entity("type1") + entity("type2") + for (cardinality in AssociationEndCardinality.entries) { + association("type2", "prop1$cardinality", "type1", cardinality) + } + } + + val result = oSession.applySchema(model) + oSession.initializeIndices(result) + + for (cardinality in AssociationEndCardinality.entries) { + oSession.assertAssociationExists("type2", "type1", "prop1$cardinality", cardinality) + } + } + + @Test + fun `two association with the same name to a single type`(): Unit = + orientDb.provider.acquireSession().use { oSession -> + val model = model { + entity("type1") + entity("type2") + entity("type3") + association("type2", "link1", "type1", AssociationEndCardinality._0_n) + association("type3", "link1", "type1", AssociationEndCardinality._0_n) + } + + val result = oSession.applySchema(model) + oSession.initializeIndices(result) + + oSession.assertAssociationExists( + "type2", + "type1", + "link1", + AssociationEndCardinality._0_n + ) + oSession.assertAssociationExists( + "type3", + "type1", + "link1", + AssociationEndCardinality._0_n + ) + } + + @Test + fun `one-directional associations ignore cardinality`(): Unit = + orientDb.provider.acquireSession().use { oSession -> + val model = model { + entity("type1") + entity("type2") + for (cardinality in AssociationEndCardinality.entries) { + association("type2", "prop1$cardinality", "type1", cardinality) + } + } + + val result = oSession.applySchema(model, applyLinkCardinality = false) + oSession.initializeIndices(result) + + for (cardinality in AssociationEndCardinality.entries) { + oSession.assertAssociationExists("type2", "type1", "prop1$cardinality", null) + } + } + + @Test + fun `two-directional associations`(): Unit = + orientDb.provider.acquireSession().use { oSession -> + val model = model { + entity("type1") + entity("type2") + + for (cardinality1 in AssociationEndCardinality.entries) { + for (cardinality2 in AssociationEndCardinality.entries) { + twoDirectionalAssociation( + sourceEntity = "type1", + sourceName = "prop1${cardinality1}_${cardinality2}", + sourceCardinality = cardinality1, + targetEntity = "type2", + targetName = "prop2${cardinality2}_${cardinality1}", + targetCardinality = cardinality2 + ) + } + } + } + + val result = oSession.applySchema(model) + oSession.initializeIndices(result) + + for (cardinality1 in AssociationEndCardinality.entries) { + for (cardinality2 in AssociationEndCardinality.entries) { + oSession.assertAssociationExists( + "type1", + "type2", + "prop1${cardinality1}_${cardinality2}", + cardinality1 + ) + oSession.assertAssociationExists( + "type2", + "type1", + "prop2${cardinality2}_${cardinality1}", + cardinality2 + ) + } + } + } + + + @Test + fun `own indices`() { + val indices = orientDb.provider.acquireSession().use { oSession -> + val model = model { + entity("type1") { + property("prop1", "int") + property("prop2", "long") + property("prop3", "string") + property("prop4", "string") + + index("prop1", "prop2") + index("prop3") + } + } + + val (indices, _) = oSession.applySchema(model) + + indices.checkIndex("type1", unique = true, "prop1", "prop2") + indices.checkIndex("type1", unique = true, "prop3") + + val entity = oSession.getClass("type1")!! + // indices are not created right away, they are created after data migration + assertTrue(entity.getIndexes(oSession).isEmpty()) + + // indexed properties in Xodus are required and not-nullable + entity.getProperty("prop1").check(required = true, notNull = true) + entity.getProperty("prop2").check(required = true, notNull = true) + entity.getProperty("prop3").check(required = true, notNull = true) + entity.getProperty("prop4").check(required = false, notNull = false) + + indices + } + + orientDb.provider.acquireSession().use { oSession -> + oSession.applyIndices(indices) + + oSession.checkIndex("type1", true, "prop1", "prop2") + oSession.checkIndex("type1", true, "prop3") + } + } + + @Test + fun `unique index forbids to create vertices with the same property value`() { + val model = model { + entity("type1") { + property("prop1", "int") + property("prop2", "long") + index("prop1") + } + } + + orientDb.withSession { oSession -> + val (indices, _) = oSession.applySchema(model, indexForEverySimpleProperty = false) + oSession.applyIndices(indices) + } + + assertFailsWith { + orientDb.withTxSession { oSession -> + val oClass = oSession.getClass("type1")!! + val v1 = oSession.newVertex(oClass) + oSession.setLocalEntityId("type1", v1) + v1.requireLocalEntityId() + v1.setProperty("prop1", 3) + v1.setProperty("prop2", 4) + v1.save() + + val v2 = oSession.newVertex(oClass) + oSession.setLocalEntityId("type1", v2) + v2.setProperty("prop1", 3L) + v2.setProperty("prop2", 4L) + v2.save() + } + } + } + + @Test + fun `index for every simple property if required`() = + orientDb.provider.acquireSession().use { oSession -> + val model = model { + entity("type1") { + property("prop1", "int") + property("prop2", "long") + property("prop3", "string") + property("prop4", "string") + } + } + + val (indices, _) = oSession.applySchema(model, indexForEverySimpleProperty = true) + + indices.checkIndex("type1", unique = false, "prop1") + indices.checkIndex("type1", unique = false, "prop2") + indices.checkIndex("type1", unique = false, "prop3") + indices.checkIndex("type1", unique = false, "prop4") + + val entity = oSession.getClass("type1")!! + // indices are not created right away, they are created after data migration + assertTrue(entity.getIndexes(oSession).isEmpty()) + } + + @Test + fun `no indices for simple properties by default`() = + orientDb.provider.acquireSession().use { oSession -> + val model = model { + entity("type1") { + property("prop1", "int") + property("prop2", "long") + property("prop3", "string") + property("prop4", "string") + } + } + + val (indices, _) = oSession.applySchema(model) + assertTrue(indices.none { (indexName, _) -> indexName.contains("prop") }) + } + + @Test + fun `addAssociation, removeAssociation`(): Unit = + orientDb.provider.acquireSession().use { session -> + val model = model { + entity("type1") + entity("type2") + } + + val result = session.applySchema(model) + session.initializeIndices(result) + + for (cardinality in AssociationEndCardinality.entries) { + val assResult = session.addAssociation( + LinkMetadata( + name = "ass1${cardinality.name}", + outClassName = "type1", + inClassName = "type2", + cardinality = cardinality + ), + listOf() + ) + session.initializeIndices(assResult) + } + + for (cardinality in AssociationEndCardinality.entries) { + session.assertAssociationExists( + "type1", + "type2", + "ass1${cardinality.name}", + cardinality + ) + } + + for (cardinality in AssociationEndCardinality.entries) { + session.removeAssociation( + sourceClassName = "type1", + targetClassName = "type2", + associationName = "ass1${cardinality.name}" + ) + } + + for (cardinality in AssociationEndCardinality.entries) { + /* + * We do not delete the edge class when deleting an association because + * it (the edge class) may be used by other associations. + * + * Maybe it is possible to check an edge class if it is not used anywhere, but + * we do not do it at the moment. Maybe some day in the future. + * */ + session.assertAssociationNotExist( + "type1", + "type2", + "ass1${cardinality.name}", + requireEdgeClass = true + ) + } + } + + + // Backward compatible EntityId + + @Test + fun `classId is a monotonically increasing long`(): Unit = + orientDb.provider.acquireSession().use { oSession -> + val types = mutableListOf("type0", "type1", "type2") + val model = model { + for (type in types) { + entity(type) + } + } + + oSession.applySchema(model) + + val classIds = mutableSetOf() + val classIdToClassName = mutableMapOf() + for (type in types) { + val classId = oSession.getClass(type).requireClassId() + classIdToClassName[classId] = type + classIds.add(classId) + } + assertEquals(setOf(0, 1, 2), classIds) + + + // emulate the next run of the application with new classes in the codebase + types.add("type4") + types.add("type5") + val anotherModel = model { + for (type in types) { + entity(type) + } + } + + oSession.applySchema(anotherModel) + + classIds.clear() + for (type in types) { + val classId = oSession.getClass(type).requireClassId() + // classId is not changed if it has been already assigned + if (classId in classIdToClassName) { + assertEquals(classIdToClassName.getValue(classId), type) + } + classIds.add(classId) + } + assertEquals(setOf(0, 1, 2, 3, 4), classIds) + } + + + @Test + fun `search for boolean == false works by default`() { + val model = oModel(orientDb.provider) { + entity("type1") { + property("prop1", "boolean") + } + } + model.prepare() + orientDb.withTxSession { oSession -> + oSession.newVertex("type1").apply { + setProperty("prop1", true) + oSession.setLocalEntityId("type1", this) + save() + } + oSession.newVertex("type1").apply { + oSession.setLocalEntityId("type1", this) + save() + } + + } + orientDb.withTxSession { oSession -> + val updated = + oSession.query("SELECT from type1 where prop1 = true").vertexStream().toList() + val default = + oSession.query("SELECT from type1 where prop1 = false").vertexStream().toList() + val all = oSession.query("SELECT from type1").vertexStream().toList() + assertEquals(1, updated.size) + assertEquals(1, default.size) + assertEquals(2, all.size) + } + } + + @Test + fun `every class gets localEntityId property`(): Unit = + orientDb.provider.acquireSession().use { oSession -> + val types = mutableListOf("type0", "type1", "type2") + val model = model { + for (type in types) { + entity(type) + } + } + + val (indices, _) = oSession.applySchema(model) + + val sequences = (oSession as DatabaseSessionInternal).metadata.sequenceLibrary + for (type in types) { + assertNotNull(oSession.getClass(type).getProperty(LOCAL_ENTITY_ID_PROPERTY_NAME)) + // index for the localEntityId must be created regardless the indexForEverySimpleProperty param + indices.checkIndex(type, false, 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(0, sequence.next()) + } + + // emulate the next run of the application + oSession.applySchema(model) + + for (type in types) { + val sequence = sequences.getSequence(localEntityIdSequenceName(type)) + // sequences are the same + assertEquals(1, sequence.next()) + } + } + + private fun Property.check(required: Boolean, notNull: Boolean) { + assertEquals(required, isMandatory) + assertEquals(notNull, isNotNull) + } + + private val supportedSimplePropertyTypes: List = listOf( + "boolean", + "string", + "byte", "short", "int", "integer", "long", + "float", "double", + "datetime", + ) + + private fun getOType(jvmTypeName: String): PropertyType { + return when (jvmTypeName.lowercase()) { + "boolean" -> PropertyType.BOOLEAN + "string" -> PropertyType.STRING + + "byte" -> PropertyType.BYTE + "short" -> PropertyType.SHORT + "int", + "integer" -> PropertyType.INTEGER + + "long" -> PropertyType.LONG + + "float" -> PropertyType.FLOAT + "double" -> PropertyType.DOUBLE + + "datetime" -> PropertyType.LONG + + else -> throw IllegalArgumentException("$jvmTypeName is not supported. Feel free to support it.") + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 986e0559f..9503206be 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,7 +1,7 @@ pluginManagement { plugins { - id("org.jetbrains.kotlin.jvm") version ("1.9.21") - id("org.jetbrains.dokka") version ("1.8.10") + id("org.jetbrains.kotlin.jvm") version ("2.1.0") + id("org.jetbrains.dokka") version ("2.0.0") id("com.github.hierynomus.license") version ("0.16.1") id("io.codearte.nexus-staging") version ("0.30.0") id("com.github.johnrengelman.shadow") version ("8.1.1") @@ -19,8 +19,8 @@ pluginManagement { dependencyResolutionManagement { versionCatalogs { create("libs") { - version("kotlin-lang", "1.9") - version("kotlin", "1.9.21") + version("kotlin-lang", "2.1") + version("kotlin", "2.1.0") version("kotlin-logging", "3.0.5") version("lz4", "1.8.0")