mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
.Net: [MEVD] Add validation for mismatched collection key (#13594)
Closes #11355
This commit is contained in:
@@ -46,6 +46,8 @@ internal class MongoModelBuilder() : CollectionModelBuilder(s_validationOptions)
|
||||
|
||||
protected override void ValidateKeyProperty(KeyPropertyModel keyProperty)
|
||||
{
|
||||
base.ValidateKeyProperty(keyProperty);
|
||||
|
||||
var type = keyProperty.Type;
|
||||
|
||||
if (type != typeof(string) && type != typeof(int) && type != typeof(long) && type != typeof(Guid) && type != typeof(ObjectId))
|
||||
|
||||
@@ -72,7 +72,7 @@ public class AzureAISearchCollection<TKey, TRecord> : VectorStoreCollection<TKey
|
||||
static options => typeof(TRecord) == typeof(Dictionary<string, object?>)
|
||||
? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(AzureAISearchDynamicCollection)))
|
||||
: new AzureAISearchModelBuilder()
|
||||
.Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator, options.JsonSerializerOptions ?? JsonSerializerOptions.Default),
|
||||
.Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator, options.JsonSerializerOptions ?? JsonSerializerOptions.Default),
|
||||
options)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ internal class AzureAISearchDynamicModelBuilder() : CollectionModelBuilder(s_mod
|
||||
|
||||
protected override void ValidateKeyProperty(KeyPropertyModel keyProperty)
|
||||
{
|
||||
base.ValidateKeyProperty(keyProperty);
|
||||
|
||||
var type = keyProperty.Type;
|
||||
|
||||
if (type != typeof(string) && type != typeof(Guid))
|
||||
|
||||
@@ -22,6 +22,8 @@ internal class AzureAISearchModelBuilder() : CollectionJsonModelBuilder(s_modelB
|
||||
|
||||
protected override void ValidateKeyProperty(KeyPropertyModel keyProperty)
|
||||
{
|
||||
base.ValidateKeyProperty(keyProperty);
|
||||
|
||||
var type = keyProperty.Type;
|
||||
|
||||
if (type != typeof(string) && type != typeof(Guid))
|
||||
|
||||
@@ -90,7 +90,7 @@ public class CosmosMongoCollection<TKey, TRecord> : VectorStoreCollection<TKey,
|
||||
name,
|
||||
static options => typeof(TRecord) == typeof(Dictionary<string, object?>)
|
||||
? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(CosmosMongoDynamicCollection)))
|
||||
: new MongoModelBuilder().Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator),
|
||||
: new MongoModelBuilder().Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator),
|
||||
options)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ public class CosmosNoSqlCollection<TKey, TRecord> : VectorStoreCollection<TKey,
|
||||
static options => typeof(TRecord) == typeof(Dictionary<string, object?>)
|
||||
? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(CosmosNoSqlDynamicCollection)))
|
||||
: new CosmosNoSqlModelBuilder()
|
||||
.Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator, options.JsonSerializerOptions ?? JsonSerializerOptions.Default),
|
||||
.Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator, options.JsonSerializerOptions ?? JsonSerializerOptions.Default),
|
||||
options)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -22,6 +22,13 @@ internal class CosmosNoSqlModelBuilder() : CollectionJsonModelBuilder(s_modelBui
|
||||
|
||||
protected override void ValidateKeyProperty(KeyPropertyModel keyProperty)
|
||||
{
|
||||
// CosmosNoSqlKey is a composite key type (document ID + partition key) that doesn't correspond to any single key property type;
|
||||
// skip the base TKey-to-key-property validation when it's used.
|
||||
if (this.KeyType != typeof(CosmosNoSqlKey))
|
||||
{
|
||||
base.ValidateKeyProperty(keyProperty);
|
||||
}
|
||||
|
||||
// Note that the key property in Cosmos NoSQL refers to the document ID, not to the CosmosNoSqlKey structure which includes both
|
||||
// the document ID and the partition key (and which is the generic TKey type parameter of the collection).
|
||||
var type = keyProperty.Type;
|
||||
|
||||
@@ -56,7 +56,7 @@ public class InMemoryCollection<TKey, TRecord> : VectorStoreCollection<TKey, TRe
|
||||
name,
|
||||
static options => typeof(TRecord) == typeof(Dictionary<string, object?>)
|
||||
? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(InMemoryDynamicCollection)))
|
||||
: new InMemoryModelBuilder().Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator),
|
||||
: new InMemoryModelBuilder().Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator),
|
||||
options)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ internal class InMemoryModelBuilder() : CollectionModelBuilder(ValidationOptions
|
||||
|
||||
protected override void ValidateKeyProperty(KeyPropertyModel keyProperty)
|
||||
{
|
||||
base.ValidateKeyProperty(keyProperty);
|
||||
|
||||
// All .NET types are supported by the InMemory provider, but we support auto-generation of keys only for GUIDs
|
||||
if (keyProperty.IsAutoGenerated && keyProperty.Type != typeof(Guid))
|
||||
{
|
||||
|
||||
@@ -99,7 +99,7 @@ public class MongoCollection<TKey, TRecord> : VectorStoreCollection<TKey, TRecor
|
||||
name,
|
||||
static options => typeof(TRecord) == typeof(Dictionary<string, object?>)
|
||||
? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(MongoDynamicCollection)))
|
||||
: new MongoModelBuilder().Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator),
|
||||
: new MongoModelBuilder().Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator),
|
||||
options)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ public class PostgresCollection<TKey, TRecord> : VectorStoreCollection<TKey, TRe
|
||||
name,
|
||||
static options => typeof(TRecord) == typeof(Dictionary<string, object?>)
|
||||
? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(PostgresDynamicCollection)))
|
||||
: new PostgresModelBuilder().Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator),
|
||||
: new PostgresModelBuilder().Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator),
|
||||
options)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ internal class PostgresModelBuilder() : CollectionModelBuilder(PostgresModelBuil
|
||||
|
||||
protected override void ValidateKeyProperty(KeyPropertyModel keyProperty)
|
||||
{
|
||||
base.ValidateKeyProperty(keyProperty);
|
||||
|
||||
var type = keyProperty.Type;
|
||||
|
||||
if (type != typeof(short)
|
||||
|
||||
@@ -66,7 +66,7 @@ public class PineconeCollection<TKey, TRecord> : VectorStoreCollection<TKey, TRe
|
||||
name,
|
||||
static options => typeof(TRecord) == typeof(Dictionary<string, object?>)
|
||||
? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(PineconeDynamicCollection)))
|
||||
: new PineconeModelBuilder().Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator),
|
||||
: new PineconeModelBuilder().Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator),
|
||||
options)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ internal class PineconeModelBuilder() : CollectionModelBuilder(s_validationOptio
|
||||
|
||||
protected override void ValidateKeyProperty(KeyPropertyModel keyProperty)
|
||||
{
|
||||
base.ValidateKeyProperty(keyProperty);
|
||||
|
||||
var type = keyProperty.Type;
|
||||
|
||||
if (type != typeof(string) && type != typeof(Guid))
|
||||
|
||||
@@ -90,7 +90,7 @@ public class QdrantCollection<TKey, TRecord> : VectorStoreCollection<TKey, TReco
|
||||
name,
|
||||
static options => typeof(TRecord) == typeof(Dictionary<string, object?>)
|
||||
? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(QdrantDynamicCollection)))
|
||||
: new QdrantModelBuilder(options.HasNamedVectors).Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator),
|
||||
: new QdrantModelBuilder(options.HasNamedVectors).Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator),
|
||||
options)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ internal class QdrantModelBuilder(bool hasNamedVectors) : CollectionModelBuilder
|
||||
|
||||
protected override void ValidateKeyProperty(KeyPropertyModel keyProperty)
|
||||
{
|
||||
base.ValidateKeyProperty(keyProperty);
|
||||
|
||||
var type = keyProperty.Type;
|
||||
|
||||
if (type != typeof(ulong) && type != typeof(Guid))
|
||||
|
||||
@@ -76,7 +76,7 @@ public class RedisHashSetCollection<TKey, TRecord> : VectorStoreCollection<TKey,
|
||||
name,
|
||||
static options => typeof(TRecord) == typeof(Dictionary<string, object?>)
|
||||
? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(RedisHashSetDynamicCollection)))
|
||||
: new RedisModelBuilder(ModelBuildingOptions).Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator),
|
||||
: new RedisModelBuilder(ModelBuildingOptions).Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator),
|
||||
options)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ public class RedisJsonCollection<TKey, TRecord> : VectorStoreCollection<TKey, TR
|
||||
: new RedisJsonModelBuilder(ModelBuildingOptions)
|
||||
.Build(
|
||||
typeof(TRecord),
|
||||
typeof(TKey),
|
||||
options.Definition,
|
||||
options.EmbeddingGenerator,
|
||||
options.JsonSerializerOptions ?? JsonSerializerOptions.Default),
|
||||
|
||||
@@ -19,6 +19,8 @@ internal class RedisJsonDynamicModelBuilder(CollectionModelBuildingOptions optio
|
||||
|
||||
protected override void ValidateKeyProperty(KeyPropertyModel keyProperty)
|
||||
{
|
||||
base.ValidateKeyProperty(keyProperty);
|
||||
|
||||
var type = keyProperty.Type;
|
||||
|
||||
if (type != typeof(string) && type != typeof(Guid))
|
||||
|
||||
@@ -20,6 +20,8 @@ internal class RedisJsonModelBuilder(CollectionModelBuildingOptions options) : C
|
||||
|
||||
protected override void ValidateKeyProperty(KeyPropertyModel keyProperty)
|
||||
{
|
||||
base.ValidateKeyProperty(keyProperty);
|
||||
|
||||
var type = keyProperty.Type;
|
||||
|
||||
if (type != typeof(string) && type != typeof(Guid))
|
||||
|
||||
@@ -22,6 +22,8 @@ internal class RedisModelBuilder(CollectionModelBuildingOptions options) : Colle
|
||||
|
||||
protected override void ValidateKeyProperty(KeyPropertyModel keyProperty)
|
||||
{
|
||||
base.ValidateKeyProperty(keyProperty);
|
||||
|
||||
var type = keyProperty.Type;
|
||||
|
||||
if (type != typeof(string) && type != typeof(Guid))
|
||||
|
||||
@@ -59,7 +59,7 @@ public class SqlServerCollection<TKey, TRecord>
|
||||
name,
|
||||
static options => typeof(TRecord) == typeof(Dictionary<string, object?>)
|
||||
? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(SqlServerDynamicCollection)))
|
||||
: new SqlServerModelBuilder().Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator),
|
||||
: new SqlServerModelBuilder().Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator),
|
||||
options)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ internal class SqlServerModelBuilder() : CollectionModelBuilder(s_modelBuildingO
|
||||
|
||||
protected override void ValidateKeyProperty(KeyPropertyModel keyProperty)
|
||||
{
|
||||
base.ValidateKeyProperty(keyProperty);
|
||||
|
||||
var type = keyProperty.Type;
|
||||
|
||||
if (type != typeof(int) && type != typeof(long) && type != typeof(string) && type != typeof(Guid))
|
||||
|
||||
@@ -74,7 +74,7 @@ public class SqliteCollection<TKey, TRecord> : VectorStoreCollection<TKey, TReco
|
||||
name,
|
||||
static options => typeof(TRecord) == typeof(Dictionary<string, object?>)
|
||||
? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(SqliteDynamicCollection)))
|
||||
: new SqliteModelBuilder().Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator),
|
||||
: new SqliteModelBuilder().Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator),
|
||||
options)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ internal class SqliteModelBuilder() : CollectionModelBuilder(s_modelBuildingOpti
|
||||
|
||||
protected override void ValidateKeyProperty(KeyPropertyModel keyProperty)
|
||||
{
|
||||
base.ValidateKeyProperty(keyProperty);
|
||||
|
||||
var type = keyProperty.Type;
|
||||
|
||||
if (type != typeof(int) && type != typeof(long) && type != typeof(string) && type != typeof(Guid))
|
||||
|
||||
+4
-3
@@ -27,19 +27,20 @@ public abstract class CollectionJsonModelBuilder : CollectionModelBuilder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds and returns a <see cref="CollectionModel"/> from the given <paramref name="type"/> and <paramref name="definition"/>.
|
||||
/// Builds and returns a <see cref="CollectionModel"/> from the given <paramref name="recordType"/> and <paramref name="definition"/>.
|
||||
/// </summary>
|
||||
[RequiresDynamicCode("This model building variant is not compatible with NativeAOT. See BuildDynamic() for dynamic mapping, and a third variant accepting source-generated delegates will be introduced in the future.")]
|
||||
[RequiresUnreferencedCode("This model building variant is not compatible with trimming. See BuildDynamic() for dynamic mapping, and a third variant accepting source-generated delegates will be introduced in the future.")]
|
||||
public virtual CollectionModel Build(
|
||||
Type type,
|
||||
Type recordType,
|
||||
Type keyType,
|
||||
VectorStoreCollectionDefinition? definition,
|
||||
IEmbeddingGenerator? defaultEmbeddingGenerator,
|
||||
JsonSerializerOptions jsonSerializerOptions)
|
||||
{
|
||||
this._jsonSerializerOptions = jsonSerializerOptions;
|
||||
|
||||
return this.Build(type, definition, defaultEmbeddingGenerator);
|
||||
return this.Build(recordType, keyType, definition, defaultEmbeddingGenerator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+36
-14
@@ -53,6 +53,12 @@ public abstract class CollectionModelBuilder
|
||||
/// </summary>
|
||||
protected IEmbeddingGenerator? DefaultEmbeddingGenerator { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection's generic key type parameter (<c>TKey</c>), if provided.
|
||||
/// Used by <see cref="ValidateKeyProperty"/> to validate that <c>TKey</c> corresponds to the key property type on the model.
|
||||
/// </summary>
|
||||
protected Type? KeyType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="CollectionModelBuilder"/>.
|
||||
/// </summary>
|
||||
@@ -60,32 +66,38 @@ public abstract class CollectionModelBuilder
|
||||
=> this.Options = options;
|
||||
|
||||
/// <summary>
|
||||
/// Builds and returns an <see cref="CollectionModel"/> from the given <paramref name="type"/> and <paramref name="definition"/>.
|
||||
/// Builds and returns an <see cref="CollectionModel"/> from the given <paramref name="recordType"/> and <paramref name="definition"/>.
|
||||
/// </summary>
|
||||
/// <param name="recordType">The CLR type of the record.</param>
|
||||
/// <param name="keyType">The collection's generic key type parameter (<c>TKey</c>), used to validate correspondence with the key property type.</param>
|
||||
/// <param name="definition">An optional record definition that overrides attribute-based configuration.</param>
|
||||
/// <param name="defaultEmbeddingGenerator">An optional default embedding generator for vector properties.</param>
|
||||
[RequiresDynamicCode("This model building variant is not compatible with NativeAOT. See BuildDynamic() for dynamic mapping, and a third variant accepting source-generated delegates will be introduced in the future.")]
|
||||
[RequiresUnreferencedCode("This model building variant is not compatible with trimming. See BuildDynamic() for dynamic mapping, and a third variant accepting source-generated delegates will be introduced in the future.")]
|
||||
public virtual CollectionModel Build(Type type, VectorStoreCollectionDefinition? definition, IEmbeddingGenerator? defaultEmbeddingGenerator)
|
||||
public virtual CollectionModel Build(Type recordType, Type keyType, VectorStoreCollectionDefinition? definition, IEmbeddingGenerator? defaultEmbeddingGenerator)
|
||||
{
|
||||
if (type == typeof(Dictionary<string, object?>))
|
||||
this.KeyType = keyType;
|
||||
|
||||
if (recordType == typeof(Dictionary<string, object?>))
|
||||
{
|
||||
throw new ArgumentException("Dynamic mapping with Dictionary<string, object?> requires calling BuildDynamic().");
|
||||
}
|
||||
|
||||
this.DefaultEmbeddingGenerator = definition?.EmbeddingGenerator ?? defaultEmbeddingGenerator;
|
||||
|
||||
this.ProcessTypeProperties(type, definition);
|
||||
this.ProcessTypeProperties(recordType, definition);
|
||||
|
||||
if (definition is not null)
|
||||
{
|
||||
this.ProcessRecordDefinition(definition, type);
|
||||
this.ProcessRecordDefinition(definition, recordType);
|
||||
}
|
||||
|
||||
// Go over the properties, set the PropertyInfos to point to the .NET type's properties and validate type compatibility.
|
||||
foreach (var property in this.Properties)
|
||||
{
|
||||
// When we have a CLR type (POCO, not dynamic mapping), get the .NET property's type and make sure it matches the definition.
|
||||
property.PropertyInfo = type.GetProperty(property.ModelName)
|
||||
?? throw new InvalidOperationException($"Property '{property.ModelName}' not found on CLR type '{type.FullName}'.");
|
||||
property.PropertyInfo = recordType.GetProperty(property.ModelName)
|
||||
?? throw new InvalidOperationException($"Property '{property.ModelName}' not found on CLR type '{recordType.FullName}'.");
|
||||
|
||||
var clrPropertyType = property.PropertyInfo.PropertyType;
|
||||
if ((Nullable.GetUnderlyingType(clrPropertyType) ?? clrPropertyType) != (Nullable.GetUnderlyingType(property.Type) ?? property.Type))
|
||||
@@ -96,20 +108,22 @@ public abstract class CollectionModelBuilder
|
||||
}
|
||||
|
||||
this.Customize();
|
||||
this.Validate(type, definition);
|
||||
this.Validate(recordType, definition);
|
||||
|
||||
// Extra validation for non-dynamic mapping scenarios: ensure the type has a parameterless constructor.
|
||||
if (!this.Options.UsesExternalSerializer && type.GetConstructor(Type.EmptyTypes) is null)
|
||||
if (!this.Options.UsesExternalSerializer && recordType.GetConstructor(Type.EmptyTypes) is null)
|
||||
{
|
||||
throw new NotSupportedException($"Type '{type.Name}' must have a parameterless constructor.");
|
||||
throw new NotSupportedException($"Type '{recordType.Name}' must have a parameterless constructor.");
|
||||
}
|
||||
|
||||
return new(type, new ActivatorBasedRecordCreator(), this.KeyProperties, this.DataProperties, this.VectorProperties, this.PropertyMap);
|
||||
return new(recordType, new ActivatorBasedRecordCreator(), this.KeyProperties, this.DataProperties, this.VectorProperties, this.PropertyMap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds and returns an <see cref="CollectionModel"/> for dynamic mapping scenarios from the given <paramref name="definition"/>.
|
||||
/// </summary>
|
||||
/// <param name="definition">The record definition describing the collection's schema.</param>
|
||||
/// <param name="defaultEmbeddingGenerator">An optional default embedding generator for vector properties.</param>
|
||||
public virtual CollectionModel BuildDynamic(VectorStoreCollectionDefinition definition, IEmbeddingGenerator? defaultEmbeddingGenerator)
|
||||
{
|
||||
if (definition is null)
|
||||
@@ -531,10 +545,18 @@ public abstract class CollectionModelBuilder
|
||||
=> keyPropertyType == typeof(Guid);
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the .NET type for a key property is supported by the provider.
|
||||
/// Validates the key property. The default implementation validates that the collection's generic key type (<see cref="KeyType"/>)
|
||||
/// corresponds to the key property type on the model, if <see cref="KeyType"/> was provided.
|
||||
/// Provider overrides should call the base implementation.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if the provider supports auto-generating keys of the specified type; otherwise, <c>false</c>.</returns>
|
||||
protected abstract void ValidateKeyProperty(KeyPropertyModel keyProperty);
|
||||
protected virtual void ValidateKeyProperty(KeyPropertyModel keyProperty)
|
||||
{
|
||||
if (this.KeyType is not null && this.KeyType != typeof(object) && this.KeyType != keyProperty.Type)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"The collection's generic key type is '{this.KeyType.Name}', but the key property '{keyProperty.ModelName}' has type '{keyProperty.Type.Name}'. The generic key type must match the key property type.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the .NET type for a data property is supported by the provider.
|
||||
|
||||
@@ -84,7 +84,7 @@ public class WeaviateCollection<TKey, TRecord> : VectorStoreCollection<TKey, TRe
|
||||
static options => typeof(TRecord) == typeof(Dictionary<string, object?>)
|
||||
? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(WeaviateDynamicCollection)))
|
||||
: new WeaviateModelBuilder(options.HasNamedVectors)
|
||||
.Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator, WeaviateConstants.s_jsonSerializerOptions),
|
||||
.Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator, WeaviateConstants.s_jsonSerializerOptions),
|
||||
options)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ internal class WeaviateModelBuilder(bool hasNamedVectors) : CollectionJsonModelB
|
||||
|
||||
protected override void ValidateKeyProperty(KeyPropertyModel keyProperty)
|
||||
{
|
||||
base.ValidateKeyProperty(keyProperty);
|
||||
|
||||
if (keyProperty.Type != typeof(Guid))
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
|
||||
+1
-2
@@ -103,8 +103,7 @@ public sealed class CosmosMongoCollectionSearchMappingTests
|
||||
|
||||
private static CollectionModel BuildModel(List<VectorStoreProperty> properties)
|
||||
=> new MongoModelBuilder()
|
||||
.Build(
|
||||
typeof(Dictionary<string, object?>),
|
||||
.BuildDynamic(
|
||||
new() { Properties = properties },
|
||||
defaultEmbeddingGenerator: null);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public sealed class MongoMapperTests
|
||||
]
|
||||
};
|
||||
|
||||
this._sut = new(new MongoModelBuilder().Build(typeof(MongoHotelModel), definition, defaultEmbeddingGenerator: null));
|
||||
this._sut = new(new MongoModelBuilder().Build(typeof(MongoHotelModel), typeof(string), definition, defaultEmbeddingGenerator: null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -119,6 +119,7 @@ public class QdrantCollectionSearchMappingTests
|
||||
var model = new QdrantModelBuilder(hasNamedVectors: false)
|
||||
.Build(
|
||||
typeof(DataModel),
|
||||
typeof(ulong),
|
||||
new()
|
||||
{
|
||||
Properties =
|
||||
|
||||
@@ -24,7 +24,7 @@ public class QdrantMapperTests
|
||||
// Arrange.
|
||||
var definition = CreateSinglePropsVectorStoreRecordDefinition(typeof(ulong));
|
||||
var model = new QdrantModelBuilder(hasNamedVectors)
|
||||
.Build(typeof(SinglePropsModel<ulong>), definition, defaultEmbeddingGenerator: null);
|
||||
.Build(typeof(SinglePropsModel<ulong>), typeof(ulong), definition, defaultEmbeddingGenerator: null);
|
||||
var sut = new QdrantMapper<SinglePropsModel<ulong>>(model, hasNamedVectors);
|
||||
|
||||
// Act.
|
||||
@@ -54,7 +54,7 @@ public class QdrantMapperTests
|
||||
// Arrange.
|
||||
var definition = CreateSinglePropsVectorStoreRecordDefinition(typeof(Guid));
|
||||
var model = new QdrantModelBuilder(hasNamedVectors)
|
||||
.Build(typeof(SinglePropsModel<Guid>), definition, defaultEmbeddingGenerator: null);
|
||||
.Build(typeof(SinglePropsModel<Guid>), typeof(Guid), definition, defaultEmbeddingGenerator: null);
|
||||
var sut = new QdrantMapper<SinglePropsModel<Guid>>(model, hasNamedVectors);
|
||||
|
||||
// Act.
|
||||
@@ -77,7 +77,7 @@ public class QdrantMapperTests
|
||||
// Arrange.
|
||||
var definition = CreateSinglePropsVectorStoreRecordDefinition(typeof(ulong));
|
||||
var model = new QdrantModelBuilder(hasNamedVectors)
|
||||
.Build(typeof(SinglePropsModel<ulong>), definition, defaultEmbeddingGenerator: null);
|
||||
.Build(typeof(SinglePropsModel<ulong>), typeof(ulong), definition, defaultEmbeddingGenerator: null);
|
||||
var sut = new QdrantMapper<SinglePropsModel<ulong>>(model, hasNamedVectors);
|
||||
|
||||
// Act.
|
||||
@@ -109,7 +109,7 @@ public class QdrantMapperTests
|
||||
// Arrange.
|
||||
var definition = CreateSinglePropsVectorStoreRecordDefinition(typeof(Guid));
|
||||
var model = new QdrantModelBuilder(hasNamedVectors)
|
||||
.Build(typeof(SinglePropsModel<Guid>), definition, defaultEmbeddingGenerator: null);
|
||||
.Build(typeof(SinglePropsModel<Guid>), typeof(Guid), definition, defaultEmbeddingGenerator: null);
|
||||
var sut = new QdrantMapper<SinglePropsModel<Guid>>(model, hasNamedVectors);
|
||||
|
||||
// Act.
|
||||
@@ -137,7 +137,7 @@ public class QdrantMapperTests
|
||||
// Arrange.
|
||||
var definition = CreateMultiPropsVectorStoreRecordDefinition(typeof(ulong));
|
||||
var model = new QdrantModelBuilder(hasNamedVectors: true)
|
||||
.Build(typeof(MultiPropsModel<ulong>), definition, defaultEmbeddingGenerator: null);
|
||||
.Build(typeof(MultiPropsModel<ulong>), typeof(ulong), definition, defaultEmbeddingGenerator: null);
|
||||
|
||||
var sut = new QdrantMapper<MultiPropsModel<ulong>>(model, hasNamedVectors: true);
|
||||
|
||||
@@ -166,7 +166,7 @@ public class QdrantMapperTests
|
||||
// Arrange.
|
||||
var definition = CreateMultiPropsVectorStoreRecordDefinition(typeof(Guid));
|
||||
var model = new QdrantModelBuilder(hasNamedVectors: true)
|
||||
.Build(typeof(MultiPropsModel<Guid>), definition, defaultEmbeddingGenerator: null);
|
||||
.Build(typeof(MultiPropsModel<Guid>), typeof(Guid), definition, defaultEmbeddingGenerator: null);
|
||||
var sut = new QdrantMapper<MultiPropsModel<Guid>>(model, hasNamedVectors: true);
|
||||
|
||||
// Act.
|
||||
@@ -196,7 +196,7 @@ public class QdrantMapperTests
|
||||
// Arrange.
|
||||
var definition = CreateMultiPropsVectorStoreRecordDefinition(typeof(ulong));
|
||||
var model = new QdrantModelBuilder(hasNamedVectors: true)
|
||||
.Build(typeof(MultiPropsModel<ulong>), definition, defaultEmbeddingGenerator: null);
|
||||
.Build(typeof(MultiPropsModel<ulong>), typeof(ulong), definition, defaultEmbeddingGenerator: null);
|
||||
var sut = new QdrantMapper<MultiPropsModel<ulong>>(model, hasNamedVectors: true);
|
||||
|
||||
// Act.
|
||||
@@ -235,7 +235,7 @@ public class QdrantMapperTests
|
||||
// Arrange.
|
||||
var definition = CreateMultiPropsVectorStoreRecordDefinition(typeof(Guid));
|
||||
var model = new QdrantModelBuilder(hasNamedVectors: true)
|
||||
.Build(typeof(MultiPropsModel<Guid>), definition, defaultEmbeddingGenerator: null);
|
||||
.Build(typeof(MultiPropsModel<Guid>), typeof(Guid), definition, defaultEmbeddingGenerator: null);
|
||||
var sut = new QdrantMapper<MultiPropsModel<Guid>>(model, hasNamedVectors: true);
|
||||
|
||||
// Act.
|
||||
|
||||
@@ -16,7 +16,7 @@ public sealed class RedisHashSetMapperTests
|
||||
{
|
||||
private static readonly CollectionModel s_model
|
||||
= new RedisModelBuilder(RedisHashSetCollection<string, AllTypesModel>.ModelBuildingOptions)
|
||||
.Build(typeof(AllTypesModel), RedisHashSetMappingTestHelpers.s_definition, defaultEmbeddingGenerator: null);
|
||||
.Build(typeof(AllTypesModel), typeof(string), RedisHashSetMappingTestHelpers.s_definition, defaultEmbeddingGenerator: null);
|
||||
|
||||
[Fact]
|
||||
public void MapsAllFieldsFromDataToStorageModel()
|
||||
|
||||
@@ -20,7 +20,7 @@ public sealed class RedisJsonMapperTests
|
||||
{
|
||||
// Arrange.
|
||||
var model = new RedisJsonModelBuilder(RedisJsonCollection<string, MultiPropsModel>.ModelBuildingOptions)
|
||||
.Build(typeof(MultiPropsModel), definition: null, defaultEmbeddingGenerator: null, JsonSerializerOptions.Default);
|
||||
.Build(typeof(MultiPropsModel), typeof(string), definition: null, defaultEmbeddingGenerator: null, JsonSerializerOptions.Default);
|
||||
var sut = new RedisJsonMapper<MultiPropsModel>(model, JsonSerializerOptions.Default);
|
||||
|
||||
// Act.
|
||||
@@ -42,7 +42,7 @@ public sealed class RedisJsonMapperTests
|
||||
// Arrange.
|
||||
var jsonSerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
|
||||
var model = new RedisJsonModelBuilder(RedisJsonCollection<string, MultiPropsModel>.ModelBuildingOptions)
|
||||
.Build(typeof(MultiPropsModel), definition: null, defaultEmbeddingGenerator: null, jsonSerializerOptions);
|
||||
.Build(typeof(MultiPropsModel), typeof(string), definition: null, defaultEmbeddingGenerator: null, jsonSerializerOptions);
|
||||
var sut = new RedisJsonMapper<MultiPropsModel>(model, jsonSerializerOptions);
|
||||
|
||||
// Act.
|
||||
@@ -63,7 +63,7 @@ public sealed class RedisJsonMapperTests
|
||||
{
|
||||
// Arrange.
|
||||
var model = new RedisJsonModelBuilder(RedisJsonCollection<string, MultiPropsModel>.ModelBuildingOptions)
|
||||
.Build(typeof(MultiPropsModel), definition: null, defaultEmbeddingGenerator: null, JsonSerializerOptions.Default);
|
||||
.Build(typeof(MultiPropsModel), typeof(string), definition: null, defaultEmbeddingGenerator: null, JsonSerializerOptions.Default);
|
||||
var sut = new RedisJsonMapper<MultiPropsModel>(model, JsonSerializerOptions.Default);
|
||||
|
||||
// Act.
|
||||
@@ -91,7 +91,7 @@ public sealed class RedisJsonMapperTests
|
||||
// Arrange.
|
||||
var jsonSerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
|
||||
var model = new RedisJsonModelBuilder(RedisJsonCollection<string, MultiPropsModel>.ModelBuildingOptions)
|
||||
.Build(typeof(MultiPropsModel), definition: null, defaultEmbeddingGenerator: null, jsonSerializerOptions);
|
||||
.Build(typeof(MultiPropsModel), typeof(string), definition: null, defaultEmbeddingGenerator: null, jsonSerializerOptions);
|
||||
var sut = new RedisJsonMapper<MultiPropsModel>(model, jsonSerializerOptions);
|
||||
|
||||
// Act.
|
||||
|
||||
@@ -18,6 +18,19 @@ public abstract class KeyTypeTests(KeyTypeTests.Fixture fixture)
|
||||
new Guid("603840bf-cf91-4521-8b8e-8b6a2e75910a"),
|
||||
supportsAutoGeneration: true);
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that creating a collection with a TKey that doesn't match the key property type on the model throws.
|
||||
/// </summary>
|
||||
[ConditionalFact]
|
||||
public virtual void MismatchedKeyTypeThrows()
|
||||
{
|
||||
// The definition says the key property is string (matching Record<string>.Key),
|
||||
// but TKey is Guid - this mismatch should be detected during model building.
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
fixture.TestStore.CreateCollection<Guid, Record<string>>(
|
||||
fixture.CollectionName, fixture.CreateRecordDefinition<string>(withAutoGeneration: false)));
|
||||
}
|
||||
|
||||
protected virtual Task Test<TKey>(TKey key, bool supportsAutoGeneration = false)
|
||||
where TKey : struct
|
||||
=> this.Test<TKey>(key, default!, supportsAutoGeneration: supportsAutoGeneration);
|
||||
|
||||
@@ -21,7 +21,7 @@ public class CollectionModelBuilderTests
|
||||
public void Default_embedding_generator_without_record_definition()
|
||||
{
|
||||
using var embeddingGenerator = new FakeEmbeddingGenerator<string, Embedding<float>>();
|
||||
var model = new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), definition: null, embeddingGenerator);
|
||||
var model = new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), typeof(int), definition: null, embeddingGenerator);
|
||||
|
||||
// The embedding's .NET type (Embedding<float>) is inferred from the embedding generator.
|
||||
Assert.Same(embeddingGenerator, model.VectorProperty.EmbeddingGenerator);
|
||||
@@ -48,7 +48,7 @@ public class CollectionModelBuilderTests
|
||||
]
|
||||
};
|
||||
|
||||
var model = new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), recordDefinition, embeddingGenerator);
|
||||
var model = new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), typeof(int), recordDefinition, embeddingGenerator);
|
||||
|
||||
// The embedding's .NET type (Embedding<float>) is inferred from the embedding generator.
|
||||
Assert.Same(embeddingGenerator, model.VectorProperty.EmbeddingGenerator);
|
||||
@@ -147,7 +147,7 @@ public class CollectionModelBuilderTests
|
||||
]
|
||||
},
|
||||
embeddingGenerator)
|
||||
: new CustomModelBuilder().Build(typeof(RecordWithEmbeddingVectorProperty), definition: null, embeddingGenerator);
|
||||
: new CustomModelBuilder().Build(typeof(RecordWithEmbeddingVectorProperty), typeof(int), definition: null, embeddingGenerator);
|
||||
|
||||
var vectorProperty = model.VectorProperty;
|
||||
Assert.Same(embeddingGenerator, vectorProperty.EmbeddingGenerator);
|
||||
@@ -161,6 +161,7 @@ public class CollectionModelBuilderTests
|
||||
|
||||
var model = new CustomModelBuilder().Build(
|
||||
typeof(RecordWithEmbeddingVectorProperty),
|
||||
typeof(int),
|
||||
new VectorStoreCollectionDefinition
|
||||
{
|
||||
Properties =
|
||||
@@ -200,7 +201,7 @@ public class CollectionModelBuilderTests
|
||||
|
||||
var model = dynamic
|
||||
? new CustomModelBuilder().BuildDynamic(recordDefinition, embeddingGenerator)
|
||||
: new CustomModelBuilder().Build(typeof(RecordWithCustomerVectorProperty), recordDefinition, embeddingGenerator);
|
||||
: new CustomModelBuilder().Build(typeof(RecordWithCustomerVectorProperty), typeof(int), recordDefinition, embeddingGenerator);
|
||||
|
||||
var vectorProperty = model.VectorProperty;
|
||||
|
||||
@@ -216,7 +217,7 @@ public class CollectionModelBuilderTests
|
||||
using var embeddingGenerator = new FakeEmbeddingGenerator<string, Embedding<long>>();
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||
new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), definition: null, embeddingGenerator));
|
||||
new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), typeof(int), definition: null, embeddingGenerator));
|
||||
|
||||
Assert.Equal($"Embedding generator 'FakeEmbeddingGenerator<string, Embedding<long>>' on vector property '{nameof(RecordWithStringVectorProperty.Embedding)}' cannot convert the input type 'string' to a supported vector type (one of: ReadOnlyMemory<float>, Embedding<float>, float[], ReadOnlyMemory<Half>, Embedding<Half>, Half[]).", exception.Message);
|
||||
}
|
||||
@@ -228,7 +229,7 @@ public class CollectionModelBuilderTests
|
||||
using var embeddingGenerator = new FakeEmbeddingGenerator<int, Embedding<float>>();
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||
new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), definition: null, embeddingGenerator));
|
||||
new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), typeof(int), definition: null, embeddingGenerator));
|
||||
|
||||
Assert.Equal($"Embedding generator 'FakeEmbeddingGenerator<int, Embedding<float>>' on vector property '{nameof(RecordWithStringVectorProperty.Embedding)}' cannot convert the input type 'string' to a supported vector type (one of: ReadOnlyMemory<float>, Embedding<float>, float[], ReadOnlyMemory<Half>, Embedding<Half>, Half[]).", exception.Message);
|
||||
}
|
||||
@@ -237,7 +238,7 @@ public class CollectionModelBuilderTests
|
||||
public void Non_embedding_vector_property_without_embedding_generator_throws()
|
||||
{
|
||||
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||
new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), definition: null, defaultEmbeddingGenerator: null));
|
||||
new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), typeof(int), definition: null, defaultEmbeddingGenerator: null));
|
||||
|
||||
Assert.Equal($"Vector property '{nameof(RecordWithStringVectorProperty.Embedding)}' has type 'string' which isn't supported by your provider, and no embedding generator is configured. Configure a generator that supports converting 'string' to vector type supported by your provider.", exception.Message);
|
||||
}
|
||||
@@ -261,7 +262,7 @@ public class CollectionModelBuilderTests
|
||||
};
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||
new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), recordDefinition, embeddingGenerator));
|
||||
new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), typeof(int), recordDefinition, embeddingGenerator));
|
||||
|
||||
Assert.Equal("Vector property 'Embedding' has embedding type 'Embedding<Byte>' configured, but that type isn't supported by your provider. Supported types are ReadOnlyMemory<float>, Embedding<float>, float[], ReadOnlyMemory<Half>, Embedding<Half>, Half[].", exception.Message);
|
||||
}
|
||||
@@ -285,7 +286,7 @@ public class CollectionModelBuilderTests
|
||||
};
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||
new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), recordDefinition, embeddingGenerator));
|
||||
new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), typeof(int), recordDefinition, embeddingGenerator));
|
||||
|
||||
Assert.Equal("Vector property 'Embedding' has embedding type 'Embedding<Half>' configured, but that type isn't supported by your embedding generator.", exception.Message);
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ public sealed class WeaviateMapperTests
|
||||
new WeaviateModelBuilder(hasNamedVectors)
|
||||
.Build(
|
||||
typeof(WeaviateHotel),
|
||||
typeof(Guid),
|
||||
new VectorStoreCollectionDefinition
|
||||
{
|
||||
Properties =
|
||||
|
||||
Reference in New Issue
Block a user