[JAVA-3501] Searching by UUID works incorrectly Created: 05/Nov/19  Updated: 27/Oct/23  Resolved: 06/Nov/19

Status: Closed
Project: Java Driver
Component/s: JSON
Affects Version/s: None
Fix Version/s: None

Type: Improvement Priority: Major - P3
Reporter: Anton Pozdeev Assignee: Jeffrey Yemin
Resolution: Works as Designed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

Now we use Spring Boot v.2.1.3 and faced with problem associated with searching by UUID in mongodb.

Before(in Spring Boot v.1.5.8) we executed the next code: 

Lists.newArrayList(mongoTemplate.getCollection("entities")
 .distinct("name",
 new BasicQuery("\{\"_id\":{\"$uuid\": \"0021cf0a-9094-49ad-b716-c9c019134bbe\" }}").getQueryObject()));

and we got the correct result,

But now this approach doesn't work

I write:

Lists.newArrayList(mongoTemplate.getCollection("entities")
 .distinct("name", new BasicQuery(new Document("_id", "\{\"$uuid\": \"0021cf0a-9094-49ad-b716-c9c019134bbe\" }")).getQueryObject(), String.class));

and we get the error

Command failed with error 2 (BadValue): 'unknown operator: $uuid' on server localhost:27017. The full response is \{ "ok" : 0.0, "errmsg" : "unknown operator: $uuid", "code" : 2, "codeName" : "BadValue" }

I saw the ticket https://jira.spring.io/browse/DATAMONGO-2029, but It doesn't help us

 

I have created the ticket DATAMONGO-2397 , and now we use deprecated com.mongodb.util.JSON API, for example:

DBObject legacy = (DBObject) JSON.parse("{\"_id\":{\"$uuid\": \"0021cf0a-9094-49ad-b716-c9c019134bbe\" }}");
org.bson.Document filter = new org.bson.Document(legacy.toMap());
 
template.getCollection("entities")
    .distinct("firstName",filter,String.class)

but it is deprecated API, can you add $uuid parsing to the JsonReader, as it was suggested in the comment



 Comments   
Comment by Jeffrey Yemin [ 06/Nov/19 ]

Hi anton-pozdeev@mail.ru

For that you're going to need to write some code. You can parse that string, but for each $uuid in the JSON representation you'll end up with a nested document with a single field named "$uuid" whose value is the hex representation of the UUID. You'll need to recursively process that document, replacing each one matching that pattern with the corresponding UUID.

It would look something like this:

import org.bson.BsonArray;
import org.bson.BsonBinary;
import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.bson.UuidRepresentation;
import org.bson.json.JsonWriterSettings;
 
import java.util.UUID;
 
public class JAVA3501 {
    public static void main(String[] args) {
        var json = "{\"$or\":[{\"id\":{\"$in\":[" +
                "{\"$uuid\":\"cda3839c-da40-46dd-a4ee-93928772422b\"}," +
                "{\"$uuid\":\"6f69a6b7-8ae7-4cc0-a3cc-0cd2bf828f73\"}]}}," +
                "{\"id\":{\"$in\":[" +
                "{\"$uuid\":\"fd3e2448-27cf-4bf0-aeed-a0f96a5fe932\"}," +
                "{\"$uuid\":\"57cb4c75-13c5-4f62-acb6-9a8459734511\"}]}}]}";
 
        BsonDocument query = BsonDocument.parse(json);
 
        System.out.println(query.toJson(JsonWriterSettings.builder().indent(true).build()));
 
        System.out.println(fixUpUuid(query).asDocument().toJson(JsonWriterSettings.builder().indent(true).build()));
    }
 
    static BsonValue fixUpUuid(final BsonDocument document) {
        if (document.size() == 1 && document.containsKey("$uuid")) {
            return new BsonBinary(
                    UUID.fromString(document.getString("$uuid").getValue()),
                    UuidRepresentation.JAVA_LEGACY);
        }
 
        for (String key: document.keySet()) {
            BsonValue value = document.get(key);
            if (value.isDocument()) {
                document.put(key, fixUpUuid(value.asDocument()));
            } else if (value.isArray()) {
                document.put(key, fixUpUuid(value.asArray()));
            }
        }
 
        return document;
    }
 
    static private BsonArray fixUpUuid(final BsonArray array) {
         for (int i = 0; i < array.size(); i++) {
             BsonValue value = array.get(i);
             if (value.isDocument()) {
                 array.set(i, fixUpUuid(value.asDocument()));
             } else if (value.isArray()) {
                 array.set(i, fixUpUuid(value.asArray()));
             }
         }
         return array;
    }
}

I'm using BsonDocument instead of Document here because it's a bit easier to reason about its closed type system (Map<String, BsonValue> instead of Map<String, Object>).

Comment by Anton Pozdeev [ 06/Nov/19 ]

Hello, thanks for your answer, but we have the next situation, maybe you know how to resolve it.
Оn input we get a string:

{"$or":[{"id":{"$in":[{"$uuid":"cda3839c-da40-46dd-a4ee-93928772422b"},{"$uuid":"6f69a6b7-8ae7-4cc0-a3cc-0cd2bf828f73"}]}},{"id":{"$in":[{"$uuid":"fd3e2448-27cf-4bf0-aeed-a0f96a5fe932"},{"$uuid":"57cb4c75-13c5-4f62-acb6-9a8459734511"}]}}]}

and previously, we set it into new BasicQuery("string") and it works correctly, but maybe you can suggest working approach for solving current problem

Comment by Jeffrey Yemin [ 05/Nov/19 ]

Hi anton-pozdeev@mail.ru

We don't plan to re-introduce $uuid support to the Java driver's JSON parser. This was a non-standard addition in the old driver, and no other driver supports it. Instead, all drivers now support the same JSON representation, which is specified here.

I can offer two alternatives, both illustrated in this example:

        var uuid = UUID.fromString("0021cf0a-9094-49ad-b716-c9c019134bbe");
        
        // 1. Continue to parse JSON.  Use the standard JSON representation of UUIDs, which is represented as a subtype 3 BSON binary.
        var jsonParsedQuery = Document.parse(String.format("{_id: {$binary: { base64: \"%s\", subType: \"03\" } } }",
                Base64.getEncoder().encodeToString(new BsonBinary(uuid, UuidRepresentation.JAVA_LEGACY).getData())));
        
        // 2.  Replace JSON parsing with direct use of org.bson.Document
        var documentQuery = new Document("_id", uuid);

Be careful about the UUID representation, as cstrobl mentioned in DATAMONGO-2397 . There are incompatibilities with different drivers with regard to the byte order to which the UUID is serialized. That's why Example 1 explicitly specifies UuidRepresentation.JAVA_LEGACY

Generated at Thu Feb 08 08:59:45 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.