Skip to content

Commit 212a300

Browse files
authored
feat(spring-criteria): support for in/nin operators INTELLIJ-104 (#77)
1 parent df081b5 commit 212a300

File tree

5 files changed

+279
-72
lines changed

5 files changed

+279
-72
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ MongoDB plugin for IntelliJ IDEA.
55
## [Unreleased]
66

77
### Added
8+
* [INTELLIJ-104](https://jira.mongodb.org/browse/INTELLIJ-104) Add support for Spring Criteria
9+
in/nin operator, like in `where(field).in(1, 2, 3)`
810
* [INTELLIJ-61](https://jira.mongodb.org/browse/INTELLIJ-61) Add support for Spring Criteria
911
not operator, like in `where(field).not().is(value)`
1012
* [INTELLIJ-49](https://jira.mongodb.org/browse/INTELLIJ-49) Add support for Spring Criteria

packages/mongodb-dialects/java-driver/src/main/kotlin/com/mongodb/jbplugin/dialects/javadriver/glossary/JavaDriverDialectParser.kt

Lines changed: 77 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -165,77 +165,17 @@ object JavaDriverDialectParser : DialectParser<PsiElement> {
165165
val valueReference = if (filter.argumentList.expressionCount == 2) {
166166
var secondArg = filter.argumentList.expressions[1].meaningfulExpression() as PsiExpression
167167
if (secondArg.type?.isJavaIterable() == true) { // case 3
168-
HasValueReference.Runtime(
169-
secondArg,
170-
BsonArray(
171-
secondArg.type?.guessIterableContentType(secondArg.project) ?: BsonAny
172-
)
173-
)
168+
filter.argumentList.inferFromSingleVarArgElement(start = 1)
174169
} else if (secondArg.type?.isArray() == false) { // case 1
175-
val (constant, value) = secondArg.tryToResolveAsConstant()
176-
if (constant) {
177-
HasValueReference.Constant(
178-
secondArg,
179-
listOf(value),
180-
BsonArray(value?.javaClass.toBsonType(value))
181-
)
182-
} else {
183-
HasValueReference.Runtime(
184-
secondArg,
185-
BsonArray(
186-
secondArg.type?.toBsonType() ?: BsonAny
187-
)
188-
)
189-
}
170+
filter.argumentList.inferFromSingleArrayArgument(start = 1)
190171
} else { // case 2
191172
HasValueReference.Runtime(
192173
secondArg,
193174
secondArg.type?.toBsonType() ?: BsonArray(BsonAny)
194175
)
195176
}
196177
} else if (filter.argumentList.expressionCount > 2) {
197-
val allConstants: List<Pair<Boolean, Any?>> = filter.argumentList.expressions.slice(
198-
1..<filter.argumentList.expressionCount
199-
)
200-
.map { it.tryToResolveAsConstant() }
201-
202-
if (allConstants.isEmpty()) {
203-
HasValueReference.Runtime(filter, BsonArray(BsonAny))
204-
} else if (allConstants.all { it.first }) {
205-
val eachType = allConstants.mapNotNull {
206-
it.second?.javaClass?.toBsonType(it.second)
207-
}.map {
208-
flattenAnyOfReferences(it)
209-
}.toSet()
210-
211-
if (eachType.size == 1) {
212-
val type = eachType.first()
213-
HasValueReference.Constant(
214-
filter,
215-
allConstants.map { it.second },
216-
BsonArray(type)
217-
)
218-
} else {
219-
val eachType = allConstants.mapNotNull {
220-
it.second?.javaClass?.toBsonType(it.second)
221-
}.toSet()
222-
val schema = flattenAnyOfReferences(BsonAnyOf(eachType))
223-
HasValueReference.Constant(
224-
filter,
225-
allConstants.map { it.second },
226-
BsonArray(schema)
227-
)
228-
}
229-
} else {
230-
val eachType = allConstants.mapNotNull {
231-
it.second?.javaClass?.toBsonType(it.second)
232-
}.toSet()
233-
val schema = BsonAnyOf(eachType)
234-
HasValueReference.Runtime(
235-
filter,
236-
BsonArray(schema)
237-
)
238-
}
178+
filter.argumentList.inferValueReferenceFromVarArg(start = 1)
239179
} else {
240180
HasValueReference.Runtime(filter, BsonArray(BsonAny))
241181
}
@@ -450,11 +390,31 @@ object JavaDriverDialectParser : DialectParser<PsiElement> {
450390
}
451391
}
452392

453-
private fun PsiType.isArray(): Boolean {
393+
fun PsiExpressionList.inferFromSingleArrayArgument(start: Int = 0): HasValueReference.ValueReference<PsiElement> {
394+
val arrayArg = expressions[start]
395+
val (constant, value) = arrayArg.tryToResolveAsConstant()
396+
397+
return if (constant) {
398+
HasValueReference.Constant(
399+
arrayArg,
400+
listOf(value),
401+
BsonArray(value?.javaClass.toBsonType(value))
402+
)
403+
} else {
404+
HasValueReference.Runtime(
405+
arrayArg,
406+
BsonArray(
407+
arrayArg.type?.toBsonType() ?: BsonAny
408+
)
409+
)
410+
}
411+
}
412+
413+
fun PsiType.isArray(): Boolean {
454414
return this is PsiArrayType
455415
}
456416

457-
private fun PsiType.isJavaIterable(): Boolean {
417+
fun PsiType.isJavaIterable(): Boolean {
458418
if (this !is PsiClassType) {
459419
return false
460420
}
@@ -474,7 +434,7 @@ private fun PsiType.isJavaIterable(): Boolean {
474434
return return recursivelyCheckIsIterable(this)
475435
}
476436

477-
private fun PsiType.guessIterableContentType(project: Project): BsonType {
437+
fun PsiType.guessIterableContentType(project: Project): BsonType {
478438
val text = canonicalText
479439
val start = text.indexOf('<')
480440
if (start == -1) {
@@ -492,3 +452,54 @@ private fun PsiType.guessIterableContentType(project: Project): BsonType {
492452
GlobalSearchScope.everythingScope(project)
493453
).toBsonType()
494454
}
455+
456+
fun PsiExpressionList.inferValueReferenceFromVarArg(start: Int = 0): HasValueReference.ValueReference<PsiElement> {
457+
val allConstants: List<Pair<Boolean, Any?>> = expressions.slice(
458+
start..<expressionCount
459+
).map { it.tryToResolveAsConstant() }
460+
461+
if (allConstants.isEmpty()) {
462+
return HasValueReference.Runtime(parent, BsonArray(BsonAny))
463+
} else if (allConstants.all { it.first }) {
464+
val eachType = allConstants.mapNotNull {
465+
it.second?.javaClass?.toBsonType(it.second)
466+
}.map {
467+
flattenAnyOfReferences(it)
468+
}.toSet()
469+
470+
if (eachType.size == 1) {
471+
val type = eachType.first()
472+
return HasValueReference.Constant(
473+
parent,
474+
allConstants.map { it.second },
475+
BsonArray(type)
476+
)
477+
} else {
478+
val eachType = allConstants.mapNotNull {
479+
it.second?.javaClass?.toBsonType(it.second)
480+
}.toSet()
481+
val schema = flattenAnyOfReferences(BsonAnyOf(eachType))
482+
return HasValueReference.Constant(
483+
parent,
484+
allConstants.map { it.second },
485+
BsonArray(schema)
486+
)
487+
}
488+
} else {
489+
return HasValueReference.Runtime(parent, BsonArray(BsonAny))
490+
}
491+
}
492+
493+
fun PsiExpressionList.inferFromSingleVarArgElement(start: Int = 0): HasValueReference.ValueReference<PsiElement> {
494+
var secondArg = expressions[start].meaningfulExpression() as PsiExpression
495+
return if (secondArg.type?.isJavaIterable() == true) { // case 3
496+
HasValueReference.Runtime(
497+
secondArg,
498+
BsonArray(
499+
secondArg.type?.guessIterableContentType(secondArg.project) ?: BsonAny
500+
)
501+
)
502+
} else {
503+
HasValueReference.Runtime(parent, BsonArray(BsonAny))
504+
}
505+
}

packages/mongodb-dialects/spring-criteria/src/main/kotlin/com/mongodb/jbplugin/dialects/springcriteria/SpringCriteriaDialectParser.kt

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.mongodb.jbplugin.dialects.springcriteria.QueryTargetCollectionExtract
1111
import com.mongodb.jbplugin.dialects.springcriteria.QueryTargetCollectionExtractor.extractCollectionFromQueryChain
1212
import com.mongodb.jbplugin.dialects.springcriteria.QueryTargetCollectionExtractor.or
1313
import com.mongodb.jbplugin.mql.BsonAny
14+
import com.mongodb.jbplugin.mql.BsonArray
1415
import com.mongodb.jbplugin.mql.Node
1516
import com.mongodb.jbplugin.mql.components.*
1617
import com.mongodb.jbplugin.mql.toBsonType
@@ -291,7 +292,10 @@ object SpringCriteriaDialectParser : DialectParser<PsiElement> {
291292

292293
// 1st scenario: vararg operations
293294
// for example, andOperator/orOperator...
294-
if (valueFilterMethod.isVarArgs) {
295+
if (valueFilterMethod.isVarArgs &&
296+
valueFilterMethod.name != "in" &&
297+
valueFilterMethod.name != "nin"
298+
) {
295299
val childrenNodes = valueMethodCall.argumentList.expressions.flatMap {
296300
parseFilterRecursively(it).reversed()
297301
}
@@ -323,7 +327,7 @@ object SpringCriteriaDialectParser : DialectParser<PsiElement> {
323327
// v--------------------- optional negation
324328
// v------------- valueMethodCall
325329
// v----- the value itself
326-
// 2st scenario: $fieldRef$.$not$?.$filter$("abc")
330+
// 2nd scenario: $fieldRef$.$not$?.$filter$("abc")
327331
var negate = false
328332
var fieldMethodCall =
329333
valueMethodCall.firstChild.firstChild.meaningfulExpression() as? PsiMethodCallExpression
@@ -408,10 +412,41 @@ object SpringCriteriaDialectParser : DialectParser<PsiElement> {
408412
}
409413

410414
private fun inferValueReference(valueMethodCall: PsiMethodCallExpression): HasValueReference<PsiElement> {
415+
val method = valueMethodCall.fuzzyResolveMethod() ?: return HasValueReference(
416+
HasValueReference.Unknown as HasValueReference.ValueReference<PsiElement>
417+
)
418+
419+
if (method.name == "in" || method.name == "nin") {
420+
return varargExpressionListToValueReference(valueMethodCall.argumentList)
421+
}
422+
411423
val valuePsi = valueMethodCall.argumentList.expressions.getOrNull(0)
412424
return psiExpressionToValueReference(valuePsi)
413425
}
414426

427+
private fun varargExpressionListToValueReference(argumentList: PsiExpressionList, start: Int = 0): HasValueReference<PsiElement> {
428+
val valueReference: HasValueReference.ValueReference<PsiElement> =
429+
if (argumentList.expressionCount == (start + 1)) {
430+
var secondArg = argumentList.expressions[start].meaningfulExpression() as PsiExpression
431+
if (secondArg.type?.isJavaIterable() == true) { // case 3
432+
argumentList.inferFromSingleVarArgElement(start)
433+
} else if (secondArg.type?.isArray() == false) { // case 1
434+
argumentList.inferFromSingleArrayArgument(start)
435+
} else { // case 2
436+
HasValueReference.Runtime(
437+
secondArg,
438+
secondArg.type?.toBsonType() ?: BsonArray(BsonAny)
439+
)
440+
}
441+
} else if (argumentList.expressionCount > (start + 1)) {
442+
argumentList.inferValueReferenceFromVarArg(start)
443+
} else {
444+
HasValueReference.Runtime(argumentList, BsonArray(BsonAny))
445+
}
446+
447+
return HasValueReference<PsiElement>(valueReference)
448+
}
449+
415450
private fun psiExpressionToValueReference(valuePsi: PsiExpression?): HasValueReference<PsiElement> {
416451
val (_, value) = valuePsi?.tryToResolveAsConstant() ?: (false to null)
417452
val valueReference = when (value) {

packages/mongodb-dialects/spring-criteria/src/test/kotlin/com/mongodb/jbplugin/dialects/springcriteria/IntegrationTest.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -349,13 +349,15 @@ inline fun <reified T : HasCollectionReference.CollectionReference<PsiElement>>
349349
assertions: T.() -> Unit
350350
) {
351351
val ref = component<HasCollectionReference<PsiElement>>()
352-
assertNotNull(ref)
352+
assertNotEquals(null, ref) {
353+
"Could not find a HasCollectionReference component in the query."
354+
}
353355

354356
if (ref!!.reference is T) {
355357
(ref.reference as T).assertions()
356358
} else {
357359
fail(
358-
"Collection reference was not of type ${T::class.java.canonicalName} but ${ref::class.java.canonicalName}"
360+
"Collection reference was not of type ${T::class.java.canonicalName} but ${ref.reference.javaClass.canonicalName}"
359361
)
360362
}
361363
}
@@ -364,7 +366,9 @@ inline fun <reified T : HasFieldReference.FieldReference<PsiElement>> Node<PsiEl
364366
assertions: T.() -> Unit
365367
) {
366368
val ref = component<HasFieldReference<PsiElement>>()
367-
assertNotNull(ref)
369+
assertNotEquals(null, ref) {
370+
"Could not find a HasFieldReference component in the query."
371+
}
368372

369373
if (ref!!.reference is T) {
370374
(ref.reference as T).assertions()
@@ -379,7 +383,9 @@ inline fun <reified T : HasValueReference.ValueReference<PsiElement>> Node<PsiEl
379383
assertions: T.() -> Unit
380384
) {
381385
val ref = component<HasValueReference<PsiElement>>()
382-
assertNotNull(ref)
386+
assertNotEquals(null, ref) {
387+
"Could not find a HasValueReference component in the query."
388+
}
383389

384390
if (ref!!.reference is T) {
385391
(ref.reference as T).assertions()

0 commit comments

Comments
 (0)