diff --git a/gradle/diktat.yml b/gradle/diktat.yml
index 3fdb54cb..625c70e9 100644
--- a/gradle/diktat.yml
+++ b/gradle/diktat.yml
@@ -17,4 +17,8 @@
- name: TOO_MANY_PARAMETERS
enabled: true
configuration:
- maxParameterListSize: '10'
\ No newline at end of file
+ maxParameterListSize: '10'
+- name: FILE_NAME_INCORRECT
+ enabled: false
+- name: LOCAL_VARIABLE_EARLY_DECLARATION
+ enabled: false
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e2f41574..b05d973f 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -13,8 +13,8 @@ jmh-plugin="0.7.2"
diktat-plugin="1.0.1"
jmhreport-plugin="0.9.0"
# Library dependencies
-kotlin-stdlib="1.9.22"
-kotlinx-coroutines="1.7.3"
+kotlin-stdlib="1.9.24"
+kotlinx-coroutines="1.8.0"
jupiter="5.10.2"
mockito="5.11.0"
mockito-kotlin="5.3.1"
@@ -27,6 +27,8 @@ video-recorder="2.0"
gson="2.10.1"
mongodb-driver="5.1.0"
owasp-encoder="1.2.3"
+testContainers="1.19.8"
+intellij-database="241.15989.150"
[plugins]
intellij={ id="org.jetbrains.intellij", version.ref="intellij-plugin" }
@@ -45,6 +47,14 @@ kotlin-coroutines-core={ group="org.jetbrains.kotlinx", name="kotlinx-coroutines
kotlin-coroutines-swing={ group="org.jetbrains.kotlinx", name="kotlinx-coroutines-swing", version.ref="kotlinx-coroutines" }
kotlin-coroutines-test={ group="org.jetbrains.kotlinx", name="kotlinx-coroutines-test", version.ref="kotlinx-coroutines" }
######################################################
+## IntelliJ compileOnly libraries. They must not be bundled because they are already part of the
+## JetBrains ecosystem.
+intellij-database-sql={ group="com.jetbrains.intellij.database", name="database-sql", version.ref="intellij-database" }
+intellij-database-connectivity={ group="com.jetbrains.intellij.database", name="database-connectivity", version.ref="intellij-database" }
+intellij-database-jdbc-console={ group="com.jetbrains.intellij.database", name="database-jdbc-console", version.ref="intellij-database" }
+intellij-database-core-base={ group="com.jetbrains.intellij.database", name="database", version.ref="intellij-database" }
+intellij-database-core-impl={ group="com.jetbrains.intellij.database", name="database-core-impl", version.ref="intellij-database" }
+######################################################
## Production Libraries.
segment={ group="com.segment.analytics.java", name="analytics", version.ref="segment" }
gson={ group="com.google.code.gson", name="gson", version.ref="gson" }
@@ -62,13 +72,16 @@ testing-remoteRobot={ group="com.intellij.remoterobot", name="remote-robot", ver
testing-remoteRobotDeps-remoteFixtures={ group="com.intellij.remoterobot", name="remote-fixtures", version.ref="intellij-remoteRobot"}
testing-remoteRobotDeps-ideLauncher={ group="com.intellij.remoterobot", name="ide-launcher", version.ref="intellij-remoteRobot"}
testing-remoteRobotDeps-okHttp={ group="com.squareup.okhttp3", name="okhttp", version.ref="okHttp" }
-testing-intellij-ideImpl={ group="com.jetbrains.intellij.platform", name="ide-impl", version.ref="intellij-testBuild" }
+testing-intellij-ideImpl={ group="com.jetbrains.intellij.platform", name="ide", version.ref="intellij-testBuild" }
testing-intellij-coreUi={ group="com.jetbrains.intellij.platform", name="core-ui", version.ref="intellij-testBuild" }
testing-remoteRobotDeps-retrofit={ group="com.squareup.retrofit2", name="retrofit", version.ref="retrofit" }
testing-remoteRobotDeps-retrofitGson={ group="com.squareup.retrofit2", name="converter-gson", version.ref="retrofit" }
testing-jmh-core={ group="org.openjdk.jmh", name="jmh-core", version.ref="jmh" }
testing-jmh-annotationProcessor={ group="org.openjdk.jmh", name="jmh-generator-annprocess", version.ref="jmh" }
testing-jmh-generatorByteCode={ group="org.openjdk.jmh", name="jmh-generator-bytecode", version.ref="jmh" }
+testing-testContainers-core= { group="org.testcontainers", name="testcontainers", version.ref="testContainers" }
+testing-testContainers-mongodb= { group="org.testcontainers", name="mongodb", version.ref="testContainers" }
+testing-testContainers-jupiter= { group="org.testcontainers", name="junit-jupiter", version.ref="testContainers"}
######################################################
## Libraries and plugins only used for the buildScript.
buildScript-plugin-kotlin={ group="org.jetbrains.kotlin", name="kotlin-gradle-plugin", version="1.9.23" }
diff --git a/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/observability/actions/GetMongoDBVersionAction.kt b/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/actions/GetMongoDBVersionAction.kt
similarity index 68%
rename from packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/observability/actions/GetMongoDBVersionAction.kt
rename to packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/actions/GetMongoDBVersionAction.kt
index 25d5eb4b..c08b4f91 100644
--- a/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/observability/actions/GetMongoDBVersionAction.kt
+++ b/packages/jetbrains-plugin/src/main/kotlin/com/mongodb/jbplugin/actions/GetMongoDBVersionAction.kt
@@ -1,4 +1,9 @@
-package com.mongodb.jbplugin.observability.actions
+/**
+ * A Simple, example action, that prints out in a modal popup the version of the
+ * connected MongoDB Cluster.
+ */
+
+package com.mongodb.jbplugin.actions
import com.intellij.database.dataSource.localDataSource
import com.intellij.database.psi.DbDataSource
@@ -9,19 +14,24 @@ import com.intellij.openapi.components.Service
import com.intellij.openapi.rd.util.launchChildOnUi
import com.intellij.openapi.ui.Messages
import com.mongodb.jbplugin.accessadapter.datagrip.DataGripBasedReadModelProvider
-import com.mongodb.jbplugin.accessadapter.slice.BuildInfoSlice
+import com.mongodb.jbplugin.accessadapter.slice.BuildInfo
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
+/**
+ * Service that implements the action.
+ *
+ * @param coroutineScope
+ */
@Service(Service.Level.PROJECT)
-class GetMongoDBVersionActionService(
+class GetMongoDbVersionActionService(
private val coroutineScope: CoroutineScope
) {
fun actionPerformed(event: AnActionEvent) {
coroutineScope.launch {
val readModelProvider = event.project!!.getService(DataGripBasedReadModelProvider::class.java)
val dataSource = event.dataContext.getData(PlatformDataKeys.PSI_ELEMENT) as DbDataSource
- val buildInfo = readModelProvider.slice(dataSource.localDataSource!!, BuildInfoSlice)
+ val buildInfo = readModelProvider.slice(dataSource.localDataSource!!, BuildInfo.Slice)
coroutineScope.launchChildOnUi {
Messages.showMessageDialog(buildInfo.version, "Show DB Version", null)
@@ -30,8 +40,11 @@ class GetMongoDBVersionActionService(
}
}
-class GetMongoDBVersionAction: AnAction() {
+/**
+ * Action that can be run within the contextual menu of a connection in the data explorer.
+ */
+class GetMongoDbVersionAction : AnAction() {
override fun actionPerformed(event: AnActionEvent) {
- event.project!!.getService(GetMongoDBVersionActionService::class.java).actionPerformed(event)
+ event.project!!.getService(GetMongoDbVersionActionService::class.java).actionPerformed(event)
}
}
\ No newline at end of file
diff --git a/packages/jetbrains-plugin/src/main/resources/META-INF/plugin.xml b/packages/jetbrains-plugin/src/main/resources/META-INF/plugin.xml
index 07f46cd7..8be472b9 100644
--- a/packages/jetbrains-plugin/src/main/resources/META-INF/plugin.xml
+++ b/packages/jetbrains-plugin/src/main/resources/META-INF/plugin.xml
@@ -16,7 +16,7 @@
+ class="com.mongodb.jbplugin.actions.GetMongoDbVersionAction" text="Show MongoDB Version">
diff --git a/packages/mongodb-access-adapter/datagrip-access-adapter/build.gradle.kts b/packages/mongodb-access-adapter/datagrip-access-adapter/build.gradle.kts
index 444e9691..5d8bf996 100644
--- a/packages/mongodb-access-adapter/datagrip-access-adapter/build.gradle.kts
+++ b/packages/mongodb-access-adapter/datagrip-access-adapter/build.gradle.kts
@@ -1,22 +1,53 @@
+
+
repositories {
maven("https://www.jetbrains.com/intellij-repository/releases/")
+ maven("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies")
+}
+
+plugins {
+ alias(libs.plugins.intellij)
+}
+
+
+tasks {
+ named("test", Test::class) {
+ environment("TESTCONTAINERS_RYUK_DISABLED", "true")
+ val homePath = project.layout.buildDirectory.dir("idea-sandbox/config-test").get().asFile.absolutePath
+
+ jvmArgs(listOf(
+ "--add-opens=java.base/java.lang=ALL-UNNAMED",
+ "--add-opens=java.desktop/java.awt=ALL-UNNAMED",
+ "--add-opens=java.desktop/javax.swing=ALL-UNNAMED",
+ "--add-opens=java.desktop/sun.awt=ALL-UNNAMED",
+ "-Dpolyglot.engine.WarnInterpreterOnly=false",
+ "-Dpolyglot.log.level=OFF",
+ "-Didea.home.path=${homePath}"
+ ))
+ }
}
+
+intellij {
+ version.set(libs.versions.intellij.min) // Target IDE Version
+ type.set(libs.versions.intellij.type) // Target IDE Platform
+
+ plugins.set(listOf("com.intellij.java", "com.intellij.database"))
+}
+
dependencies {
implementation(libs.gson)
implementation(libs.mongodb.driver)
implementation(project(":packages:mongodb-access-adapter"))
- compileOnly(libs.testing.intellij.ideImpl)
- compileOnly(libs.testing.intellij.coreUi)
- compileOnly("com.jetbrains.intellij.database:database-sql:241.15989.150")
- compileOnly("com.jetbrains.intellij.database:database-connectivity:241.15989.150")
- compileOnly("com.jetbrains.intellij.database:database-core-impl:241.15989.150") {
- exclude("com.jetbrains.fus.reporting", "ap-validation")
+ testImplementation("com.jetbrains.intellij.platform:test-framework-junit5:241.15989.155") {
+ exclude("ai.grazie.spell")
+ exclude("ai.grazie.utils")
+ exclude("ai.grazie.nlp")
+ exclude("ai.grazie.model")
+ exclude("org.jetbrains.teamcity")
}
- compileOnly("com.jetbrains.intellij.database:database-jdbc-console:241.15989.150")
- compileOnly("com.jetbrains.intellij.database:database:241.15989.150")
- compileOnly("com.jetbrains.intellij.platform:images:241.15989.157")
- compileOnly("com.jetbrains.intellij.grid:grid-impl:241.15989.150")
- compileOnly("com.jetbrains.intellij.grid:grid:241.15989.150")
- compileOnly("com.jetbrains.intellij.grid:grid-core-impl:241.15989.150")
+
+ testImplementation(libs.testing.testContainers.core)
+ testImplementation(libs.testing.testContainers.jupiter)
+ testImplementation(libs.testing.testContainers.mongodb)
}
\ No newline at end of file
diff --git a/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/DataGripBasedReadModelProvider.kt b/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/DataGripBasedReadModelProvider.kt
index 2eefb4e0..122419f5 100644
--- a/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/DataGripBasedReadModelProvider.kt
+++ b/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/DataGripBasedReadModelProvider.kt
@@ -1,3 +1,8 @@
+/**
+ * Represents a service that allows access to a MongoDB cluster
+ * configured through a DataGrip DataSource.
+ */
+
package com.mongodb.jbplugin.accessadapter.datagrip
import com.intellij.database.dataSource.LocalDataSource
@@ -6,30 +11,47 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
-import com.mongodb.jbplugin.accessadapter.MongoDBReadModelProvider
+import com.mongodb.jbplugin.accessadapter.MongoDbReadModelProvider
import com.mongodb.jbplugin.accessadapter.Slice
-import com.mongodb.jbplugin.accessadapter.datagrip.adapter.DataGripMongoDBDriver
-import com.mongodb.jbplugin.accessadapter.slice.BuildInfoSlice
+import com.mongodb.jbplugin.accessadapter.datagrip.adapter.DataGripMongoDbDriver
import kotlinx.coroutines.runBlocking
+private typealias MapOfCachedValues = MutableMap>
+
+/**
+ * The service to be injected to access MongoDB. Usually you will use
+ * it like this:
+ *
+ * ```kt
+ * val readModelProvider = event.project!!.getService(DataGripBasedReadModelProvider::class.java)
+ * val dataSource = event.dataContext.getData(PlatformDataKeys.PSI_ELEMENT) as DbDataSource
+ * val buildInfo = readModelProvider.slice(dataSource.localDataSource!!, BuildInfoSlice)
+ * ```
+ *
+ * It will aggressively cache data at the project level, to avoid hitting MongoDB. Also, the provided
+ * driver is very slow, so it's better to avoid querying on performance sensitive contexts.
+ *
+ * @param project
+ */
@Service(Service.Level.PROJECT)
class DataGripBasedReadModelProvider(
private val project: Project,
-) : MongoDBReadModelProvider {
- private val cachedValues: MutableMap> = mutableMapOf()
+) : MongoDbReadModelProvider {
+ private val cachedValues: MapOfCachedValues = mutableMapOf()
- override fun slice(dataSource: LocalDataSource, slice: Slice): T {
- return cachedValues
- .computeIfAbsent(slice.javaClass.canonicalName, fromSlice(dataSource, BuildInfoSlice))
+ override fun slice(dataSource: LocalDataSource, slice: Slice): T = cachedValues
+ .computeIfAbsent(slice.javaClass.canonicalName, fromSlice(dataSource, slice))
.value as T
- }
- private inline fun fromSlice(dataSource: LocalDataSource, slice: Slice): (String) -> CachedValue {
+ private fun fromSlice(
+ dataSource: LocalDataSource,
+ slice: Slice
+ ): (String) -> CachedValue {
val cacheManager = CachedValuesManager.getManager(project)
return {
cacheManager.createCachedValue {
runBlocking {
- val driver = DataGripMongoDBDriver(project, dataSource)
+ val driver = DataGripMongoDbDriver(project, dataSource)
val sliceData = slice.queryUsingDriver(driver)
CachedValueProvider.Result.create(sliceData, dataSource)
diff --git a/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataGripMongoDBDriver.kt b/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataGripMongoDBDriver.kt
deleted file mode 100644
index 3ad4ef6e..00000000
--- a/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataGripMongoDBDriver.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.mongodb.jbplugin.accessadapter.datagrip.adapter
-
-import com.intellij.database.dataSource.LocalDataSource
-import com.intellij.openapi.project.Project
-import com.mongodb.jbplugin.accessadapter.MongoDBDriver
-import com.mongodb.jbplugin.accessadapter.Namespace
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import org.bson.Document
-import kotlin.reflect.KClass
-import kotlin.time.Duration
-
-class DataGripMongoDBDriver(
- val project: Project,
- val dataSource: LocalDataSource
-) : MongoDBDriver {
- override suspend fun runCommand(command: Document, result: KClass, timeout: Duration): T = withContext(Dispatchers.IO) {
- DataSourceQuery(project, dataSource, result).runQuery(
- """db.runCommand(${command.toJson()})""",
- timeout
- )[0]
- }
-
- override suspend fun findOne(
- namespace: Namespace,
- query: Document,
- options: Document,
- result: KClass,
- timeout: Duration
- ): T? = withContext(Dispatchers.IO) {
- DataSourceQuery(project, dataSource, result).runQuery(
- """db.getSiblingDB("${namespace.database}")
- .getCollection("${namespace.collection}")
- .findOne(${query.toJson()}, ${options.toJson()}) """.trimMargin(),
- timeout
- ).getOrNull(0)
- }
-
- override suspend fun findAll(
- namespace: Namespace,
- query: Document,
- result: KClass,
- limit: Int,
- timeout: Duration
- ) = withContext(Dispatchers.IO) {
- DataSourceQuery(project, dataSource, result).runQuery(
- """db.getSiblingDB("${namespace.database}")
- .getCollection("${namespace.collection}")
- .find(${query.toJson()}).limit(${limit}) """.trimMargin(),
- timeout
- )
- }
-}
\ No newline at end of file
diff --git a/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataGripMongoDbDriver.kt b/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataGripMongoDbDriver.kt
new file mode 100644
index 00000000..84bcd338
--- /dev/null
+++ b/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataGripMongoDbDriver.kt
@@ -0,0 +1,129 @@
+/**
+ * Represents a MongoDB driver interface that uses a DataGrip
+ * connection to query MongoDB.
+ */
+
+package com.mongodb.jbplugin.accessadapter.datagrip.adapter
+
+import com.google.gson.Gson
+import com.intellij.database.dataSource.DatabaseConnection
+import com.intellij.database.dataSource.DatabaseConnectionManager
+import com.intellij.database.dataSource.LocalDataSource
+import com.intellij.database.dataSource.connection.ConnectionRequestor
+import com.intellij.database.run.ConsoleRunConfiguration
+import com.intellij.openapi.project.Project
+import com.mongodb.jbplugin.accessadapter.MongoDbDriver
+import com.mongodb.jbplugin.accessadapter.Namespace
+import org.bson.Document
+
+import kotlin.reflect.KClass
+import kotlin.time.Duration
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeout
+
+/**
+ * The driver itself. Shouldn't be used directly, but through the
+ * DataGripBasedReadModelProvider.
+ *
+ * @see com.mongodb.jbplugin.accessadapter.datagrip.DataGripBasedReadModelProvider
+ *
+ * @param project
+ * @param dataSource
+ */
+internal class DataGripMongoDbDriver(
+ private val project: Project,
+ private val dataSource: LocalDataSource
+) : MongoDbDriver {
+ private val gson = Gson()
+
+ override suspend fun runCommand(
+command: Document,
+ result: KClass,
+ timeout: Duration
+): T = withContext(
+Dispatchers.IO
+) {
+ runQuery(
+ """db.runCommand(${command.toJson()})""",
+ result,
+ timeout
+ )[0]
+ }
+
+ override suspend fun findOne(
+ namespace: Namespace,
+ query: Document,
+ options: Document,
+ result: KClass,
+ timeout: Duration
+ ): T? = withContext(Dispatchers.IO) {
+ runQuery(
+ """db.getSiblingDB("${namespace.database}")
+ .getCollection("${namespace.collection}")
+ .findOne(${query.toJson()}, ${options.toJson()}) """.trimMargin(),
+ result,
+ timeout
+ ).getOrNull(0)
+ }
+
+ override suspend fun findAll(
+ namespace: Namespace,
+ query: Document,
+ result: KClass,
+ limit: Int,
+ timeout: Duration
+ ) = withContext(Dispatchers.IO) {
+ runQuery(
+ """db.getSiblingDB("${namespace.database}")
+ .getCollection("${namespace.collection}")
+ .find(${query.toJson()}).limit($limit) """.trimMargin(),
+ result,
+ timeout
+ )
+ }
+
+ suspend fun runQuery(
+queryString: String,
+ resultClass: KClass,
+ timeout: Duration
+): List =
+ withContext(Dispatchers.IO) {
+ val connection = getConnection()
+ val remoteConnection = connection.remoteConnection
+ val statement = remoteConnection.prepareStatement(queryString.trimIndent())
+
+ withTimeout(timeout) {
+ val listOfResults = mutableListOf()
+ val resultSet = statement.executeQuery()
+
+ if (resultClass.java.isPrimitive || resultClass == String::class.java) {
+ while (resultSet.next()) {
+ listOfResults.add(resultSet.getObject(1) as T)
+ }
+ } else {
+ while (resultSet.next()) {
+ val hashMap = resultSet.getObject(1) as Map
+ val result = gson.fromJson(gson.toJson(hashMap), resultClass.java)
+ listOfResults.add(result)
+ }
+ }
+
+ listOfResults
+ }
+ }
+
+ private suspend fun getConnection(): DatabaseConnection {
+ val connections = DatabaseConnectionManager.getInstance().activeConnections
+ return connections.firstOrNull { it.connectionPoint.dataSource == dataSource }
+ ?: DatabaseConnectionManager.establishConnection(
+ dataSource,
+ ConsoleRunConfiguration.newConfiguration(project).apply {
+ setOptionsFromDataSource(dataSource)
+ },
+ ConnectionRequestor.Anonymous(),
+ project,
+ true // if password is not available
+ )!!
+ }
+}
\ No newline at end of file
diff --git a/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataGripQueryAdapter.kt b/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataGripQueryAdapter.kt
deleted file mode 100644
index 0ff5e3a0..00000000
--- a/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataGripQueryAdapter.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.mongodb.jbplugin.accessadapter.datagrip.adapter
-
-import com.google.gson.Gson
-import com.intellij.database.dataSource.DatabaseConnectionCore
-import com.intellij.database.datagrid.DataRequest
-
-class DataGripQueryAdapter(
- private val queryScript: String,
- private val resultClass: Class,
- private val gson: Gson,
- ownerEx: OwnerEx,
- private val continuation: (List) -> Unit,
-): DataRequest.RawRequest(ownerEx) {
- override fun processRaw(p0: Context?, p1: DatabaseConnectionCore?) {
- val remoteConnection = p1!!.remoteConnection
- val statement = remoteConnection.prepareStatement(queryScript.trimIndent())
-
- val listOfResults = mutableListOf()
- val resultSet = statement.executeQuery()
-
- if (resultClass.isPrimitive || resultClass == String::class.java) {
- while (resultSet.next()) {
- listOfResults.add(resultSet.getObject(1) as T)
- }
- } else {
- while (resultSet.next()) {
- val hashMap = resultSet.getObject(1) as Map
- val result = gson.fromJson(gson.toJson(hashMap), resultClass)
- listOfResults.add(result)
- }
- }
-
- continuation(listOfResults)
- }
-}
\ No newline at end of file
diff --git a/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataSourceQuery.kt b/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataSourceQuery.kt
deleted file mode 100644
index becfefa0..00000000
--- a/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataSourceQuery.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.mongodb.jbplugin.accessadapter.datagrip.adapter
-
-import com.google.gson.Gson
-import com.intellij.database.console.session.DatabaseSession
-import com.intellij.database.console.session.DatabaseSessionManager
-import com.intellij.database.dataSource.LocalDataSource
-import com.intellij.openapi.project.Project
-import kotlinx.coroutines.*
-import java.util.concurrent.TimeoutException
-import java.util.concurrent.atomic.AtomicBoolean
-import kotlin.coroutines.resume
-import kotlin.reflect.KClass
-import kotlin.time.Duration
-
-class DataSourceQuery(
- private val project: Project,
- private val dataSource: LocalDataSource,
- private val result: KClass
-) {
- private val gson = Gson()
-
- suspend fun runQuery(queryString: String, timeout: Duration): List = withContext(Dispatchers.IO) {
- suspendCancellableCoroutine { callback ->
- val session = getSession()
- val hasFinished = AtomicBoolean(false)
-
- launch {
- val query = DataGripQueryAdapter(queryString, result.java, gson, session) {
- if (!hasFinished.compareAndSet(false, true)) {
- callback.resume(it)
- }
- }
-
- session.messageBus.dataProducer.processRequest(query)
-
- delay(timeout)
- if (!hasFinished.compareAndSet(false, true)) {
- callback.cancel(TimeoutException("Timeout running query '$queryString'"))
- }
- }
- }
- }
-
- private fun getSession(): DatabaseSession {
- val sessions = DatabaseSessionManager.getSessions(project, dataSource)
- if (sessions.isEmpty()) {
- return DatabaseSessionManager.openSession(project, dataSource, "mongodb")
- }
-
- return sessions[0]
- }
-}
\ No newline at end of file
diff --git a/packages/mongodb-access-adapter/datagrip-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/IntegrationTest.kt b/packages/mongodb-access-adapter/datagrip-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/IntegrationTest.kt
new file mode 100644
index 00000000..05275767
--- /dev/null
+++ b/packages/mongodb-access-adapter/datagrip-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/IntegrationTest.kt
@@ -0,0 +1,144 @@
+/**
+ * Test extension that allows us to test with the IntelliJ environment
+ * without spinning up the whole IDE. Also, sets up a MongoDB instance
+ * that can be queried.
+ */
+
+package com.mongodb.jbplugin.accessadapter.datagrip
+
+import com.intellij.database.dataSource.DatabaseDriverManagerImpl
+import com.intellij.database.dataSource.LocalDataSource
+import com.intellij.database.dataSource.validation.DatabaseDriverValidator.createDownloaderTask
+import com.intellij.database.psi.DataSourceManager
+import com.intellij.ide.impl.ProjectUtil
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.application.EDT
+import com.intellij.openapi.application.ModalityState
+import com.intellij.openapi.progress.EmptyProgressIndicator
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.project.ProjectManager
+import com.intellij.testFramework.junit5.RunInEdt
+import com.intellij.testFramework.junit5.TestApplication
+import com.intellij.util.ui.EDT
+import com.mongodb.jbplugin.accessadapter.MongoDbDriver
+import com.mongodb.jbplugin.accessadapter.datagrip.adapter.DataGripMongoDbDriver
+import org.junit.jupiter.api.extension.*
+import org.testcontainers.containers.MongoDBContainer
+import org.testcontainers.junit.jupiter.Testcontainers
+import org.testcontainers.lifecycle.Startables
+
+import java.nio.file.Files
+import java.util.*
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+
+/**
+ * Represents what version of MongoDB we support in the plugin.
+ */
+enum class MongoDbversion(val versionString: String) {
+ LATEST("7.0.9"),
+;
+}
+
+/**
+ * Annotation to be used in the test, at the class level.
+ *
+ * @see com.mongodb.jbplugin.accessadapter.datagrip.adapter.DataGripMongoDbDriverTest
+ */
+@TestApplication
+@RunInEdt(allMethods = true, writeIntent = true)
+@ExtendWith(IntegrationTestExtension::class)
+@Testcontainers(parallel = false)
+annotation class IntegrationTest(val mongodb: MongoDBVersion = MongoDBVersion.LATEST, val sharded: Boolean = false)
+
+/**
+ * Extension implementation. Must not be used directly.
+ */
+internal class IntegrationTestExtension : BeforeAllCallback,
+ AfterAllCallback,
+ ParameterResolver {
+ private val namespace = ExtensionContext.Namespace.create(IntegrationTestExtension::class.java)
+ private val containerKey = "CONTAINER"
+ private val projectKey = "PROJECT"
+ private val driverKey = "DRIVER"
+ private val versionKey = "VERSION"
+
+ override fun beforeAll(context: ExtensionContext?) {
+ val annotation = context!!.requiredTestClass.getAnnotation(IntegrationTest::class.java)
+ val container = MongoDBContainer("mongo:${annotation.mongodb.versionString}-jammy")
+ .let {
+ if (annotation.sharded) {
+ it.withSharding()
+ } else {
+ it
+ }
+ }
+
+ Startables.deepStart(container).join()
+ context.getStore(namespace).put(containerKey, container)
+ context.getStore(namespace).put(versionKey, annotation.mongodb)
+
+ val project = runBlocking(Dispatchers.EDT) {
+ val testClassName = context.requiredTestClass.simpleName
+ ProjectUtil.openOrCreateProject(testClassName, Files.createTempDirectory(testClassName))!!
+ }
+
+ context.getStore(namespace).put(projectKey, project)
+
+ val dataSource = runBlocking {
+ val dataSourceManager = DataSourceManager.byDataSource(project, LocalDataSource::class.java)!!
+ val instance = DatabaseDriverManagerImpl.getInstance()
+ val jdbcDriver = instance.getDriver("mongo")
+
+ val dataSource = LocalDataSource().apply {
+ name = UUID.randomUUID().toString()
+ url = container.connectionString
+ isConfiguredByUrl = true
+ username = ""
+ passwordStorage = LocalDataSource.Storage.PERSIST
+ databaseDriver = jdbcDriver
+ }
+
+ dataSourceManager.addDataSource(dataSource)
+ dataSource
+ }
+
+ createDownloaderTask(dataSource, null).run(EmptyProgressIndicator())
+
+ runBlocking(Dispatchers.EDT) {
+ EDT.dispatchAllInvocationEvents()
+ }
+
+ val driver = DataGripMongoDbDriver(project, dataSource)
+ context.getStore(namespace).put(driverKey, driver)
+
+ runBlocking(Dispatchers.EDT) {
+ EDT.dispatchAllInvocationEvents()
+ }
+ }
+
+ override fun afterAll(context: ExtensionContext?) {
+ val project = context!!.getStore(namespace).get(projectKey) as Project
+ val mongodb = context.getStore(namespace).get(containerKey) as MongoDBContainer
+
+ ApplicationManager.getApplication().invokeLater({
+ ProjectManager.getInstance().closeAndDispose(project)
+ }, ModalityState.defaultModalityState())
+
+ mongodb.close()
+ }
+
+ override fun supportsParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Boolean =
+ parameterContext?.parameter?.type == Project::class.java ||
+ parameterContext?.parameter?.type == MongoDbDriver::class.java ||
+ parameterContext?.parameter?.type == MongoDBVersion::class.java
+
+ override fun resolveParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Any =
+ when (parameterContext?.parameter?.type) {
+ Project::class.java -> extensionContext!!.getStore(namespace).get(projectKey)
+ MongoDbDriver::class.java -> extensionContext!!.getStore(namespace).get(driverKey)
+ MongoDBVersion::class.java -> extensionContext!!.getStore(namespace).get(versionKey)
+ else -> TODO("Parameter of type ${parameterContext?.parameter?.type?.canonicalName} is not supported.")
+ }
+}
\ No newline at end of file
diff --git a/packages/mongodb-access-adapter/datagrip-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataGripMongoDbDriverTest.kt b/packages/mongodb-access-adapter/datagrip-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataGripMongoDbDriverTest.kt
new file mode 100644
index 00000000..262bf1c6
--- /dev/null
+++ b/packages/mongodb-access-adapter/datagrip-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataGripMongoDbDriverTest.kt
@@ -0,0 +1,33 @@
+package com.mongodb.jbplugin.accessadapter.datagrip.adapter
+
+import com.mongodb.jbplugin.accessadapter.MongoDbDriver
+import com.mongodb.jbplugin.accessadapter.datagrip.IntegrationTest
+import com.mongodb.jbplugin.accessadapter.datagrip.MongoDBVersion
+import org.bson.Document
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+
+import kotlinx.coroutines.runBlocking
+
+@IntegrationTest
+class DataGripMongoDbDriverTest {
+ @Test
+ fun `can connect and run a command`(version: MongoDBVersion, driver: MongoDbDriver) = runBlocking {
+ val result = driver.runCommand(Document(mapOf(
+ "buildInfo" to 1,
+ )), Map::class)
+
+ assertEquals(result["version"], version.versionString)
+ }
+
+ @Test
+ fun `is able to map the result to a class`(version: MongoDBVersion, driver: MongoDbDriver) = runBlocking {
+ data class MyBuildInfo(val version: String)
+
+ val result = driver.runCommand(Document(mapOf(
+ "buildInfo" to 1,
+ )), MyBuildInfo::class)
+
+ assertEquals(result.version, version.versionString)
+ }
+}
\ No newline at end of file
diff --git a/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/MongoDBDriver.kt b/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/MongoDBDriver.kt
deleted file mode 100644
index 6f1dbdbb..00000000
--- a/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/MongoDBDriver.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.mongodb.jbplugin.accessadapter
-
-import org.bson.Document
-import org.owasp.encoder.Encode
-import kotlin.reflect.KClass
-import kotlin.time.Duration
-import kotlin.time.Duration.Companion.seconds
-
-data class Namespace(val database: String, val collection: String) {
- override fun toString(): String {
- return "${database}.${collection}"
- }
-}
-
-fun String.toNS(): Namespace {
- val (db, coll) = trim().split(".", limit = 2)
- return Namespace(
- Encode.forJavaScript(db),
- Encode.forJavaScript(coll)
- )
-}
-
-interface MongoDBDriver {
- suspend fun runCommand(command: Document, result: KClass, timeout: Duration = 1.seconds): T
- suspend fun findOne(namespace: Namespace, query: Document, options: Document, result: KClass, timeout: Duration = 1.seconds): T?
- suspend fun findAll(namespace: Namespace, query: Document, result: KClass, limit: Int = 10, timeout: Duration = 1.seconds): List
-}
\ No newline at end of file
diff --git a/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/MongoDBReadModelProvider.kt b/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/MongoDBReadModelProvider.kt
deleted file mode 100644
index 66c89004..00000000
--- a/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/MongoDBReadModelProvider.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.mongodb.jbplugin.accessadapter
-
-interface Slice {
- suspend fun queryUsingDriver(from: MongoDBDriver): State
-}
-
-interface MongoDBReadModelProvider {
- fun slice(dataSource: DataSource, slice: Slice): T
-}
\ No newline at end of file
diff --git a/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbDriver.kt b/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbDriver.kt
new file mode 100644
index 00000000..7b0adf3c
--- /dev/null
+++ b/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbDriver.kt
@@ -0,0 +1,69 @@
+/**
+ * Represents the MongoDB Driver facade that we will use internally.
+ * Usually, we won't use this class directly, only in tests. What we
+ * will use is the MongoDBReadModelProvider, that provides caching
+ * and safety mechanisms.
+ *
+ * @see com.mongodb.jbplugin.accessadapter.MongoDbReadModelProvider
+ */
+
+package com.mongodb.jbplugin.accessadapter
+
+import org.bson.Document
+import org.owasp.encoder.Encode
+import kotlin.reflect.KClass
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+
+/**
+ * Represents a MongoDB Namespace (db/coll)
+ *
+ * @property database
+ * @property collection
+ */
+data class Namespace(val database: String, val collection: String) {
+ override fun toString(): String = "$database.$collection"
+}
+
+/**
+ * Represents the MongoDB Driver facade that we will use internally.
+ * Usually, we won't use this class directly, only in tests. What we
+ * will use is the MongoDBReadModelProvider, that provides caching
+ * and safety mechanisms.
+ *
+ * @see com.mongodb.jbplugin.accessadapter.MongoDbReadModelProvider
+ */
+interface MongoDbDriver {
+ suspend fun runCommand(
+command: Document,
+ result: KClass,
+ timeout: Duration = 1.seconds
+): T
+ suspend fun findOne(
+namespace: Namespace,
+ query: Document,
+ options: Document,
+ result: KClass,
+ timeout: Duration = 1.seconds
+): T?
+ suspend fun findAll(
+namespace: Namespace,
+ query: Document,
+ result: KClass,
+ limit: Int = 10,
+ timeout: Duration = 1.seconds
+): List
+}
+
+/**
+ * Converts a string in form of `db.coll` to a Namespace object.
+ *
+ * @return
+ */
+fun String.toNs(): Namespace {
+ val (db, coll) = trim().split(".", limit = 2)
+ return Namespace(
+ Encode.forJavaScript(db),
+ Encode.forJavaScript(coll)
+ )
+}
\ No newline at end of file
diff --git a/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbReadModelProvider.kt b/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbReadModelProvider.kt
new file mode 100644
index 00000000..95671aef
--- /dev/null
+++ b/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbReadModelProvider.kt
@@ -0,0 +1,28 @@
+/**
+ * Interface that needs to be implemented to access MongoDB. Implementation
+ * classes must be thread safe.
+ */
+
+package com.mongodb.jbplugin.accessadapter
+
+/**
+ * A slice of data from MongoDB. S is the resulting type of the query.
+ *
+ * @see com.mongodb.jbplugin.accessadapter.slice.BuildInfo.Slice
+ *
+ * @param S
+ */
+interface Slice {
+ suspend fun queryUsingDriver(from: MongoDbDriver): S
+}
+
+/**
+ * Accessing MongoDB state will be done through the provider, that will ensure
+ * efficient access or caching if necessary. The type `D` is the type of DataSource
+ * that will be used by the slice. It's an opaque type.
+ *
+ * @param D
+ */
+interface MongoDbReadModelProvider {
+ fun slice(dataSource: D, slice: Slice): T
+}
\ No newline at end of file
diff --git a/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/slice/BuildInfo.kt b/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/slice/BuildInfo.kt
new file mode 100644
index 00000000..7574aabd
--- /dev/null
+++ b/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/slice/BuildInfo.kt
@@ -0,0 +1,30 @@
+/**
+ * A slice that represents the build information of the connected cluster.
+ */
+
+package com.mongodb.jbplugin.accessadapter.slice
+
+import com.mongodb.jbplugin.accessadapter.MongoDbDriver
+import org.bson.Document
+
+/**
+ * Slice to be used when querying the MongoDbReadModelProvider.
+ *
+ * @see com.mongodb.jbplugin.accessadapter.slice.BuildInfo.Slice
+ * @see com.mongodb.jbplugin.accessadapter.MongoDbReadModelProvider.slice
+ * @property version
+ * @property gitVersion
+ */
+data class BuildInfo(
+ val version: String,
+ val gitVersion: String
+) {
+ object Slice : com.mongodb.jbplugin.accessadapter.Slice {
+ override suspend fun queryUsingDriver(from: MongoDbDriver): BuildInfo = from.runCommand(
+ Document(mapOf(
+ "buildInfo" to 1,
+ )),
+ BuildInfo::class
+ )
+ }
+}
\ No newline at end of file
diff --git a/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/slice/BuildInfoSlice.kt b/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/slice/BuildInfoSlice.kt
deleted file mode 100644
index 8cf750b0..00000000
--- a/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/slice/BuildInfoSlice.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.mongodb.jbplugin.accessadapter.slice
-
-import com.mongodb.jbplugin.accessadapter.MongoDBDriver
-import com.mongodb.jbplugin.accessadapter.Slice
-import org.bson.Document
-
-data class BuildInfo(
- val version: String,
- val gitVersion: String
-)
-data object BuildInfoSlice : Slice {
- override suspend fun queryUsingDriver(from: MongoDBDriver): BuildInfo {
- return from.runCommand(
- Document(mapOf(
- "buildInfo" to 1,
- )),
- BuildInfo::class
- )
- }
-}
\ No newline at end of file
diff --git a/packages/mongodb-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/MongoDBDriverTest.kt b/packages/mongodb-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbDriverTest.kt
similarity index 98%
rename from packages/mongodb-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/MongoDBDriverTest.kt
rename to packages/mongodb-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbDriverTest.kt
index c9514eb3..9b3e8c1b 100644
--- a/packages/mongodb-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/MongoDBDriverTest.kt
+++ b/packages/mongodb-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbDriverTest.kt
@@ -3,7 +3,7 @@ package com.mongodb.jbplugin.accessadapter
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
-class MongoDBDriverTest {
+class MongoDbDriverTest {
@Test
fun `parses a namespace`() {
val namespace = "mydb.mycoll".toNS()
diff --git a/packages/mongodb-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/slice/BuildInfoSliceTest.kt b/packages/mongodb-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/slice/BuildInfoTest.kt
similarity index 74%
rename from packages/mongodb-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/slice/BuildInfoSliceTest.kt
rename to packages/mongodb-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/slice/BuildInfoTest.kt
index 978aadea..197f2f84 100644
--- a/packages/mongodb-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/slice/BuildInfoSliceTest.kt
+++ b/packages/mongodb-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/slice/BuildInfoTest.kt
@@ -1,20 +1,21 @@
package com.mongodb.jbplugin.accessadapter.slice
-import com.mongodb.jbplugin.accessadapter.MongoDBDriver
-import kotlinx.coroutines.runBlocking
+import com.mongodb.jbplugin.accessadapter.MongoDbDriver
import org.bson.Document
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.mockito.Mockito
-class BuildInfoSliceTest {
+import kotlinx.coroutines.runBlocking
+
+class BuildInfoTest {
@Test
fun `returns a valid build info`(): Unit = runBlocking {
val command = Document(mapOf("buildInfo" to 1))
- val driver = Mockito.mock()
+ val driver = Mockito.mock()
Mockito.`when`(driver.runCommand(command, BuildInfo::class)).thenReturn(BuildInfo("7.8.0", "1235abc"))
- val data = BuildInfoSlice.queryUsingDriver(driver)
+ val data = BuildInfo.Slice.queryUsingDriver(driver)
Assertions.assertEquals("7.8.0", data.version)
Assertions.assertEquals("1235abc", data.gitVersion)
}