From 3d3b60ab5fc1976b9209eec245763d11aa0e7ef7 Mon Sep 17 00:00:00 2001 From: QuadStingray Date: Mon, 3 Feb 2025 08:26:08 +0100 Subject: [PATCH] fix: lucene query some cases not negates closes#71 --- build.sbt | 2 +- .../mongodb/lucene/LuceneQueryConverter.scala | 69 +++++++++++-------- .../mongodb/lucene/LuceneSearchSuite.scala | 10 +++ 3 files changed, 53 insertions(+), 28 deletions(-) diff --git a/build.sbt b/build.sbt index 34d68db8..67bdfa49 100644 --- a/build.sbt +++ b/build.sbt @@ -43,7 +43,7 @@ licenses += ("Apache-2.0", url("https://www.apache.org/licenses/LICENSE-2.0.html //crossScalaVersions := Seq("2.13.16") crossScalaVersions := Seq("3.6.0", "2.13.16") -scalaVersion := crossScalaVersions.value.last +scalaVersion := crossScalaVersions.value.head scalacOptions += "-deprecation" diff --git a/src/main/scala/dev/mongocamp/driver/mongodb/lucene/LuceneQueryConverter.scala b/src/main/scala/dev/mongocamp/driver/mongodb/lucene/LuceneQueryConverter.scala index 166e2d2e..df9fe8e5 100644 --- a/src/main/scala/dev/mongocamp/driver/mongodb/lucene/LuceneQueryConverter.scala +++ b/src/main/scala/dev/mongocamp/driver/mongodb/lucene/LuceneQueryConverter.scala @@ -4,8 +4,8 @@ import com.typesafe.scalalogging.LazyLogging import dev.mongocamp.driver.mongodb._ import dev.mongocamp.driver.mongodb.exception.NotSupportedException import org.apache.lucene.queryparser.classic.QueryParser -import org.apache.lucene.search._ import org.apache.lucene.search.BooleanClause.Occur +import org.apache.lucene.search._ import org.joda.time.DateTime import org.mongodb.scala.bson.conversions.Bson @@ -35,7 +35,7 @@ object LuceneQueryConverter extends LazyLogging { private def getMongoDbSearchMap(query: Query, negated: Boolean, searchWithValueAndString: Boolean): Map[String, Any] = { val searchMapResponse = mutable.Map[String, Any]() query match { - case booleanQuery: BooleanQuery => appendBooleanQueryToSearchMap(searchMapResponse, booleanQuery, searchWithValueAndString) + case booleanQuery: BooleanQuery => appendBooleanQueryToSearchMap(searchMapResponse, booleanQuery, searchWithValueAndString, negated) case termRangeQuery: TermRangeQuery => appendTermRangeQueryToSearchMap(negated, searchMapResponse, termRangeQuery, searchWithValueAndString) case termQuery: TermQuery => appendTermQueryToSearchMap(negated, searchMapResponse, termQuery, searchWithValueAndString) case query: PrefixQuery => appendPrefixQueryToSearchMap(negated, searchMapResponse, query) @@ -53,14 +53,17 @@ object LuceneQueryConverter extends LazyLogging { private def appendBooleanQueryToSearchMap( searchMapResponse: mutable.Map[String, Any], booleanQuery: BooleanQuery, - searchWithValueAndString: Boolean + searchWithValueAndString: Boolean, + negate: Boolean ): Unit = { val subQueries = booleanQuery.clauses().asScala val listOfAnd = ArrayBuffer[Map[String, Any]]() val listOfOr = ArrayBuffer[Map[String, Any]]() + val listOfNOr = ArrayBuffer[Map[String, Any]]() var nextTypeAnd = true subQueries.foreach(c => { - val queryMap = getMongoDbSearchMap(c.query(), c.isProhibited, searchWithValueAndString) + val negateSubquery = (c.occur() == Occur.MUST_NOT) + val queryMap = getMongoDbSearchMap(c.query(), negateSubquery, searchWithValueAndString) var thisTypeAnd = true if (c.occur == Occur.MUST) { @@ -87,10 +90,18 @@ object LuceneQueryConverter extends LazyLogging { }) if (listOfAnd.nonEmpty) { - searchMapResponse.put("$and", listOfAnd.toList) + if (negate) { + searchMapResponse.put("$nor", listOfAnd.toList) + } else { + searchMapResponse.put("$and", listOfAnd.toList) + } } if (listOfOr.nonEmpty) { - searchMapResponse.put("$or", listOfOr.toList) + if (negate) { + searchMapResponse.put("$nor", listOfOr.toList) + } else { + searchMapResponse.put("$or", listOfOr.toList) + } } } @@ -186,15 +197,17 @@ object LuceneQueryConverter extends LazyLogging { } private def appendPhraseQueryToSearchMap(negated: Boolean, searchMapResponse: mutable.Map[String, Any], query: PhraseQuery): Unit = { - val listOfSearches = query.getTerms.map(term => { - val convertedValue = checkAndConvertValue(term.text()) - if (convertedValue.isInstanceOf[String]) { - Map(term.field() -> generateRegexQuery(s"(.*?)$convertedValue(.*?)", "i")) - } - else { - Map(term.field() -> Map("$eq" -> convertedValue)) - } - }).toList + val listOfSearches = query.getTerms + .map(term => { + val convertedValue = checkAndConvertValue(term.text()) + if (convertedValue.isInstanceOf[String]) { + Map(term.field() -> generateRegexQuery(s"(.*?)$convertedValue(.*?)", "i")) + } + else { + Map(term.field() -> Map("$eq" -> convertedValue)) + } + }) + .toList if (negated) { searchMapResponse.put("$nor", listOfSearches) } @@ -228,18 +241,20 @@ object LuceneQueryConverter extends LazyLogging { val convertedValue: Option[Any] = (List() ++ checkOrReturn(() => s.toDouble) ++ checkOrReturn(() => s.toLong) ++ checkOrReturn(() => s.toBoolean)).headOption val response = convertedValue.getOrElse({ - val parsedOptions: List[Date] = Try(new DateTime(s).toDate).toOption.toList ++ datePatters.flatMap(pattern => { - try { - val formatter = new SimpleDateFormat(pattern) - val r = Option(formatter.parse(s)) - logger.info(s"parsed date $s with pattern $pattern to $r") - r - } - catch { - case e: Exception => - None - } - }).distinct + val parsedOptions: List[Date] = Try(new DateTime(s).toDate).toOption.toList ++ datePatters + .flatMap(pattern => { + try { + val formatter = new SimpleDateFormat(pattern) + val r = Option(formatter.parse(s)) + logger.info(s"parsed date $s with pattern $pattern to $r") + r + } + catch { + case e: Exception => + None + } + }) + .distinct parsedOptions.headOption.getOrElse(s) }) response diff --git a/src/test/scala/dev/mongocamp/driver/mongodb/lucene/LuceneSearchSuite.scala b/src/test/scala/dev/mongocamp/driver/mongodb/lucene/LuceneSearchSuite.scala index e34c4ab6..7295d44c 100644 --- a/src/test/scala/dev/mongocamp/driver/mongodb/lucene/LuceneSearchSuite.scala +++ b/src/test/scala/dev/mongocamp/driver/mongodb/lucene/LuceneSearchSuite.scala @@ -3,6 +3,7 @@ package dev.mongocamp.driver.mongodb.lucene import dev.mongocamp.driver.mongodb._ import dev.mongocamp.driver.mongodb.dao.BasePersonSuite import dev.mongocamp.driver.mongodb.test.TestDatabase._ +import org.mongodb.scala.Document import java.util.TimeZone @@ -135,4 +136,13 @@ class LuceneSearchSuite extends BasePersonSuite { assertEquals(search.size, 199) } + test("negate query with values in braces") { + val luceneQuery = LuceneQueryConverter.parse("NOT fieldName:('value1' OR 'value2' OR 'value2')", "ube") + val document = LuceneQueryConverter.toDocument(luceneQuery) + assertEquals("{\"$and\": [{\"$nor\": [{\"fieldName\": {\"$eq\": \"value1\"}}, {\"fieldName\": {\"$eq\": \"value2\"}}, {\"fieldName\": {\"$eq\": \"value2\"}}]}]}", document.asInstanceOf[Document].toJson()) + val luceneQuery2 = LuceneQueryConverter.parse("NOT fieldName:('value1' AND 'value2' AND 'value2')", "ube") + val document2 = LuceneQueryConverter.toDocument(luceneQuery2) + assertEquals("{\"$and\": [{\"$nor\": [{\"fieldName\": {\"$eq\": \"value1\"}}, {\"fieldName\": {\"$eq\": \"value2\"}}, {\"fieldName\": {\"$eq\": \"value2\"}}]}]}", document2.asInstanceOf[Document].toJson()) + } + }