Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dotnet/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.10" />
<PackageVersion Include="DuckDB.NET.Data.Full" Version="1.2.0" />
<PackageVersion Include="DuckDB.NET.Data" Version="1.1.3" />
<PackageVersion Include="MongoDB.Driver" Version="2.30.0" />
<PackageVersion Include="MongoDB.Driver" Version="3.5.1" />
<PackageVersion Include="Microsoft.Graph" Version="5.94.0" />
<PackageVersion Include="Microsoft.OpenApi" Version="1.6.24" />
<PackageVersion Include="Microsoft.OpenApi.Readers" Version="1.6.24" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ public CosmosMongoVectorStoreFixture()
.Build();

var connectionString = GetConnectionString(configuration);
#pragma warning disable CA2000
var client = new MongoClient(connectionString);
#pragma warning restore CA2000

this.MongoDatabase = client.GetDatabase("test");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@ public async Task InitializeAsync()
cts.CancelAfter(TimeSpan.FromSeconds(60));
await this._container.StartAsync(cts.Token);

#pragma warning disable CA2000
var mongoClient = new MongoClient(new MongoClientSettings
{
Server = new MongoServerAddress(this._container.Hostname, this._container.GetMappedPublicPort(MongoDbBuilder.MongoDbPort)),
DirectConnection = true,
});
#pragma warning restore CA2000

this.MongoDatabase = mongoClient.GetDatabase("test");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,20 @@ public BsonDocument MapFromDataToStorageModel(Dictionary<string, object?> dataMo
: keyValue switch
{
string s => s,
Guid g => BsonValue.Create(g),
Guid g => new BsonBinaryData(g, GuidRepresentation.Standard),
ObjectId o => o,
long i => i,
int i => i,

null => throw new InvalidOperationException($"Key property '{model.KeyProperty.ModelName}' is null."),
_ => throw new InvalidCastException($"Key property '{model.KeyProperty.ModelName}' must be a string.")
_ => throw new InvalidCastException($"Key property '{model.KeyProperty.ModelName}' must be a string, Guid, ObjectId, long or int.")
};

foreach (var property in model.DataProperties)
{
if (dataModel.TryGetValue(property.ModelName, out var dataValue))
{
document[property.StorageName] = BsonValue.Create(dataValue);
document[property.StorageName] = BsonValueFactory.Create(dataValue);
}
}

Expand Down Expand Up @@ -167,6 +167,8 @@ Embedding<float> e
Type t when t == typeof(decimal?) => value.AsNullableDecimal,
Type t when t == typeof(DateTime) => value.ToUniversalTime(),
Type t when t == typeof(DateTime?) => value.ToNullableUniversalTime(),
Type t when t == typeof(Guid) => value.AsGuid,
Type t when t == typeof(Guid?) => value.AsNullableGuid,

_ => (object?)null
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Conventions;
using MongoDB.Bson.Serialization.Serializers;

namespace Microsoft.SemanticKernel.Connectors.MongoDB;

Expand Down Expand Up @@ -40,7 +41,8 @@ public MongoMapper(CollectionModel model)

var conventionPack = new ConventionPack
{
new IgnoreExtraElementsConvention(ignoreExtraElements: true)
new IgnoreExtraElementsConvention(ignoreExtraElements: true),
new GuidStandardRepresentationConvention()
};

ConventionRegistry.Register(
Expand Down Expand Up @@ -139,4 +141,15 @@ public TRecord MapFromStorageToDataModel(BsonDocument storageModel, bool include

return BsonSerializer.Deserialize<TRecord>(storageModel);
}

private class GuidStandardRepresentationConvention : ConventionBase, IMemberMapConvention
{
public void Apply(BsonMemberMap memberMap)
{
if (memberMap.MemberType == typeof(Guid) && memberMap.MemberInfo.GetCustomAttribute<BsonRepresentationAttribute>() is null)
{
memberMap.SetSerializer(new GuidSerializer(GuidRepresentation.Standard));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ private static HttpClientHandler CreateHandler()
catch (PlatformNotSupportedException) { } // not supported on older frameworks
return handler;
}
#elif NET462
#elif NET462 || NET472
private static HttpClientHandler CreateHandler()
=> new();
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ internal static class CosmosMongoCollectionSearchMapping
if (filterClause is EqualToFilterClause equalToFilterClause)
{
propertyName = equalToFilterClause.FieldName;
propertyValue = BsonValue.Create(equalToFilterClause.Value);
propertyValue = BsonValueFactory.Create(equalToFilterClause.Value);
filterOperator = EqualOperator;
}
else
Expand Down
5 changes: 4 additions & 1 deletion dotnet/src/VectorData/CosmosMongoDB/CosmosMongoDB.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<!-- THIS PROPERTY GROUP MUST COME FIRST -->
<AssemblyName>Microsoft.SemanticKernel.Connectors.CosmosMongoDB</AssemblyName>
<RootNamespace>$(AssemblyName)</RootNamespace>
<TargetFrameworks>net8.0;netstandard2.0;net462</TargetFrameworks>
<TargetFrameworks>net8.0;net472</TargetFrameworks>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsAotCompatible>
<VersionSuffix>preview</VersionSuffix>
</PropertyGroup>
Expand All @@ -15,6 +15,9 @@

<ItemGroup>
<Compile Include="$(RepoRoot)/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/*.cs" Link="%(RecursiveDir)%(Filename)%(Extension)" />
<Compile Include="..\MongoDB\BsonValueFactory.cs">
<Link>BsonValueFactory.cs</Link>
</Compile>
</ItemGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ private BsonDocument GenerateEqualityComparison(PropertyModel property, object?
// Short form of equality (instead of $eq)
if (nodeType is ExpressionType.Equal)
{
return new BsonDocument { [property.StorageName] = BsonValue.Create(value) };
return new BsonDocument { [property.StorageName] = BsonValueFactory.Create(value) };
}

var filterOperator = nodeType switch
Expand All @@ -95,7 +95,7 @@ private BsonDocument GenerateEqualityComparison(PropertyModel property, object?
_ => throw new UnreachableException()
};

return new BsonDocument { [property.StorageName] = new BsonDocument { [filterOperator] = BsonValue.Create(value) } };
return new BsonDocument { [property.StorageName] = new BsonDocument { [filterOperator] = BsonValueFactory.Create(value) } };
}

private BsonDocument TranslateAndOr(BinaryExpression andOr)
Expand Down Expand Up @@ -257,7 +257,7 @@ BsonDocument ProcessInlineEnumerable(IEnumerable elements, Expression item)
{
[property.StorageName] = new BsonDocument
{
["$in"] = new BsonArray(from object? element in elements select BsonValue.Create(element))
["$in"] = new BsonArray(from object? element in elements select BsonValueFactory.Create(element))
}
};
}
Expand Down
42 changes: 42 additions & 0 deletions dotnet/src/VectorData/MongoDB/BsonValueFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using MongoDB.Bson;

namespace Microsoft.SemanticKernel.Connectors.MongoDB;

/// <summary>
/// A class that constructs the correct BsonValue for a given CLR type.
/// </summary>
internal static class BsonValueFactory
{
/// <summary>
/// Create a BsonValue for the given CLR type.
/// </summary>
/// <param name="value">The CLR object to create a BSON value for.</param>
/// <returns>The appropriate <see cref="BsonValue"/> for that CLR type.</returns>
public static BsonValue Create(object? value)
{
if (value is null)
{
return BsonNull.Value;
}

if (value.GetType().IsArray)
{
if (value is Guid[] guids)
{
return new BsonArray(Array.ConvertAll(guids, x => new BsonBinaryData(x, GuidRepresentation.Standard)));
}

return new BsonArray(value as Array);
}

if (value is Guid guid)
{
return new BsonBinaryData(guid, GuidRepresentation.Standard);
}

return BsonValue.Create(value);
}
}
39 changes: 37 additions & 2 deletions dotnet/src/VectorData/MongoDB/MongoCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.Extensions.VectorData;
using Microsoft.Extensions.VectorData.ProviderServices;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using MEVD = Microsoft.Extensions.VectorData;

Expand Down Expand Up @@ -75,6 +76,9 @@ public class MongoCollection<TKey, TRecord> : VectorStoreCollection<TKey, TRecor
/// <summary>Number of nearest neighbors to use during the vector search.</summary>
private readonly int? _numCandidates;

/// <summary><see cref="BsonSerializationInfo"/> to use for serializing key values.</summary>
private readonly BsonSerializationInfo? _keySerializationInfo;

/// <summary>Types of keys permitted.</summary>
private static readonly Type[] s_validKeyTypes = [typeof(string), typeof(Guid), typeof(ObjectId), typeof(int), typeof(long)];

Expand Down Expand Up @@ -135,6 +139,11 @@ internal MongoCollection(IMongoDatabase mongoDatabase, string name, Func<MongoCo
VectorStoreName = mongoDatabase.DatabaseNamespace?.DatabaseName,
CollectionName = name
};

// Cache the key serialization info if possible
this._keySerializationInfo = typeof(TKey) == typeof(object)
? null
: this.GetKeySerializationInfo();
}

/// <inheritdoc />
Expand Down Expand Up @@ -676,10 +685,36 @@ private async IAsyncEnumerable<VectorSearchResult<TRecord>> EnumerateAndMapSearc
}

private FilterDefinition<BsonDocument> GetFilterById(TKey id)
=> Builders<BsonDocument>.Filter.Eq(MongoConstants.MongoReservedKeyPropertyName, id);
{
// Use cached key serialization info but fall back to BsonValueFactory for dynamic mapper.
var bsonValue = this._keySerializationInfo?.SerializeValue(id) ?? BsonValueFactory.Create(id);
return Builders<BsonDocument>.Filter.Eq(MongoConstants.MongoReservedKeyPropertyName, bsonValue);
}

private FilterDefinition<BsonDocument> GetFilterByIds(IEnumerable<TKey> ids)
=> Builders<BsonDocument>.Filter.In(MongoConstants.MongoReservedKeyPropertyName, ids);
{
// Use cached key serialization info but fall back to BsonValueFactory for dynamic mapper.
var bsonValues = this._keySerializationInfo.SerializeValues(ids) ?? (BsonArray)BsonValueFactory.Create(ids);
return Builders<BsonDocument>.Filter.In(MongoConstants.MongoReservedKeyPropertyName, bsonValues);
}

private BsonSerializationInfo GetKeySerializationInfo()
{
var documentSerializer = BsonSerializer.LookupSerializer<TRecord>();
Verify.NotNull(documentSerializer, $"BsonSerializer not found for type '{typeof(TRecord)}'");

if (documentSerializer is not IBsonDocumentSerializer bsonDocumentSerializer)
{
throw new InvalidOperationException($"BsonSerializer for type '{typeof(TRecord)}' does not implement IBsonDocumentSerializer");
}

if (!bsonDocumentSerializer.TryGetMemberSerializationInfo(this._model.KeyProperty.ModelName, out var keySerializationInfo))
{
throw new InvalidOperationException($"BsonSerializer for type '{typeof(TRecord)}' does not recognize key property {this._model.KeyProperty.ModelName}");
}

return keySerializationInfo;
}

private async Task<bool> InternalCollectionExistsAsync(CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ internal static class MongoCollectionSearchMapping
if (filterClause is EqualToFilterClause equalToFilterClause)
{
propertyName = equalToFilterClause.FieldName;
propertyValue = BsonValue.Create(equalToFilterClause.Value);
propertyValue = BsonValueFactory.Create(equalToFilterClause.Value);
filterOperator = EqualOperator;
}
else
Expand Down
2 changes: 1 addition & 1 deletion dotnet/src/VectorData/MongoDB/MongoDB.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<!-- THIS PROPERTY GROUP MUST COME FIRST -->
<AssemblyName>Microsoft.SemanticKernel.Connectors.MongoDB</AssemblyName>
<RootNamespace>$(AssemblyName)</RootNamespace>
<TargetFrameworks>net8.0;netstandard2.0;net462</TargetFrameworks>
<TargetFrameworks>net8.0;net472</TargetFrameworks>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsAotCompatible>
<VersionSuffix>preview</VersionSuffix>
</PropertyGroup>
Expand Down
6 changes: 3 additions & 3 deletions dotnet/src/VectorData/MongoDB/MongoFilterTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ private BsonDocument GenerateEqualityComparison(PropertyModel property, object?
// Short form of equality (instead of $eq)
if (nodeType is ExpressionType.Equal)
{
return new BsonDocument { [property.StorageName] = BsonValue.Create(value) };
return new BsonDocument { [property.StorageName] = BsonValueFactory.Create(value) };
}

var filterOperator = nodeType switch
Expand All @@ -101,7 +101,7 @@ private BsonDocument GenerateEqualityComparison(PropertyModel property, object?
_ => throw new UnreachableException()
};

return new BsonDocument { [property.StorageName] = new BsonDocument { [filterOperator] = BsonValue.Create(value) } };
return new BsonDocument { [property.StorageName] = new BsonDocument { [filterOperator] = BsonValueFactory.Create(value) } };
}

private BsonDocument TranslateAndOr(BinaryExpression andOr)
Expand Down Expand Up @@ -261,7 +261,7 @@ BsonDocument ProcessInlineEnumerable(IEnumerable elements, Expression item)
{
[property.StorageName] = new BsonDocument
{
["$in"] = new BsonArray(from object? element in elements select BsonValue.Create(element))
["$in"] = new BsonArray(from object? element in elements select BsonValueFactory.Create(element))
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

namespace CosmosMongoDB.ConformanceTests.Support;

#pragma warning disable CA1001
public sealed class CosmosMongoTestStore : TestStore
#pragma warning restore CA1001
{
public static CosmosMongoTestStore Instance { get; } = new();

Expand Down
Loading