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!