From a058e10542b7e366dba9204b07f64a3643b98e92 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Tue, 7 Jan 2025 16:04:06 +0100 Subject: [PATCH] fix: and/or/nor not working with iterable args --- ...avaDriverFieldCheckLinterInspectionTest.kt | 98 +++- .../glossary/JavaDriverDialectParser.kt | 15 +- .../glossary/JavaDriverDialectParserTest.kt | 484 +++++++++++++++++- 3 files changed, 591 insertions(+), 6 deletions(-) diff --git a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/inspections/impl/JavaDriverFieldCheckLinterInspectionTest.kt b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/inspections/impl/JavaDriverFieldCheckLinterInspectionTest.kt index 2d1a3e6d..b8fef1ca 100644 --- a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/inspections/impl/JavaDriverFieldCheckLinterInspectionTest.kt +++ b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/inspections/impl/JavaDriverFieldCheckLinterInspectionTest.kt @@ -148,6 +148,7 @@ import com.mongodb.client.FindIterable; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoCollection; import org.bson.Document; +import org.bson.conversions.Bson; import org.bson.types.ObjectId; import static com.mongodb.client.model.Filters.*; @@ -158,7 +159,8 @@ public class Repository { this.client = client; } - public FindIterable exampleFind() { + // and queries + public FindIterable inlineAndQuery() { return client.getDatabase("myDatabase") .getCollection("myCollection") .find( @@ -167,10 +169,102 @@ public class Repository { ) ); } + + public FindIterable andQueryFromAVariableWithVariableFieldName() { + var fieldName = "nonExistingField"; + var andQuery = and( + eq(fieldName, "123") + ); + return client.getDatabase("myDatabase") + .getCollection("myCollection") + .find(andQuery); + } + + public FindIterable andQueryFromAMethodCallWithFieldNameFromMethodCall() { + return client.getDatabase("myDatabase") + .getCollection("myCollection") + .find(getAndQueryWithFieldNameFromMethodCall()); + } + + private Bson getAndQueryWithFieldNameFromMethodCall() { + return and( + eq(getFieldName(), "123") + ); + } + + private String getFieldName() { + return "nonExistingField"; + } + + // or queries + public FindIterable inlineOrQuery() { + return client.getDatabase("myDatabase") + .getCollection("myCollection") + .find( + or( + eq("nonExistingField", "123") + ) + ); + } + + public FindIterable orQueryFromAVariableWithVariableFieldName() { + var fieldName = "nonExistingField"; + var orQuery = or( + eq(fieldName, "123") + ); + return client.getDatabase("myDatabase") + .getCollection("myCollection") + .find(orQuery); + } + + public FindIterable orQueryFromAMethodCallWithFieldNameFromMethodCall() { + return client.getDatabase("myDatabase") + .getCollection("myCollection") + .find(getOrQueryWithFieldNameFromMethodCall()); + } + + private Bson getOrQueryWithFieldNameFromMethodCall() { + return or( + eq(getFieldName(), "123") + ); + } + + // nor queries + public FindIterable inlineNorQuery() { + return client.getDatabase("myDatabase") + .getCollection("myCollection") + .find( + nor( + eq("nonExistingField", "123") + ) + ); + } + + public FindIterable norQueryFromAVariableWithVariableFieldName() { + var fieldName = "nonExistingField"; + var norQuery = nor( + eq(fieldName, "123") + ); + return client.getDatabase("myDatabase") + .getCollection("myCollection") + .find(norQuery); + } + + public FindIterable norQueryFromAMethodCallWithFieldNameFromMethodCall() { + return client.getDatabase("myDatabase") + .getCollection("myCollection") + .find(getNorQueryWithFieldNameFromMethodCall()); + } + + private Bson getNorQueryWithFieldNameFromMethodCall() { + return nor( + eq(getFieldName(), "123") + ); + } } """, ) - fun `shows an inspection when a field, referenced in a nested $and query, does not exists in the current namespace`( + fun `shows an inspection when a field, referenced in different forms of a nested $and, $or and $nor query, does not exists in the current namespace`( fixture: CodeInsightTestFixture, ) { val (dataSource, readModelProvider) = fixture.setupConnection() diff --git a/packages/mongodb-dialects/java-driver/src/main/kotlin/com/mongodb/jbplugin/dialects/javadriver/glossary/JavaDriverDialectParser.kt b/packages/mongodb-dialects/java-driver/src/main/kotlin/com/mongodb/jbplugin/dialects/javadriver/glossary/JavaDriverDialectParser.kt index c2912c3c..cea4bf71 100644 --- a/packages/mongodb-dialects/java-driver/src/main/kotlin/com/mongodb/jbplugin/dialects/javadriver/glossary/JavaDriverDialectParser.kt +++ b/packages/mongodb-dialects/java-driver/src/main/kotlin/com/mongodb/jbplugin/dialects/javadriver/glossary/JavaDriverDialectParser.kt @@ -289,8 +289,19 @@ object JavaDriverDialectParser : DialectParser { HasValueReference(valueReference) ), ) - } else if (method.isVarArgs || method.name == "not") { - // Filters.and, Filters.or... are varargs + } else if (method.name == "and" || method.name == "or" || method.name == "nor") { + return Node( + filter, + listOf( + Named(Name.from(method.name)), + HasFilter( + filter.getVarArgsOrIterableArgs() + .mapNotNull { resolveBsonBuilderCall(it, FILTERS_FQN) } + .mapNotNull { parseFilterExpression(it) }, + ), + ), + ) + } else if (method.name == "not") { return Node( filter, listOf( diff --git a/packages/mongodb-dialects/java-driver/src/test/kotlin/com/mongodb/jbplugin/dialects/javadriver/glossary/JavaDriverDialectParserTest.kt b/packages/mongodb-dialects/java-driver/src/test/kotlin/com/mongodb/jbplugin/dialects/javadriver/glossary/JavaDriverDialectParserTest.kt index b83661d4..cb3aeb4c 100644 --- a/packages/mongodb-dialects/java-driver/src/test/kotlin/com/mongodb/jbplugin/dialects/javadriver/glossary/JavaDriverDialectParserTest.kt +++ b/packages/mongodb-dialects/java-driver/src/test/kotlin/com/mongodb/jbplugin/dialects/javadriver/glossary/JavaDriverDialectParserTest.kt @@ -586,7 +586,7 @@ public class Repository { } """, ) - fun `supports vararg operators`(psiFile: PsiFile) { + fun `supports filters built with Filters#and using varargs arguments`(psiFile: PsiFile) { val query = psiFile.getQueryAtMethod("Repository", "findReleasedBooks") val parsedQuery = JavaDriverDialect.parser.parse(query) @@ -652,7 +652,9 @@ public class Repository { } """, ) - fun `supports vararg operators with parameterised queries in variables`(psiFile: PsiFile) { + fun `supports filters built with Filters#and using varargs arguments passed as a variable`( + psiFile: PsiFile + ) { val query = psiFile.getQueryAtMethod("Repository", "findReleasedBooks") val parsedQuery = JavaDriverDialect.parser.parse(query) @@ -698,6 +700,484 @@ public class Repository { import com.mongodb.client.FindIterable; import com.mongodb.client.MongoClient; import org.bson.Document; +import org.bson.conversions.Bson; +import static com.mongodb.client.model.Filters.*; + +public class Repository { + private final MongoClient client; + + public Repository(MongoClient client) { + this.client = client; + } + + private Bson getAnd() { + var released = eq("released", true); + var notHidden = eq("hidden", false); + return and(released, notHidden); + } + + public FindIterable findReleasedBooks() { + return client.getDatabase("myDatabase") + .getCollection("myCollection") + .find(getAnd()); + } +} + """, + ) + fun `supports filters built with Filters#and using varargs arguments returned from a method call`( + psiFile: PsiFile + ) { + val query = psiFile.getQueryAtMethod("Repository", "findReleasedBooks") + val parsedQuery = JavaDriverDialect.parser.parse(query) + + val hasChildren = + parsedQuery.component>()!! + + val and = hasChildren.children[0] + assertEquals(Name.AND, and.component()!!.name) + val andChildren = and.component>()!! + + val firstEq = andChildren.children[0] + assertEquals( + "released", + (firstEq.component>()!!.reference as HasFieldReference.FromSchema).fieldName, + ) + assertEquals( + BsonAnyOf(BsonNull, BsonBoolean), + (firstEq.component>()!!.reference as HasValueReference.Constant).type, + ) + assertEquals( + true, + (firstEq.component>()!!.reference as HasValueReference.Constant).value + ) + + val secondEq = andChildren.children[1] + assertEquals( + "hidden", + (secondEq.component>()!!.reference as HasFieldReference.FromSchema).fieldName, + ) + assertEquals( + BsonAnyOf(BsonNull, BsonBoolean), + (secondEq.component>()!!.reference as HasValueReference.Constant).type, + ) + assertEquals( + false, + (secondEq.component>()!!.reference as HasValueReference.Constant).value + ) + } + + @ParsingTest( + fileName = "Repository.java", + value = """ +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoClient; +import org.bson.Document; +import static com.mongodb.client.model.Filters.*; + +public class Repository { + private final MongoClient client; + + public Repository(MongoClient client) { + this.client = client; + } + + public FindIterable findReleasedBooks() { + return client.getDatabase("myDatabase") + .getCollection("myCollection") + .find(or(eq("released", true), eq("hidden", false))); + } +} + """, + ) + fun `supports filters built with Filters#or using varargs arguments`(psiFile: PsiFile) { + val query = psiFile.getQueryAtMethod("Repository", "findReleasedBooks") + val parsedQuery = JavaDriverDialect.parser.parse(query) + + val hasFilter = parsedQuery.component>()!! + + val and = hasFilter.children[0] + assertEquals(Name.OR, and.component()!!.name) + val andChildren = and.component>()!! + + val firstEq = andChildren.children[0] + assertEquals( + "released", + (firstEq.component>()!!.reference as HasFieldReference.FromSchema).fieldName, + ) + assertEquals( + BsonAnyOf(BsonNull, BsonBoolean), + (firstEq.component>()!!.reference as HasValueReference.Constant).type, + ) + assertEquals( + true, + (firstEq.component>()!!.reference as HasValueReference.Constant).value + ) + + val secondEq = andChildren.children[1] + assertEquals( + "hidden", + (secondEq.component>()!!.reference as HasFieldReference.FromSchema).fieldName, + ) + assertEquals( + BsonAnyOf(BsonNull, BsonBoolean), + (secondEq.component>()!!.reference as HasValueReference.Constant).type, + ) + assertEquals( + false, + (secondEq.component>()!!.reference as HasValueReference.Constant).value + ) + } + + @ParsingTest( + fileName = "Repository.java", + value = """ +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoClient; +import org.bson.Document; +import static com.mongodb.client.model.Filters.*; + +public class Repository { + private final MongoClient client; + + public Repository(MongoClient client) { + this.client = client; + } + + public FindIterable findReleasedBooks() { + var released = eq("released", true); + var notHidden = eq("hidden", false); + var query = or(released, notHidden); + + return client.getDatabase("myDatabase") + .getCollection("myCollection") + .find(query); + } +} + """, + ) + fun `supports filters built with Filters#or using varargs arguments passed as a variable`( + psiFile: PsiFile + ) { + val query = psiFile.getQueryAtMethod("Repository", "findReleasedBooks") + val parsedQuery = JavaDriverDialect.parser.parse(query) + + val hasChildren = + parsedQuery.component>()!! + + val and = hasChildren.children[0] + assertEquals(Name.OR, and.component()!!.name) + val andChildren = and.component>()!! + + val firstEq = andChildren.children[0] + assertEquals( + "released", + (firstEq.component>()!!.reference as HasFieldReference.FromSchema).fieldName, + ) + assertEquals( + BsonAnyOf(BsonNull, BsonBoolean), + (firstEq.component>()!!.reference as HasValueReference.Constant).type, + ) + assertEquals( + true, + (firstEq.component>()!!.reference as HasValueReference.Constant).value + ) + + val secondEq = andChildren.children[1] + assertEquals( + "hidden", + (secondEq.component>()!!.reference as HasFieldReference.FromSchema).fieldName, + ) + assertEquals( + BsonAnyOf(BsonNull, BsonBoolean), + (secondEq.component>()!!.reference as HasValueReference.Constant).type, + ) + assertEquals( + false, + (secondEq.component>()!!.reference as HasValueReference.Constant).value + ) + } + + @ParsingTest( + fileName = "Repository.java", + value = """ +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoClient; +import org.bson.Document; +import org.bson.conversions.Bson; +import static com.mongodb.client.model.Filters.*; + +public class Repository { + private final MongoClient client; + + public Repository(MongoClient client) { + this.client = client; + } + + private Bson getOr() { + var released = eq("released", true); + var notHidden = eq("hidden", false); + return or(released, notHidden); + } + + public FindIterable findReleasedBooks() { + return client.getDatabase("myDatabase") + .getCollection("myCollection") + .find(getOr()); + } +} + """, + ) + fun `supports filters built with Filters#or using varargs arguments returned from a method call`( + psiFile: PsiFile + ) { + val query = psiFile.getQueryAtMethod("Repository", "findReleasedBooks") + val parsedQuery = JavaDriverDialect.parser.parse(query) + + val hasChildren = + parsedQuery.component>()!! + + val and = hasChildren.children[0] + assertEquals(Name.OR, and.component()!!.name) + val andChildren = and.component>()!! + + val firstEq = andChildren.children[0] + assertEquals( + "released", + (firstEq.component>()!!.reference as HasFieldReference.FromSchema).fieldName, + ) + assertEquals( + BsonAnyOf(BsonNull, BsonBoolean), + (firstEq.component>()!!.reference as HasValueReference.Constant).type, + ) + assertEquals( + true, + (firstEq.component>()!!.reference as HasValueReference.Constant).value + ) + + val secondEq = andChildren.children[1] + assertEquals( + "hidden", + (secondEq.component>()!!.reference as HasFieldReference.FromSchema).fieldName, + ) + assertEquals( + BsonAnyOf(BsonNull, BsonBoolean), + (secondEq.component>()!!.reference as HasValueReference.Constant).type, + ) + assertEquals( + false, + (secondEq.component>()!!.reference as HasValueReference.Constant).value + ) + } + + @ParsingTest( + fileName = "Repository.java", + value = """ +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoClient; +import org.bson.Document; +import static com.mongodb.client.model.Filters.*; + +public class Repository { + private final MongoClient client; + + public Repository(MongoClient client) { + this.client = client; + } + + public FindIterable findReleasedBooks() { + return client.getDatabase("myDatabase") + .getCollection("myCollection") + .find(nor(eq("released", true), eq("hidden", false))); + } +} + """, + ) + fun `supports filters built with Filters#nor using varargs arguments`(psiFile: PsiFile) { + val query = psiFile.getQueryAtMethod("Repository", "findReleasedBooks") + val parsedQuery = JavaDriverDialect.parser.parse(query) + + val hasFilter = parsedQuery.component>()!! + + val and = hasFilter.children[0] + assertEquals(Name.NOR, and.component()!!.name) + val andChildren = and.component>()!! + + val firstEq = andChildren.children[0] + assertEquals( + "released", + (firstEq.component>()!!.reference as HasFieldReference.FromSchema).fieldName, + ) + assertEquals( + BsonAnyOf(BsonNull, BsonBoolean), + (firstEq.component>()!!.reference as HasValueReference.Constant).type, + ) + assertEquals( + true, + (firstEq.component>()!!.reference as HasValueReference.Constant).value + ) + + val secondEq = andChildren.children[1] + assertEquals( + "hidden", + (secondEq.component>()!!.reference as HasFieldReference.FromSchema).fieldName, + ) + assertEquals( + BsonAnyOf(BsonNull, BsonBoolean), + (secondEq.component>()!!.reference as HasValueReference.Constant).type, + ) + assertEquals( + false, + (secondEq.component>()!!.reference as HasValueReference.Constant).value + ) + } + + @ParsingTest( + fileName = "Repository.java", + value = """ +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoClient; +import org.bson.Document; +import static com.mongodb.client.model.Filters.*; + +public class Repository { + private final MongoClient client; + + public Repository(MongoClient client) { + this.client = client; + } + + public FindIterable findReleasedBooks() { + var released = eq("released", true); + var notHidden = eq("hidden", false); + var query = nor(released, notHidden); + + return client.getDatabase("myDatabase") + .getCollection("myCollection") + .find(query); + } +} + """, + ) + fun `supports filters built with Filters#nor using varargs arguments passed as a variable`( + psiFile: PsiFile + ) { + val query = psiFile.getQueryAtMethod("Repository", "findReleasedBooks") + val parsedQuery = JavaDriverDialect.parser.parse(query) + + val hasChildren = + parsedQuery.component>()!! + + val and = hasChildren.children[0] + assertEquals(Name.NOR, and.component()!!.name) + val andChildren = and.component>()!! + + val firstEq = andChildren.children[0] + assertEquals( + "released", + (firstEq.component>()!!.reference as HasFieldReference.FromSchema).fieldName, + ) + assertEquals( + BsonAnyOf(BsonNull, BsonBoolean), + (firstEq.component>()!!.reference as HasValueReference.Constant).type, + ) + assertEquals( + true, + (firstEq.component>()!!.reference as HasValueReference.Constant).value + ) + + val secondEq = andChildren.children[1] + assertEquals( + "hidden", + (secondEq.component>()!!.reference as HasFieldReference.FromSchema).fieldName, + ) + assertEquals( + BsonAnyOf(BsonNull, BsonBoolean), + (secondEq.component>()!!.reference as HasValueReference.Constant).type, + ) + assertEquals( + false, + (secondEq.component>()!!.reference as HasValueReference.Constant).value + ) + } + + @ParsingTest( + fileName = "Repository.java", + value = """ +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoClient; +import org.bson.Document; +import org.bson.conversions.Bson; +import static com.mongodb.client.model.Filters.*; + +public class Repository { + private final MongoClient client; + + public Repository(MongoClient client) { + this.client = client; + } + + private Bson getNor() { + var released = eq("released", true); + var notHidden = eq("hidden", false); + return nor(released, notHidden); + } + + public FindIterable findReleasedBooks() { + return client.getDatabase("myDatabase") + .getCollection("myCollection") + .find(getNor()); + } +} + """, + ) + fun `supports filters built with Filters#nor using varargs arguments returned from a method call`( + psiFile: PsiFile + ) { + val query = psiFile.getQueryAtMethod("Repository", "findReleasedBooks") + val parsedQuery = JavaDriverDialect.parser.parse(query) + + val hasChildren = + parsedQuery.component>()!! + + val and = hasChildren.children[0] + assertEquals(Name.NOR, and.component()!!.name) + val andChildren = and.component>()!! + + val firstEq = andChildren.children[0] + assertEquals( + "released", + (firstEq.component>()!!.reference as HasFieldReference.FromSchema).fieldName, + ) + assertEquals( + BsonAnyOf(BsonNull, BsonBoolean), + (firstEq.component>()!!.reference as HasValueReference.Constant).type, + ) + assertEquals( + true, + (firstEq.component>()!!.reference as HasValueReference.Constant).value + ) + + val secondEq = andChildren.children[1] + assertEquals( + "hidden", + (secondEq.component>()!!.reference as HasFieldReference.FromSchema).fieldName, + ) + assertEquals( + BsonAnyOf(BsonNull, BsonBoolean), + (secondEq.component>()!!.reference as HasValueReference.Constant).type, + ) + assertEquals( + false, + (secondEq.component>()!!.reference as HasValueReference.Constant).value + ) + } + + @ParsingTest( + fileName = "Repository.java", + value = """ +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoClient; +import org.bson.Document; import static com.mongodb.client.model.Filters.*; public class Repository {