Skip to content

Commit

Permalink
chore: add support for unwind
Browse files Browse the repository at this point in the history
  • Loading branch information
kmruiz committed Jan 17, 2025
1 parent 0c984ec commit 9f6ddc0
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ MongoDB plugin for IntelliJ IDEA.
## [Unreleased]

### Added
* [INTELLIJ-176](https://jira.mongodb.org/browse/INTELLIJ-176) Add support for parsing, inspecting and autocompleting in an unwind stage written using `Aggregation.unwind`.
* [INTELLIJ-173](https://jira.mongodb.org/browse/INTELLIJ-173) Add support for parsing, inspecting and autocompleting in a project stage written using `Aggregation.match` and chained `ProjectionOperations` using `andInclude` and `andExclude`.
* [INTELLIJ-172](https://jira.mongodb.org/browse/INTELLIJ-172) Add support for parsing, inspecting and autocompleting in an aggregation written using Spring Data MongoDB (`MongoTemplate.aggregate`, `MongoTemplate.aggregateStream`) and a match stage written using `Aggregation.match`.
* [INTELLIJ-179](https://jira.mongodb.org/browse/INTELLIJ-179) Telemetry when Create Index intention is clicked.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public Document queryMovieById(String id) {
.getCollection("movies")
.aggregate(List.of(Aggregates.match(
Filters.eq(id)
)))
), Aggregates.unwind("year")))
.first();
}

Expand Down Expand Up @@ -74,7 +74,7 @@ public List<Document> queryMoviesByYear(String year) {
)
),
Aggregates.unwind(
"asd",
"awards.wins",
new UnwindOptions()
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,25 @@ import com.mongodb.jbplugin.dialects.springcriteria.QueryTargetCollectionExtract
import com.mongodb.jbplugin.dialects.springcriteria.aggregationstageparsers.MatchStageParser
import com.mongodb.jbplugin.dialects.springcriteria.aggregationstageparsers.ProjectStageParser
import com.mongodb.jbplugin.dialects.springcriteria.aggregationstageparsers.StageParser
import com.mongodb.jbplugin.dialects.springcriteria.aggregationstageparsers.UnwindStageParser
import com.mongodb.jbplugin.mql.BsonAny
import com.mongodb.jbplugin.mql.BsonArray
import com.mongodb.jbplugin.mql.Node
import com.mongodb.jbplugin.mql.components.*
import com.mongodb.jbplugin.mql.toBsonType

private const val CRITERIA_CLASS_FQN = "org.springframework.data.mongodb.core.query.Criteria"
private const val DOCUMENT_FQN = "org.springframework.data.mongodb.core.mapping.Document"
private const val MONGO_TEMPLATE_FQN = "org.springframework.data.mongodb.core.MongoTemplate"
const val AGGREGATE_FQN = "org.springframework.data.mongodb.core.aggregation.Aggregation"
const val PROJECTION_OPERATION_FQN = "org.springframework.data.mongodb.core.aggregation.ProjectionOperation"
const val FIELDS_FQN = "org.springframework.data.mongodb.core.aggregation.Fields"
internal const val CRITERIA_CLASS_FQN = "org.springframework.data.mongodb.core.query.Criteria"
internal const val DOCUMENT_FQN = "org.springframework.data.mongodb.core.mapping.Document"
internal const val MONGO_TEMPLATE_FQN = "org.springframework.data.mongodb.core.MongoTemplate"
internal const val AGGREGATE_FQN = "org.springframework.data.mongodb.core.aggregation.Aggregation"
internal const val PROJECTION_OPERATION_FQN = "org.springframework.data.mongodb.core.aggregation.ProjectionOperation"
internal const val FIELDS_FQN = "org.springframework.data.mongodb.core.aggregation.Fields"

object SpringCriteriaDialectParser : DialectParser<PsiElement> {
private val aggregationStageParsers: List<StageParser> = listOf(
MatchStageParser(::parseFilterRecursively),
ProjectStageParser()
ProjectStageParser(),
UnwindStageParser()
)

override fun isCandidateForQuery(source: PsiElement) =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.mongodb.jbplugin.dialects.springcriteria.aggregationstageparsers

import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiMethodCallExpression
import com.mongodb.jbplugin.dialects.javadriver.glossary.tryToResolveAsConstantString
import com.mongodb.jbplugin.dialects.springcriteria.AGGREGATE_FQN
import com.mongodb.jbplugin.mql.Component
import com.mongodb.jbplugin.mql.Node
import com.mongodb.jbplugin.mql.components.HasFieldReference
import com.mongodb.jbplugin.mql.components.Name
import com.mongodb.jbplugin.mql.components.Named

class UnwindStageParser : StageParser {
override fun isSuitableForFieldAutoComplete(
methodCall: PsiMethodCallExpression,
method: PsiMethod
) = true

override fun canParse(stageCallMethod: PsiMethod): Boolean {
val owningClassFqn = stageCallMethod.containingClass?.qualifiedName ?: return false
return owningClassFqn == AGGREGATE_FQN && stageCallMethod.name == "unwind"
}

override fun parse(stageCall: PsiMethodCallExpression): Node<PsiElement> {
val psiField =
stageCall.argumentList.expressions.getOrNull(0) ?: return unwindNode(stageCall)

val referencedField = psiField.tryToResolveAsConstantString()
?: return unwindNode(stageCall)

return unwindNode(
stageCall,
HasFieldReference(HasFieldReference.FromSchema(psiField, referencedField))
)
}

private fun unwindNode(stageCall: PsiMethodCallExpression, vararg additionalComponents: Component): Node<PsiElement> =
Node(stageCall, listOf(Named(Name.UNWIND)) + additionalComponents)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.mongodb.jbplugin.dialects.springcriteria.aggregationstageparsers

import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.mongodb.jbplugin.dialects.springcriteria.IntegrationTest
import com.mongodb.jbplugin.dialects.springcriteria.ParsingTest
import com.mongodb.jbplugin.dialects.springcriteria.SpringCriteriaDialectParser
import com.mongodb.jbplugin.dialects.springcriteria.assert
import com.mongodb.jbplugin.dialects.springcriteria.collection
import com.mongodb.jbplugin.dialects.springcriteria.component
import com.mongodb.jbplugin.dialects.springcriteria.field
import com.mongodb.jbplugin.dialects.springcriteria.getQueryAtMethod
import com.mongodb.jbplugin.dialects.springcriteria.stageN
import com.mongodb.jbplugin.mql.components.HasCollectionReference
import com.mongodb.jbplugin.mql.components.HasFieldReference
import com.mongodb.jbplugin.mql.components.HasSourceDialect
import com.mongodb.jbplugin.mql.components.IsCommand
import com.mongodb.jbplugin.mql.components.Name
import org.junit.jupiter.api.Assertions.assertEquals

@IntegrationTest
class UnwindStageParserTest {
@ParsingTest(
fileName = "Book.java",
"""
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.List;
@Document
record Book() {}
class Repository {
private final MongoTemplate template;
public Repository(MongoTemplate template) {
this.template = template;
}
public AggregationResults<Book> allReleasedBooks() {
return template.aggregate(
Aggregation.newAggregation(
Aggregation.unwind("author")
),
Book.class,
Book.class
);
}
}
"""
)
fun `should be able to parse an unwind stage with a literal field name`(psiFile: PsiFile) {
val query = psiFile.getQueryAtMethod("Repository", "allReleasedBooks")
SpringCriteriaDialectParser.parse(query).assert(IsCommand.CommandType.AGGREGATE) {
component<HasSourceDialect> {
assertEquals(HasSourceDialect.DialectName.SPRING_CRITERIA, name)
}

collection<HasCollectionReference.OnlyCollection<PsiElement>> {
assertEquals("book", collection)
}

stageN(0, Name.UNWIND) {
field<HasFieldReference.FromSchema<PsiElement>> {
assertEquals("author", fieldName)
}
}
}
}

@ParsingTest(
fileName = "Book.java",
"""
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.List;
@Document
record Book() {}
class Repository {
private static final String AUTHOR = "author";
private final MongoTemplate template;
public Repository(MongoTemplate template) {
this.template = template;
}
public AggregationResults<Book> allReleasedBooks() {
return template.aggregate(
Aggregation.newAggregation(
Aggregation.unwind(AUTHOR)
),
Book.class,
Book.class
);
}
}
"""
)
fun `should be able to parse an unwind stage with a constant field name`(psiFile: PsiFile) {
val query = psiFile.getQueryAtMethod("Repository", "allReleasedBooks")
SpringCriteriaDialectParser.parse(query).assert(IsCommand.CommandType.AGGREGATE) {
component<HasSourceDialect> {
assertEquals(HasSourceDialect.DialectName.SPRING_CRITERIA, name)
}

collection<HasCollectionReference.OnlyCollection<PsiElement>> {
assertEquals("book", collection)
}

stageN(0, Name.UNWIND) {
field<HasFieldReference.FromSchema<PsiElement>> {
assertEquals("author", fieldName)
}
}
}
}
}

0 comments on commit 9f6ddc0

Please sign in to comment.