Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(index): default to queryPlanner and allow users to enable executionStats INTELLIJ-187 #121

Merged
merged 7 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ MongoDB plugin for IntelliJ IDEA.
## [Unreleased]

### Added
* [INTELLIJ-187](https://jira.mongodb.org/browse/INTELLIJ-187) Use safe execution plans by default. Allow full execution
plans through a Plugin settings flag.
* [INTELLIJ-180](https://jira.mongodb.org/browse/INTELLIJ-180) Telemetry when inspections are shown and resolved.
It can be disabled in the Plugin settings.
* [INTELLIJ-176](https://jira.mongodb.org/browse/INTELLIJ-176) Add support for parsing, inspecting and autocompleting in an unwind stage written using `Aggregation.unwind`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import com.mongodb.jbplugin.i18n.CodeActionsMessages
import com.mongodb.jbplugin.i18n.Icons
import com.mongodb.jbplugin.meta.service
import com.mongodb.jbplugin.mql.Node
import com.mongodb.jbplugin.mql.QueryContext
import com.mongodb.jbplugin.mql.components.HasSourceDialect
import com.mongodb.jbplugin.mql.components.IsCommand
import com.mongodb.jbplugin.mql.parser.components.whenHasAnyCommand
Expand Down Expand Up @@ -86,7 +87,7 @@ internal object RunQueryCodeAction : MongoDbCodeAction {
coroutineScope.launchChildBackground {
val outputQuery = MongoshDialect.formatter.formatQuery(
query,
explain = false
QueryContext.empty()
)

if (dataSource?.isConnected() == true) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import com.mongodb.jbplugin.linting.IndexCheckWarning
import com.mongodb.jbplugin.linting.IndexCheckingLinter
import com.mongodb.jbplugin.meta.service
import com.mongodb.jbplugin.mql.Node
import com.mongodb.jbplugin.mql.QueryContext
import com.mongodb.jbplugin.observability.TelemetryEvent
import com.mongodb.jbplugin.observability.probe.CreateIndexIntentionProbe
import com.mongodb.jbplugin.observability.probe.InspectionStatusChangedProbe
import com.mongodb.jbplugin.settings.pluginSetting
import kotlinx.coroutines.CoroutineScope

class IndexCheckInspectionBridge(coroutineScope: CoroutineScope) :
Expand Down Expand Up @@ -52,12 +54,28 @@ internal object IndexCheckLinterInspection : MongoDbInspection {
query: Node<PsiElement>,
formatter: DialectFormatter,
) {
val isFullExplainPlanEnabled by pluginSetting { ::isFullExplainPlanEnabled }

if (dataSource == null || !dataSource.isConnected()) {
return
}

val queryContext = QueryContext(
emptyMap(),
if (isFullExplainPlanEnabled) {
QueryContext.ExplainPlanType.FULL
} else {
QueryContext.ExplainPlanType.SAFE
}
)

val readModelProvider by query.source.project.service<DataGripBasedReadModelProvider>()
val result = IndexCheckingLinter.lintQuery(dataSource, readModelProvider, query)
val result = IndexCheckingLinter.lintQuery(
dataSource,
readModelProvider,
query,
queryContext
)

result.warnings.forEach {
when (it) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class PluginSettingsStateComponent : SimplePersistentStateComponent<PluginSettin
class PluginSettings : BaseState(), Serializable {
var isTelemetryEnabled by property(true)
var hasTelemetryOptOutputNotificationBeenShown by property(false)
var isFullExplainPlanEnabled by property(false)
}

class SettingsDelegate<T>(private val settingProp: KMutableProperty0<T>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,25 @@ class PluginSettingsConfigurable : Configurable {
override fun isModified(): Boolean {
val savedSettings by service<PluginSettingsStateComponent>()
return settingsComponent.isTelemetryEnabledCheckBox.isSelected !=
savedSettings.state.isTelemetryEnabled
savedSettings.state.isTelemetryEnabled ||
settingsComponent.enableFullExplainPlan.isSelected !=
savedSettings.state.isFullExplainPlanEnabled
}

override fun apply() {
val savedSettings by service<PluginSettingsStateComponent>()
savedSettings.state.apply {
isTelemetryEnabled = settingsComponent.isTelemetryEnabledCheckBox.isSelected
isFullExplainPlanEnabled = settingsComponent.enableFullExplainPlan.isSelected
}
}

override fun reset() {
val savedSettings by service<PluginSettingsStateComponent>()
settingsComponent.isTelemetryEnabledCheckBox.isSelected =
savedSettings.state.isTelemetryEnabled
settingsComponent.enableFullExplainPlan.isSelected =
savedSettings.state.isFullExplainPlanEnabled
}

override fun getDisplayName() = SettingsMessages.message("settings.display-name")
Expand All @@ -56,15 +61,29 @@ private class PluginSettingsComponent {
val root: JPanel
val isTelemetryEnabledCheckBox =
JBCheckBox(TelemetryMessages.message("settings.telemetry-collection-checkbox"))
val enableFullExplainPlan =
JBCheckBox(TelemetryMessages.message("settings.full-explain-plan-checkbox"))
val privacyPolicyButton = JButton(TelemetryMessages.message("action.view-privacy-policy"))
val evaluateOperationPerformanceButton =
JButton(TelemetryMessages.message("settings.view-full-explain-plan-documentation"))

init {
privacyPolicyButton.addActionListener {
BrowserUtil.browse(TelemetryMessages.message("settings.telemetry-privacy-policy"))
}

evaluateOperationPerformanceButton.addActionListener {
BrowserUtil.browse(
TelemetryMessages.message("settings.full-explain-plan-documentation")
)
}

root =
FormBuilder.createFormBuilder()
.addComponent(enableFullExplainPlan)
.addTooltip(TelemetryMessages.message("settings.full-explain-plan-tooltip"))
.addComponent(evaluateOperationPerformanceButton)
.addSeparator()
.addComponent(isTelemetryEnabledCheckBox)
.addTooltip(TelemetryMessages.message("settings.telemetry-collection-tooltip"))
.addComponent(privacyPolicyButton)
Expand All @@ -73,5 +92,6 @@ private class PluginSettingsComponent {

root.accessibleContext.accessibleName = "MongoDB Settings"
isTelemetryEnabledCheckBox.accessibleContext.accessibleName = "MongoDB Enable Telemetry"
enableFullExplainPlan.accessibleContext.accessibleName = "MongoDB Enable Full Explain Plan"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ action.disable-telemetry=Disable Telemetry
action.view-privacy-policy=View Privacy Policy
settings.telemetry-collection-tooltip=Allow the collection of anonymous diagnostics and usage telemetry data to help improve the product.
settings.telemetry-collection-checkbox=Enable telemetry
settings.telemetry-privacy-policy=https://www.mongodb.com/legal/privacy/privacy-policy
settings.full-explain-plan-tooltip=Uses 'executionStats', running the actual query and providing runtime information. Might impact cluster performance.
settings.full-explain-plan-checkbox=Enable full explain plan
settings.view-full-explain-plan-documentation=Analyze Query Performance
settings.full-explain-plan-documentation=https://www.mongodb.com/docs/manual/tutorial/evaluate-operation-performance/
settings.telemetry-privacy-policy=https://www.mongodb.com/legal/privacy/privacy-policy
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.mongodb.jbplugin.accessadapter.MongoDbDriver
import com.mongodb.jbplugin.accessadapter.datagrip.DataGripBasedReadModelProvider
import com.mongodb.jbplugin.mql.Namespace
import com.mongodb.jbplugin.mql.Node
import com.mongodb.jbplugin.mql.QueryContext
import kotlinx.coroutines.withTimeout
import org.bson.Document
import org.bson.conversions.Bson
Expand Down Expand Up @@ -142,7 +143,7 @@ internal class DirectMongoDbDriver(

override suspend fun connectionString(): ConnectionString = ConnectionString(uri)

override suspend fun <S> explain(query: Node<S>): ExplainPlan {
override suspend fun <S> explain(query: Node<S>, queryContext: QueryContext): ExplainPlan {
throw UnsupportedOperationException()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,22 @@ class MongoDbSettingsFixture(
findAll<JCheckboxFixture>().find { it.text == "Enable telemetry" }
?: throw NoSuchElementException()
}

val enableFullExplainPlan by lazy {
findAll<JCheckboxFixture>().find { it.text == "Enable full explain plan" }
?: throw NoSuchElementException()
}

val privacyPolicyButton by lazy {
findAll<JButtonFixture>().find { it.text == "View Privacy Policy" }
?: throw NoSuchElementException()
}

val analyzeQueryPerformanceButton by lazy {
findAll<JButtonFixture>().find { it.text == "Analyze Query Performance" }
?: throw NoSuchElementException()
}

val ok by lazy {
remoteRobot.findAll<JButtonFixture>().find { it.text == "OK" }
?: throw NoSuchElementException()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ class SettingsUiTest {
assertNotEquals(telemetryBeforeTest, telemetryAfterTest)
}

@Test
@RequiresProject("basic-java-project-with-mongodb")
fun `allows toggling the full explain plan`(remoteRobot: RemoteRobot) {
val telemetryBeforeTest = remoteRobot.useSetting<Boolean>("isFullExplainPlanEnabled")

val settings = remoteRobot.openSettings()
settings.enableFullExplainPlan.click()
settings.ok.click()

val telemetryAfterTest = remoteRobot.useSetting<Boolean>("isFullExplainPlanEnabled")
assertNotEquals(telemetryBeforeTest, telemetryAfterTest)
}

@Test
@RequiresProject("basic-java-project-with-mongodb")
fun `allows opening the privacy policy in a browser`(remoteRobot: RemoteRobot) {
Expand All @@ -44,4 +57,28 @@ class SettingsUiTest {

assertEquals("https://www.mongodb.com/legal/privacy/privacy-policy", lastBrowserUrl)
}

@Test
@RequiresProject("basic-java-project-with-mongodb")
fun `allows opening the analyze query performance page`(remoteRobot: RemoteRobot) {
remoteRobot.openBrowserSettings().run {
useFakeBrowser()
}

val settings = remoteRobot.openSettings()
settings.analyzeQueryPerformanceButton.click()
settings.ok.click()

val lastBrowserUrl =
remoteRobot.openBrowserSettings().run {
useSystemBrowser()
ok.click()
lastBrowserUrl()
}

assertEquals(
"https://www.mongodb.com/docs/manual/tutorial/evaluate-operation-performance/",
lastBrowserUrl
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.mongodb.jbplugin.dialects.OutputQuery
import com.mongodb.jbplugin.dialects.mongosh.MongoshDialect
import com.mongodb.jbplugin.mql.Namespace
import com.mongodb.jbplugin.mql.Node
import com.mongodb.jbplugin.mql.QueryContext
import kotlinx.coroutines.*
import org.bson.Document
import org.bson.codecs.DecoderContext
Expand Down Expand Up @@ -88,9 +89,11 @@ internal class DataGripMongoDbDriver(

override suspend fun connectionString(): ConnectionString = ConnectionString(dataSource.url!!)

override suspend fun <S> explain(query: Node<S>): ExplainPlan = withContext(Dispatchers.IO) {
override suspend fun <S> explain(query: Node<S>, queryContext: QueryContext): ExplainPlan = withContext(
Dispatchers.IO
) {
val queryScript = ApplicationManager.getApplication().runReadAction<OutputQuery> {
MongoshDialect.formatter.formatQuery(query, explain = true)
MongoshDialect.formatter.formatQuery(query, queryContext)
}

if (queryScript !is OutputQuery.CanBeRun) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.mongodb.jbplugin.accessadapter.toNs
import com.mongodb.jbplugin.mql.BsonString
import com.mongodb.jbplugin.mql.Namespace
import com.mongodb.jbplugin.mql.Node
import com.mongodb.jbplugin.mql.QueryContext
import com.mongodb.jbplugin.mql.components.HasCollectionReference
import com.mongodb.jbplugin.mql.components.HasFieldReference
import com.mongodb.jbplugin.mql.components.HasFilter
Expand Down Expand Up @@ -270,7 +271,10 @@ class DataGripMongoDbDriverTest {
Unit::class,
)

val explainPlanResult = driver.explain(query)
val explainPlanResult = driver.explain(
query,
QueryContext(emptyMap(), QueryContext.ExplainPlanType.SAFE)
)
assertEquals(ExplainPlan.CollectionScan, explainPlanResult)
}

Expand Down Expand Up @@ -318,7 +322,10 @@ class DataGripMongoDbDriverTest {
)
)

val explainPlanResult = driver.explain(query)
val explainPlanResult = driver.explain(
query,
QueryContext(emptyMap(), QueryContext.ExplainPlanType.SAFE)
)
assertEquals(ExplainPlan.IndexScan, explainPlanResult)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ package com.mongodb.jbplugin.accessadapter
import com.mongodb.ConnectionString
import com.mongodb.jbplugin.mql.Namespace
import com.mongodb.jbplugin.mql.Node
import com.mongodb.jbplugin.mql.QueryContext
import org.bson.conversions.Bson
import java.util.*
import kotlin.reflect.KClass
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
Expand All @@ -40,7 +40,7 @@ interface MongoDbDriver {

suspend fun connectionString(): ConnectionString

suspend fun <S> explain(query: Node<S>): ExplainPlan
suspend fun <S> explain(query: Node<S>, queryContext: QueryContext): ExplainPlan

suspend fun <T : Any> runCommand(
database: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package com.mongodb.jbplugin.accessadapter.slice
import com.mongodb.jbplugin.accessadapter.ExplainPlan
import com.mongodb.jbplugin.accessadapter.MongoDbDriver
import com.mongodb.jbplugin.mql.Node
import com.mongodb.jbplugin.mql.QueryContext

/**
* Runs the explain plan of a query.
Expand All @@ -21,12 +22,15 @@ data class ExplainQuery(
* @property query
*/
data class Slice<S>(
val query: Node<S>
val query: Node<S>,
val queryContext: QueryContext,
) : com.mongodb.jbplugin.accessadapter.Slice<ExplainQuery> {
override val id = "${javaClass.canonicalName}::$query"

override suspend fun queryUsingDriver(from: MongoDbDriver): ExplainQuery {
val plan = runCatching { from.explain(query) }.getOrDefault(ExplainPlan.NotRun)
val plan = runCatching {
from.explain(query, queryContext)
}.getOrDefault(ExplainPlan.NotRun)
return ExplainQuery(plan)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.mongodb.jbplugin.dialects.OutputQuery
import com.mongodb.jbplugin.mql.*

object JavaDriverDialectFormatter : DialectFormatter {
override fun <S> formatQuery(query: Node<S>, explain: Boolean) =
override fun <S> formatQuery(query: Node<S>, queryContext: QueryContext) =
OutputQuery.None

override fun <S> indexCommandForQuery(query: Node<S>) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import com.mongodb.jbplugin.mql.components.*
import com.mongodb.jbplugin.mql.components.HasFieldReference.FromSchema
import com.mongodb.jbplugin.mql.components.HasFieldReference.Inferred
import com.mongodb.jbplugin.mql.components.HasFieldReference.Unknown
import com.mongodb.jbplugin.mql.components.HasValueReference.Computed
import com.mongodb.jbplugin.mql.parser.anyError
import com.mongodb.jbplugin.mql.parser.components.aggregationStages
import com.mongodb.jbplugin.mql.parser.components.allFiltersRecursively
Expand All @@ -26,22 +25,31 @@ import org.owasp.encoder.Encode
object MongoshDialectFormatter : DialectFormatter {
override fun <S> formatQuery(
query: Node<S>,
explain: Boolean,
queryContext: QueryContext,
): OutputQuery {
val isAggregate = isAggregate(query)
val canEmitAggregate = canEmitAggregate(query)

val outputString = MongoshBackend(prettyPrint = explain).apply {
val outputString = MongoshBackend(
prettyPrint =
queryContext.explainPlan != QueryContext.ExplainPlanType.NONE
).apply {
if (isAggregate && !canEmitAggregate) {
emitComment("Only aggregates with a single match stage can be converted.")
return@apply
}

emitDbAccess()
emitCollectionReference(query.component<HasCollectionReference<S>>())
if (explain) {
if (queryContext.explainPlan != QueryContext.ExplainPlanType.NONE) {
emitFunctionName("explain")
emitFunctionCall()
emitFunctionCall(long = false, {
// https://www.mongodb.com/docs/manual/reference/command/explain/#command-fields
when (queryContext.explainPlan) {
QueryContext.ExplainPlanType.FULL -> emitStringLiteral("executionStats")
else -> emitStringLiteral("queryPlanner")
}
})
emitPropertyAccess()
if (isAggregate) {
emitFunctionName("aggregate")
Expand Down
Loading
Loading