How can I find documents in MongoDB by UUID type field using the Delphi driver?

124 Views Asked by At

can someone tell me how to search for MongoDB collection documents by the value of the _id field, if this field is of type UUID? I can't figure out how to use the Delphi driver to transfer the value of the desired UUID to the server? Therefore, I can't implement the simplest MongoDb query like

db.MongoCollection.Find({_id: UUID("bb909281-e6f1-41c6-89f7-2b55f749278c")}). 

All the search options I know involve sending the query as a JSON string, and either strings, or numbers, or boolean values, etc. are allowed there. I can only write from Delphi like this:

procedure GetSomeDataFromMongoDbCollectionByUuidDocField;
var
  MongoDbContr: string;
  FDMongoCon: TFDConnection;
  MongoCon: TMongoConnection;
  MongoCursor: IMongoCursor;
begin
  FDMongoCon := nil;
  MongoCursor := nil;
  
  try
    MongoDbConStr := 'DriverID=Mongo;Server=MyMongoDbServer;Port=27017;User_Name=MyMongoDbServerLogin;Password=MyMongoDbServerPassword';
  
    FDMongoCon := TFDConnection.Create(nil);
    FDMongoCon.ConnectionString := MongoDbConStr;
    FDMongoCon.LoginPrompt := false;
    FDMongoCon.Open;
  
    MongoCon := TMongoConnection(FDMongoCon.CliObj);
  
    MongoCursor := MongoCon['MongoDataBase']['MongoCollection'].Find()
      .Match()
      .Add('_id', 'UUID("bb909281-e6f1-41c6-89f7-2b55f749278c")')
    .&End;
  
    if MongoCursor <> nil
      then if MongoCursor.IsAlive
        then while MongoCursor.Next do
          begin
            // Processing results
          end;
  finally
    if FDMongoCon <> nil
    then
      With FDMongoCon do
      begin
        if InTransaction
         then Rollback;
        if Connected
          then close;
        Free;
      end;
  end;
end;

But this query does not return anything, since a string is passed as a parameter there, which, when compared with a binary value, naturally does not match it.

Using MongoDB operators executing JS code on the server side, the request can be written as follows:

MongoCursor := MongoCon['MongoDataBase']['MongoCollection'].Find()
  .Match()
  .Add('$where', 'function(){ return(this._id == ''UUID("bb909281-e6f1-41c6-89f7-2b55f749278c")'' ) }')
.&End;

This query works and returns what you need. You can write it using the $expr operator, but in this case it will turn out to be more cumbersome. But in both cases, query options using JS work very slowly on collections with a large number of documents, which I have encountered in practice, and which is confirmed by the MongoDB documentation, which does not recommend using JS queries when fetching data precisely because of their low performance. That's why I'm trying to figure out if there is a way to request data by UUID field, not using JS, but through pipeline MongoDB operators, where I can't pass the desired UUID value in the form necessary for the server. I tried many different options, including converting to Base64 and passing the value as Bytes, but nothing helped.

When I get the UUID field data from the query results, the driver returns it to me in this form:

{
"_id": {"$binary": "u5CSgebxQcaJ9ytV90knjA==",
            "subType": "0"
            }
}

Therefore, I get the value along the path "_id.$binary" (there is a base64 representation of the bytes of the UUID string) and then translate it into the usual hexadecimal representation with the following code:

TGUID.Create(TNetEncoding.Base64.DecodeStringToBytes('u5CSgebxQcaJ9ytV90knjA=='), TEndian.Big).ToString.Trim(['{', '}']);

But on the contrary, I can't figure out how to pass the value of the UUID string to the request. As I said before, I have query options where I passed the value as an array of bytes, but they also do not return anything:

procedure GetSomeDataFromMongoDbCollectionByUuidDocField;
var
  MongoDbContr: string;
  FDMongoCon: TFDConnection;
  MongoCon: TMongoConnection;
  MongoCursor: IMongoCursor;
  UUIDBytes: TBytes;
begin
  FDMongoCon := nil;
  MongoCursor := nil;
  UUIDBytes := nil;
  
  try
    MongoDbConStr := 'DriverID=Mongo;Server=MyMongoDbServer;Port=27017;User_Name=MyMongoDbServerLogin;Password=MyMongoDbServerPassword';
  
    FDMongoCon := TFDConnection.Create(nil);
    FDMongoCon.ConnectionString := MongoDbConStr;
    FDMongoCon.LoginPrompt := false;
    FDMongoCon.Open;
  
    MongoCon := TMongoConnection(FDMongoCon.CliObj);
  
    UUIDBytes := UUIDStringToBytes('bb909281-e6f1-41c6-89f7-2b55f749278c', TEndian.Big); // Function returns this TBytes (187, 144, 146, 129, 230, 241, 65, 198, 137, 247, 43, 85, 247, 73, 39, 140)
    
    MongoCursor := MongoCon['MongoDataBase']['MongoCollection'].Find()
      .Match()
      .Add('_id', UUIDBytes, TJSONBinaryType.UUID) // All other possible values of TJSONBinaryType have not changed anything
    .&End;
  
    if MongoCursor <> nil
      then if MongoCursor.IsAlive
        then while MongoCursor.Next do
          begin
            // Processing results
          end;
  finally
    if UUIDBytes <> nil
      then if Length(UUIDBytes) > 0
        then SetLength(UUIDBytes, 0);
    
    if FDMongoCon <> nil
    then
      With FDMongoCon do
      begin
        if InTransaction
         then Rollback;
        if Connected
          then close;
        Free;
      end;
  end;
end;

Here is the code of the UUIDStringToBytes function:

function UUIDStringToBytes(UUIDStr: string; DataEndian: TEndian = TEndian.Big): TBytes;
var
  Str1, Str2, Str3, Str4, Str5, ByteStr: string;
  StrArray: TArray<string>;
  i, StrNumber: integer;
  BytesArray: TBytes;
  Endian: TEndian;
begin
  Result := nil;

  Endian := DataEndian;
  StrArray := nil;
  BytesArray := nil;

  try
    UUIDStr := UUIDStr.Trim;
    UUIDStr := UUIDStr.Trim(['{', '}']);
    UUIDStr := UUIDStr.Replace('-', string.empty, [rfReplaceAll]);
    if UUIDStr.Length <> 32
      then raise Exception.Create('The UUID string has the wrong format!');

    Str1 := UUIDStr.Substring(0, 8);
    Str2 := UUIDStr.Substring(8, 4);
    Str3 := UUIDStr.Substring(12, 4);
    Str4 := UUIDStr.Substring(16, 4);
    Str5 := UUIDStr.Substring(20, 12);

    SetLength(StrArray, 5);
    StrArray[0] := Str1;
    StrArray[1] := Str2;
    StrArray[2] := Str3;
    StrArray[3] := Str4;
    StrArray[4] := Str5;

     for StrNumber := Low(StrArray) to High(StrArray) do
     begin
       if StrArray[StrNumber] = string.empty
         then continue;
       if StrArray[StrNumber].Length < 2
         then continue;

       if StrNumber > 2
         then if Endian <> TEndian.Big
           then Endian := TEndian.Big;
       case Endian of
         Big:
         begin
           i := 0;
           while i + 2 <= StrArray[StrNumber].Length do
           begin
             ByteStr := StrArray[StrNumber].Substring(i, 2);
             if ByteStr = string.empty
               then System.Break;
             if ByteStr.Length < 2
               then System.Break;

             if BytesArray = nil
               then SetLength(BytesArray, 0);
             SetLength(BytesArray, Length(BytesArray) + 1);
             BytesArray[High(BytesArray)] := ('$' + ByteStr).ToInt64;
             Inc(i, 2);
           end;
         end;

         Little:
         begin
           i := StrArray[StrNumber].Length - 2;
           while i >= 0 do
           begin
             ByteStr := StrArray[StrNumber].Substring(i, 2);
             if ByteStr = string.empty
               then System.Break;
             if ByteStr.Length < 2
               then System.Break;

             if BytesArray = nil
               then SetLength(BytesArray, 0);
             SetLength(BytesArray, Length(BytesArray) + 1);
             BytesArray[High(BytesArray)] := ('$' + ByteStr).ToInt64;
             Dec(i, 2);
           end;
         end;
       end;
     end;

     if BytesArray <> nil
       then if Length(BytesArray) > 0
         then Result := Copy(BytesArray);
  finally
    if StrArray <> nil
      then if Length(StrArray) > 0
        then SetLength(StrArray, 0);
    if BytesArray <> nil
      then if Length(BytesArray) > 0
        then SetLength(BytesArray, 0);
  end; // Конец finally
end;

I even tried to form a JSON object from a UUID string using MongoDB Extended JSON (v2) notation (my MongoDB server stores UUID values in exactly the 4th format):

{ "$binary": {
    "base64": "u5CSgebxQcaJ9ytV90knjA==",
    "subType": "04"
    }
}

Then I tried to pass this object to the request as a JSON string, even translated it into BSON (which is an array of bytes) and passed it to the request as an array of bytes, but nothing helped. I tried the same manipulations with a JSON object that returns a driver from the UUID values of fields that does not correspond to the canonical form, according to MongoDB Extended JSON (v2) notation:

{
    "$binary": "u5CSgebxQcaJ9ytV90knjA==",
    "subType": "0"
}

But I have not achieved a positive result. Can someone tell me how to use the MongoDB driver for Delphi to pass the value of the UUID field in the request? I would appreciate any suggestions.Thanks!

0

There are 0 best solutions below