Skip to content

Commit

Permalink
chore: draft how accessing mongodb will work
Browse files Browse the repository at this point in the history
  • Loading branch information
kmruiz committed May 21, 2024
1 parent 784fa23 commit 620ebe8
Show file tree
Hide file tree
Showing 17 changed files with 265 additions and 37 deletions.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ subprojects {
}

compileOnly(rootProject.libs.kotlin.stdlib)
compileOnly(rootProject.libs.kotlin.coroutines.core)
compileOnly(rootProject.libs.kotlin.reflect)
testImplementation(rootProject.libs.testing.jupiter.engine)
testImplementation(rootProject.libs.testing.jupiter.vintage.engine)
testImplementation(rootProject.libs.testing.mockito.core)
Expand Down
5 changes: 5 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ segment="3.5.1"
jsoup="1.17.2"
video-recorder="2.0"
gson="2.10.1"
mongodb-driver="5.1.0"
owasp-encoder="1.2.3"

[plugins]
intellij={ id="org.jetbrains.intellij", version.ref="intellij-plugin" }
Expand All @@ -38,13 +40,16 @@ jmhreport={ id="io.morethan.jmhreport", version.ref="jmhreport-plugin" }
## Kotlin compileOnly libraries. They must not be bundled because they are already part of the
## JetBrains ecosystem.
kotlin-stdlib={ group="org.jetbrains.kotlin", name="kotlin-stdlib", version.ref="kotlin-stdlib" }
kotlin-reflect={ group="org.jetbrains.kotlin", name="kotlin-reflect", version.ref="kotlin-stdlib" }
kotlin-coroutines-core={ group="org.jetbrains.kotlinx", name="kotlinx-coroutines-core", version.ref="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" }
######################################################
## Production Libraries.
segment={ group="com.segment.analytics.java", name="analytics", version.ref="segment" }
gson={ group="com.google.code.gson", name="gson", version.ref="gson" }
mongodb-driver={ group="org.mongodb", name="mongodb-driver-kotlin-sync", version.ref="mongodb-driver" }
owasp-encoder={ group="org.owasp.encoder", name="encoder", version.ref="owasp-encoder" }
######################################################
## Testing Libraries.
testing-jupiter-engine={ group="org.junit.jupiter", name="junit-jupiter-engine", version.ref="jupiter" }
Expand Down
2 changes: 2 additions & 0 deletions packages/jetbrains-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ intellij {

dependencies {
implementation(project(":packages:mongodb-access-adapter"))
implementation(project(":packages:mongodb-access-adapter:datagrip-access-adapter"))
implementation(project(":packages:mongodb-autocomplete-engine"))
implementation(project(":packages:mongodb-dialects"))
implementation(project(":packages:mongodb-linting-engine"))
implementation(project(":packages:mongodb-mql-model"))

implementation(libs.mongodb.driver)
implementation(libs.segment)

jmh(libs.kotlin.stdlib)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.components.Service
import com.intellij.openapi.rd.util.launchChildOnUi
import com.intellij.openapi.ui.Messages
import com.mongodb.jbplugin.dataaccess.ServerInfoRepository
import com.mongodb.jbplugin.accessadapter.datagrip.DataGripBasedReadModelProvider
import com.mongodb.jbplugin.accessadapter.slice.BuildInfoSlice
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

Expand All @@ -18,10 +19,9 @@ class GetMongoDBVersionActionService(
) {
fun actionPerformed(event: AnActionEvent) {
coroutineScope.launch {
val repository = event.project!!.getService(ServerInfoRepository::class.java)
val readModelProvider = event.project!!.getService(DataGripBasedReadModelProvider::class.java)
val dataSource = event.dataContext.getData(PlatformDataKeys.PSI_ELEMENT) as DbDataSource

val buildInfo = repository.getServerInfo(dataSource.localDataSource!!)
val buildInfo = readModelProvider.slice(dataSource.localDataSource!!, BuildInfoSlice)

coroutineScope.launchChildOnUi {
Messages.showMessageDialog(buildInfo.version, "Show DB Version", null)
Expand Down
3 changes: 2 additions & 1 deletion packages/mongodb-access-adapter/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
dependencies {

implementation(libs.owasp.encoder)
implementation(libs.mongodb.driver)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
repositories {
maven("https://www.jetbrains.com/intellij-repository/releases/")
}
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")
}
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")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.mongodb.jbplugin.accessadapter.datagrip

import com.intellij.database.dataSource.LocalDataSource
import com.intellij.openapi.components.Service
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.Slice
import com.mongodb.jbplugin.accessadapter.datagrip.adapter.DataGripMongoDBDriver
import com.mongodb.jbplugin.accessadapter.slice.BuildInfoSlice
import kotlinx.coroutines.runBlocking

@Service(Service.Level.PROJECT)
class DataGripBasedReadModelProvider(
private val project: Project,
) : MongoDBReadModelProvider<LocalDataSource> {
private val cachedValues: MutableMap<String, CachedValue<*>> = mutableMapOf()

override fun <T : Any> slice(dataSource: LocalDataSource, slice: Slice<T>): T {
return cachedValues
.computeIfAbsent(slice.javaClass.canonicalName, fromSlice(dataSource, BuildInfoSlice))
.value as T
}

private inline fun <reified T : Any> fromSlice(dataSource: LocalDataSource, slice: Slice<T>): (String) -> CachedValue<T> {
val cacheManager = CachedValuesManager.getManager(project)
return {
cacheManager.createCachedValue {
runBlocking {
val driver = DataGripMongoDBDriver(project, dataSource)
val sliceData = slice.queryUsingDriver(driver)

CachedValueProvider.Result.create(sliceData, dataSource)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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 <T : Any> runCommand(command: Document, result: KClass<T>, timeout: Duration): T = withContext(Dispatchers.IO) {
DataSourceQuery(project, dataSource, result).runQuery(
"""db.runCommand(${command.toJson()})""",
timeout
)[0]
}

override suspend fun <T : Any> findOne(
namespace: Namespace,
query: Document,
options: Document,
result: KClass<T>,
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 <T : Any> findAll(
namespace: Namespace,
query: Document,
result: KClass<T>,
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
)
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.mongodb.jbplugin.dataaccess
package com.mongodb.jbplugin.accessadapter.datagrip.adapter

import com.google.gson.Gson
import com.intellij.database.dataSource.DatabaseConnectionCore
import com.intellij.database.datagrid.DataRequest

internal class DataGripQueryAdapter<out T: Any>(
class DataGripQueryAdapter<out T: Any>(
private val queryScript: String,
private val resultClass: Class<T>,
private val gson: Gson,
Expand Down Expand Up @@ -32,5 +32,4 @@ internal class DataGripQueryAdapter<out T: Any>(

continuation(listOfResults)
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.mongodb.jbplugin.dataaccess
package com.mongodb.jbplugin.accessadapter.datagrip.adapter

import com.google.gson.Gson
import com.intellij.database.console.session.DatabaseSession
Expand All @@ -9,21 +9,23 @@ 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

internal class BaseAccessAdapter(
class DataSourceQuery<T: Any>(
private val project: Project,
private val dataSource: LocalDataSource
private val dataSource: LocalDataSource,
private val result: KClass<T>
) {
private val gson = Gson()

suspend inline fun <reified T : Any> runQuery(queryString: String, timeout: Duration): List<T> = withContext(Dispatchers.IO) {
suspend fun runQuery(queryString: String, timeout: Duration): List<T> = withContext(Dispatchers.IO) {
suspendCancellableCoroutine { callback ->
val session = getSession()
val hasFinished = AtomicBoolean(false)

launch {
val query = DataGripQueryAdapter(queryString, T::class.java, gson, session) {
val query = DataGripQueryAdapter(queryString, result.java, gson, session) {
if (!hasFinished.compareAndSet(false, true)) {
callback.resume(it)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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 <T: Any> runCommand(command: Document, result: KClass<T>, timeout: Duration = 1.seconds): T
suspend fun <T: Any> findOne(namespace: Namespace, query: Document, options: Document, result: KClass<T>, timeout: Duration = 1.seconds): T?
suspend fun <T: Any> findAll(namespace: Namespace, query: Document, result: KClass<T>, limit: Int = 10, timeout: Duration = 1.seconds): List<T>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.mongodb.jbplugin.accessadapter

interface Slice<State: Any> {
suspend fun queryUsingDriver(from: MongoDBDriver): State
}

interface MongoDBReadModelProvider<DataSource> {
fun <T: Any> slice(dataSource: DataSource, slice: Slice<T>): T
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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<BuildInfo> {
override suspend fun queryUsingDriver(from: MongoDBDriver): BuildInfo {
return from.runCommand(
Document(mapOf(
"buildInfo" to 1,
)),
BuildInfo::class
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.mongodb.jbplugin.accessadapter

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class MongoDBDriverTest {
@Test
fun `parses a namespace`() {
val namespace = "mydb.mycoll".toNS()
assertEquals("mydb", namespace.database)
assertEquals("mycoll", namespace.collection)
}

@Test
fun `parses a namespace where collections have dots in a name`() {
val namespace = "mydb.myco.ll".toNS()
assertEquals("mydb", namespace.database)
assertEquals("myco.ll", namespace.collection)
}

@Test
fun `escapes characters that can be dangerous in javascript`() {
val namespace = """mydb.myco"ll""".toNS()
assertEquals("mydb", namespace.database)
assertEquals("myco\\x22ll", namespace.collection)
}

@Test
fun `removes trailing spaces`() {
val namespace = """ mydb.myco"ll """.toNS()
assertEquals("mydb", namespace.database)
assertEquals("myco\\x22ll", namespace.collection)
}

@Test
fun `serialises to a valid namespace string`() {
val namespace = Namespace("mydb", "my.cool.col")
assertEquals("mydb.my.cool.col", namespace.toString())
}

@Test
fun `can parse back a serialised namespace`() {
val namespace = Namespace("mydb", "my.cool.col")
val deserialized = namespace.toString().toNS()

assertEquals(namespace, deserialized)
}
}
Loading

0 comments on commit 620ebe8

Please sign in to comment.