spring-projects / spring-data-mongodb

Provides support to increase developer productivity in Java when using MongoDB. Uses familiar Spring concepts such as a template classes for core API usage and lightweight repository style data access.
https://spring.io/projects/spring-data-mongodb/
Apache License 2.0
1.62k stars 1.09k forks source link

Criteria.expr generated weird query #4793

Closed ichromanrd closed 1 month ago

ichromanrd commented 1 month ago

Hi, I have this query

db.collection.find({ 
  $expr: {
    $lt: [
      { $toLong: '$fieldToBeConverted' },
      1000
    ]
  } 
})

This is the repository code

val expr = Lt.valueOf(
  ToLong.toLong("$fieldTobeConverted")
).lessThanValue(1000)

val criteria = Criteria.expr(expr)

mongoTemplate.find(Query().addCriteria(criteria), Dto::class.java)

turned on the debug mode, and query generated as:

{
  "$lt": [
    "$$expr",
    [
      {
        "$toLong": "$fieldToBeConverted"
      },
      123
    ]
  ]
}

Is this expected? or am I doing it wrong?

ichromanrd commented 1 month ago

Spring Data Mongo version 4.2.4

christophstrobl commented 1 month ago

Both 4.2.x and main branch QueryMapper renders the above as {"$expr": {"$lt": [{"$toLong": "$fieldTobeConverted"}, 1000]}}.

I might be missing something here, so please take the time to provide a complete minimal sample (something that we can unzip or git clone, build, and deploy) that reproduces the problem.

ichromanrd commented 1 month ago

Hi @christophstrobl apologize, seems I gave wrong example. Actually I tried to do update using aggregation pipeline.

basically something like this query:

db.collection.findAndModify({
query: { _id: '1000' },
update: [
  {
    "$set": {
      "fieldToUpdate": {
        "$cond": {
          "if": {
            "$and": [
              {
                "$lte": [
                  "$lastEventDate",
                  {
                    "$date": "2024-09-25T04:25:51.389Z"
                  }
                ]
              },
              {
                "$lt": [
                  {
                    "$toLong": "$lastTrxId"
                  },
                  1000
                ]
              }
            ]
          },
          "then": "1201",
          "else": "$fieldToUpdate"
        }
      }
    }
  }
  ]
],
new: true
})

Initially I was using this code:

val criteriaList = val criteriaList = mutableListOf(
  Criteria.where(PropertyName.lastUpdated).lte(eventDate),
)

lastNumericalId?.toLongOrNull()?.let {
  val c2 = Criteria.expr {
    ComparisonOperators.Lt.valueOf(
      ConvertOperators.ToLong.toLong("numericalStringField")
    ).lessThan(it)
  }

  criteriaList.add(c2)
}

val condition = Criteria().andOperator(*criteriaList.toTypedArray())

val update = AggregationUpdate.update()
val setOperation = SetOperation(
  "fieldToUpdate",
  ConditionalOperators
    .`when`(condition)
    .then(value)
    .otherwiseValueOf(propertyName),
)
update.set(setOperation)

mongoTemplate.findAndModify(
  Query.query(Criteria.where("_id").`is`("1234"),
  update,
  FindAndModifyOptions.options().upsert(true).returnNew(true),
  TheCollectionDto::class.java,
)

and it's not working. But I've found a solution. ConditionalOperators have another when accepting AggregationExpression, and that fits my case. Here is the updated code:

val criteriaList: MutableList<AggregationExpression> = mutableListOf(
  Lte.valueOf(PropertyName.lastEventDate).lessThanEqualToValue(eventDate),
)

lastTrxId?.toLongOrNull()?.let { lastTrxIdNumerical ->
    val expr = Lt.valueOf(
        ToLong.toLong("\$lastTransactionId"),
    ).lessThanValue(lastTrxIdNumerical)
    criteriaList.add(expr)
}

val condition = And.and(*criteriaList.toTypedArray())

val update = AggregationUpdate.update()
val setOperation = SetOperation(
  "fieldToUpdate",
  ConditionalOperators
    .`when`(condition)
    .then(value)
    .otherwiseValueOf(propertyName),
)
update.set(setOperation)

mongoTemplate.findAndModify(
  Query.query(Criteria.where("_id").`is`("1234"),
  update,
  FindAndModifyOptions.options().upsert(true).returnNew(true),
  TheCollectionDto::class.java,
)