Skip to content

.Net: [MEVD] Updated Nullable handling in VectorStoreRecordModelBuilder #11543

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
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
9 changes: 9 additions & 0 deletions dotnet/SK-dotnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProcessWithCloudEvents.Proc
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProcessWithCloudEvents.Grpc", "samples\Demos\ProcessWithCloudEvents\ProcessWithCloudEvents.Grpc\ProcessWithCloudEvents.Grpc.csproj", "{08D84994-794A-760F-95FD-4EFA8998A16D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VectorData.UnitTests", "src\Connectors\VectorData.UnitTests\VectorData.UnitTests.csproj", "{FB55A80D-BD5C-4252-8E66-59BB1712E621}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1459,6 +1461,12 @@ Global
{08D84994-794A-760F-95FD-4EFA8998A16D}.Publish|Any CPU.Build.0 = Release|Any CPU
{08D84994-794A-760F-95FD-4EFA8998A16D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08D84994-794A-760F-95FD-4EFA8998A16D}.Release|Any CPU.Build.0 = Release|Any CPU
{FB55A80D-BD5C-4252-8E66-59BB1712E621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FB55A80D-BD5C-4252-8E66-59BB1712E621}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FB55A80D-BD5C-4252-8E66-59BB1712E621}.Publish|Any CPU.ActiveCfg = Publish|Any CPU
{FB55A80D-BD5C-4252-8E66-59BB1712E621}.Publish|Any CPU.Build.0 = Publish|Any CPU
{FB55A80D-BD5C-4252-8E66-59BB1712E621}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FB55A80D-BD5C-4252-8E66-59BB1712E621}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1657,6 +1665,7 @@ Global
{7C092DD9-9985-4D18-A817-15317D984149} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263}
{31F6608A-FD36-F529-A5FC-C954A0B5E29E} = {7C092DD9-9985-4D18-A817-15317D984149}
{08D84994-794A-760F-95FD-4EFA8998A16D} = {7C092DD9-9985-4D18-A817-15317D984149}
{FB55A80D-BD5C-4252-8E66-59BB1712E621} = {5A7028A7-4DDF-4E4F-84A9-37CE8F8D7E89}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,10 +357,7 @@ protected virtual void ValidateProperty(VectorStoreRecordPropertyModel propertyM
{
var type = propertyModel.Type;

if (type.IsGenericType && Nullable.GetUnderlyingType(type) is Type underlyingType)
{
type = underlyingType;
}
type = GetNullableUnderlyingTypeOrDefault(type);

switch (propertyModel)
{
Expand Down Expand Up @@ -396,10 +393,20 @@ protected virtual void ValidateProperty(VectorStoreRecordPropertyModel propertyM
}
}

private static Type GetNullableUnderlyingTypeOrDefault(Type type)
{
return type.IsGenericType && Nullable.GetUnderlyingType(type) is Type underlyingType ? underlyingType : type;
}

private static Type GetNullableTypeOrDefault(Type type)
{
return type.IsValueType && !type.IsGenericTypeDefinition ? typeof(Nullable<>).MakeGenericType(type) : type;
}

private static void ValidatePropertyType(string propertyName, Type propertyType, string propertyCategoryDescription, HashSet<Type> supportedTypes, HashSet<Type>? supportedEnumerableElementTypes = null)
{
// Add shortcut before testing all the more expensive scenarios.
if (supportedTypes.Contains(propertyType))
if (supportedTypes.Contains(propertyType) || supportedTypes.Contains(GetNullableTypeOrDefault(propertyType)))
{
return;
}
Expand All @@ -409,7 +416,9 @@ private static void ValidatePropertyType(string propertyName, Type propertyType,
{
var typeToCheck = GetCollectionElementType(propertyType);

if (!supportedEnumerableElementTypes.Contains(typeToCheck))
typeToCheck = GetNullableUnderlyingTypeOrDefault(typeToCheck);

if (!supportedEnumerableElementTypes.Contains(typeToCheck) && !supportedEnumerableElementTypes.Contains(GetNullableTypeOrDefault(typeToCheck)))
{
var supportedEnumerableElementTypesString = string.Join(", ", supportedEnumerableElementTypes!.Select(t => t.FullName));
throw new NotSupportedException($"Enumerable {propertyCategoryDescription} properties must have one of the supported element types: {supportedEnumerableElementTypesString}. Element type of the property '{propertyName}' is {typeToCheck.FullName}.");
Expand Down
6 changes: 6 additions & 0 deletions dotnet/src/Connectors/VectorData.UnitTests/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Suppressing errors for Test projects under dotnet folder
[*.cs]
dotnet_diagnostic.CA2007.severity = none # Do not directly await a Task
dotnet_diagnostic.VSTHRD111.severity = none # Use .ConfigureAwait(bool) is hidden by default, set to none to prevent IDE from changing on autosave
dotnet_diagnostic.CS1591.severity = none # Missing XML comment for publicly visible type or member
dotnet_diagnostic.IDE1006.severity = warning # Naming rule violations
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using Microsoft.Extensions.VectorData;
using Microsoft.Extensions.VectorData.ConnectorSupport;
using Xunit;

namespace VectorData.UnitTests;

public class VectorStoreRecordModelBuilderTests
{
[Theory]
[InlineData(typeof(bool), typeof(RecordWithSimpleDataProperties))]
[InlineData(typeof(bool?), typeof(RecordWithSimpleDataProperties))]
[InlineData(typeof(bool), typeof(RecordWithNullableDataProperties))]
[InlineData(typeof(bool?), typeof(RecordWithNullableDataProperties))]
[InlineData(typeof(bool), typeof(RecordWithEnumerableDataProperties))]
[InlineData(typeof(bool?), typeof(RecordWithEnumerableDataProperties))]
[InlineData(typeof(bool), typeof(RecordWithEnumerableNullableDataProperties))]
[InlineData(typeof(bool?), typeof(RecordWithEnumerableNullableDataProperties))]
public void BuildWithDifferentDataPropertyTypesReturnsVectorStoreRecordModel(Type dataPropertyType, Type recordType)
{
// Arrange
var options = new VectorStoreRecordModelBuildingOptions()
{
SupportsMultipleKeys = false,
SupportsMultipleVectors = true,
RequiresAtLeastOneVector = false,

SupportedKeyPropertyTypes = [typeof(string)],
SupportedDataPropertyTypes = [dataPropertyType],
SupportedEnumerableDataPropertyElementTypes = [dataPropertyType],
SupportedVectorPropertyTypes = [typeof(ReadOnlyMemory<float>)]
};

var builder = new VectorStoreRecordModelBuilder(options);

// Act
var model = builder.Build(recordType, vectorStoreRecordDefinition: null);

// Assert
Assert.NotNull(model);
}

#region private

#pragma warning disable CA1812
private sealed class RecordWithSimpleDataProperties
{
[VectorStoreRecordKey]
public string Key { get; set; }

[VectorStoreRecordData]
public bool Flag { get; set; }

[VectorStoreRecordVector(Dimensions: 4)]
public ReadOnlyMemory<float> Embedding { get; set; }
}

private sealed class RecordWithNullableDataProperties
{
[VectorStoreRecordKey]
public string Key { get; set; }

[VectorStoreRecordData]
public bool? Flag { get; set; }

[VectorStoreRecordVector(Dimensions: 4)]
public ReadOnlyMemory<float> Embedding { get; set; }
}

private sealed class RecordWithEnumerableDataProperties
{
[VectorStoreRecordKey]
public string Key { get; set; }

[VectorStoreRecordData]
public List<bool> Flags { get; set; }

[VectorStoreRecordVector(Dimensions: 4)]
public ReadOnlyMemory<float> Embedding { get; set; }
}

private sealed class RecordWithEnumerableNullableDataProperties
{
[VectorStoreRecordKey]
public string Key { get; set; }

[VectorStoreRecordData]
public List<bool?> Flags { get; set; }

[VectorStoreRecordVector(Dimensions: 4)]
public ReadOnlyMemory<float> Embedding { get; set; }
}
#pragma warning restore CA1812

#endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyName>VectorData.UnitTests</AssemblyName>
<RootNamespace>VectorData.UnitTests</RootNamespace>
<TargetFramework>net8.0</TargetFramework>
<IsTestProject>true</IsTestProject>
<Nullable>enable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<IsPackable>false</IsPackable>
<NoWarn>$(NoWarn);VSTHRD111,CA2007,CS8618</NoWarn>
<NoWarn>$(NoWarn);MEVD9001</NoWarn>
<!-- Experimental MEVD connector-facing APIs -->
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\VectorData.Abstractions\VectorData.Abstractions.csproj" />
</ItemGroup>

</Project>
Loading