chore: import upstream snapshot with attribution
This commit is contained in:
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using PgVector.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.ModelTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using Xunit;
|
||||
|
||||
namespace PgVector.ConformanceTests.ModelTests;
|
||||
|
||||
public class PostgresBasicModelTests(PostgresBasicModelTests.Fixture fixture)
|
||||
: BasicModelTests<string>(fixture), IClassFixture<PostgresBasicModelTests.Fixture>
|
||||
{
|
||||
public new class Fixture : BasicModelTests<string>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PostgresTestStore.Instance;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using PgVector.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.ModelTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using Xunit;
|
||||
|
||||
namespace PgVector.ConformanceTests.ModelTests;
|
||||
|
||||
public class PostgresDynamicModelTests(PostgresDynamicModelTests.Fixture fixture)
|
||||
: DynamicModelTests<string>(fixture), IClassFixture<PostgresDynamicModelTests.Fixture>
|
||||
{
|
||||
public new class Fixture : DynamicModelTests<string>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PostgresTestStore.Instance;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using PgVector.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.ModelTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using Xunit;
|
||||
|
||||
namespace PgVector.ConformanceTests.ModelTests;
|
||||
|
||||
public class PostgresMultiVectorModelTests(PostgresMultiVectorModelTests.Fixture fixture)
|
||||
: MultiVectorModelTests<string>(fixture), IClassFixture<PostgresMultiVectorModelTests.Fixture>
|
||||
{
|
||||
public new class Fixture : MultiVectorModelTests<string>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PostgresTestStore.Instance;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using PgVector.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.ModelTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using Xunit;
|
||||
|
||||
namespace PgVector.ConformanceTests.ModelTests;
|
||||
|
||||
public class PostgresNoDataModelTests(PostgresNoDataModelTests.Fixture fixture)
|
||||
: NoDataModelTests<string>(fixture), IClassFixture<PostgresNoDataModelTests.Fixture>
|
||||
{
|
||||
public new class Fixture : NoDataModelTests<string>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PostgresTestStore.Instance;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using PgVector.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.ModelTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using Xunit;
|
||||
|
||||
namespace PgVector.ConformanceTests.ModelTests;
|
||||
|
||||
public class PostgresNoVectorModelTests(PostgresNoVectorModelTests.Fixture fixture)
|
||||
: NoVectorModelTests<string>(fixture), IClassFixture<PostgresNoVectorModelTests.Fixture>
|
||||
{
|
||||
public new class Fixture : NoVectorModelTests<string>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PostgresTestStore.Instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net10.0;net472</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>PgVector.ConformanceTests</RootNamespace>
|
||||
<UserSecretsId>b7762d10-e29b-4bb1-8b74-b6d69a667dd4</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="Testcontainers.PostgreSql" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\VectorData\PgVector\PgVector.csproj" />
|
||||
<ProjectReference Include="..\VectorData.ConformanceTests\VectorData.ConformanceTests.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using VectorData.ConformanceTests;
|
||||
|
||||
namespace PgVector.ConformanceTests;
|
||||
|
||||
public class PostgresTestSuiteImplementationTests : TestSuiteImplementationTests
|
||||
{
|
||||
protected override ICollection<Type> IgnoredTestBases { get; } =
|
||||
[
|
||||
// Hybrid search not supported
|
||||
typeof(HybridSearchTests<>)
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using PgVector.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using Xunit;
|
||||
|
||||
namespace PgVector.ConformanceTests;
|
||||
|
||||
public class PostgresCollectionManagementTests(PostgresCollectionManagementTests.Fixture fixture)
|
||||
: CollectionManagementTests<string>(fixture), IClassFixture<PostgresCollectionManagementTests.Fixture>
|
||||
{
|
||||
public class Fixture : VectorStoreFixture
|
||||
{
|
||||
public override TestStore TestStore => PostgresTestStore.Instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.SemanticKernel.Connectors.PgVector;
|
||||
using VectorData.ConformanceTests;
|
||||
using Xunit;
|
||||
|
||||
namespace PgVector.ConformanceTests;
|
||||
|
||||
public class PostgresDependencyInjectionTests
|
||||
: DependencyInjectionTests<PostgresVectorStore, PostgresCollection<string, DependencyInjectionTests<string>.Record>, string, DependencyInjectionTests<string>.Record>
|
||||
{
|
||||
protected const string ConnectionString = "Host=localhost;Database=test;";
|
||||
|
||||
protected override void PopulateConfiguration(ConfigurationManager configuration, object? serviceKey = null)
|
||||
=> configuration.AddInMemoryCollection(
|
||||
[
|
||||
new(CreateConfigKey("Postgres", serviceKey, "ConnectionString"), ConnectionString),
|
||||
]);
|
||||
|
||||
private static string ConnectionStringProvider(IServiceProvider sp)
|
||||
=> sp.GetRequiredService<IConfiguration>().GetRequiredSection("Postgres:ConnectionString").Value!;
|
||||
|
||||
private static string ConnectionStringProvider(IServiceProvider sp, object serviceKey)
|
||||
=> sp.GetRequiredService<IConfiguration>().GetRequiredSection(CreateConfigKey("Postgres", serviceKey, "ConnectionString")).Value!;
|
||||
|
||||
public override IEnumerable<Func<IServiceCollection, object?, string, ServiceLifetime, IServiceCollection>> CollectionDelegates
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return (services, serviceKey, name, lifetime) => serviceKey is null
|
||||
? services.AddPostgresCollection<string, Record>(
|
||||
name, connectionString: ConnectionString, lifetime: lifetime)
|
||||
: services.AddKeyedPostgresCollection<string, Record>(
|
||||
serviceKey, name, connectionString: ConnectionString, lifetime: lifetime);
|
||||
|
||||
yield return (services, serviceKey, name, lifetime) => serviceKey is null
|
||||
? services.AddPostgresCollection<string, Record>(
|
||||
name, ConnectionStringProvider, lifetime: lifetime)
|
||||
: services.AddKeyedPostgresCollection<string, Record>(
|
||||
serviceKey, name, sp => ConnectionStringProvider(sp, serviceKey), lifetime: lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<Func<IServiceCollection, object?, ServiceLifetime, IServiceCollection>> StoreDelegates
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return (services, serviceKey, lifetime) => serviceKey is null
|
||||
? services.AddPostgresVectorStore(
|
||||
ConnectionString, lifetime: lifetime)
|
||||
: services.AddKeyedPostgresVectorStore(
|
||||
serviceKey, ConnectionString, lifetime: lifetime);
|
||||
|
||||
yield return (services, serviceKey, lifetime) => serviceKey is null
|
||||
? services.AddPostgresVectorStore(
|
||||
connectionStringProvider: ConnectionStringProvider, lifetime: lifetime)
|
||||
: services.AddKeyedPostgresVectorStore(
|
||||
serviceKey, connectionStringProvider: sp => ConnectionStringProvider(sp, serviceKey), lifetime: lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConnectionStringProviderCantBeNull()
|
||||
{
|
||||
IServiceCollection services = new ServiceCollection();
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() => services.AddPostgresCollection<string, Record>(name: "notNull", connectionStringProvider: null!));
|
||||
Assert.Throws<ArgumentNullException>(() => services.AddKeyedPostgresCollection<string, Record>(serviceKey: "notNull", name: "notNull", connectionStringProvider: null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConnectionStringCantBeNullOrEmpty()
|
||||
{
|
||||
IServiceCollection services = new ServiceCollection();
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() => services.AddPostgresVectorStore(connectionString: null!));
|
||||
Assert.Throws<ArgumentNullException>(() => services.AddKeyedPostgresVectorStore(serviceKey: "notNull", connectionString: null!));
|
||||
Assert.Throws<ArgumentNullException>(() => services.AddPostgresCollection<string, Record>(
|
||||
name: "notNull", connectionString: null!));
|
||||
Assert.Throws<ArgumentException>(() => services.AddPostgresCollection<string, Record>(
|
||||
name: "notNull", connectionString: ""));
|
||||
Assert.Throws<ArgumentNullException>(() => services.AddKeyedPostgresCollection<string, Record>(
|
||||
serviceKey: "notNull", name: "notNull", connectionString: null!));
|
||||
Assert.Throws<ArgumentException>(() => services.AddKeyedPostgresCollection<string, Record>(
|
||||
serviceKey: "notNull", name: "notNull", connectionString: ""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using PgVector.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using Xunit;
|
||||
|
||||
namespace PgVector.ConformanceTests;
|
||||
|
||||
public class PostgresDistanceFunctionTests(PostgresDistanceFunctionTests.Fixture fixture)
|
||||
: DistanceFunctionTests<int>(fixture), IClassFixture<PostgresDistanceFunctionTests.Fixture>
|
||||
{
|
||||
public override Task EuclideanSquaredDistance() => Assert.ThrowsAsync<NotSupportedException>(base.EuclideanSquaredDistance);
|
||||
public override Task NegativeDotProductSimilarity() => Assert.ThrowsAsync<NotSupportedException>(base.NegativeDotProductSimilarity);
|
||||
|
||||
public override async Task HammingDistance()
|
||||
{
|
||||
// Hamming distance is supported by pgvector, but only on binary vectors (bit(x)), and the test uses float32 vectors (vector(x)).
|
||||
var exception = await Assert.ThrowsAsync<VectorStoreException>(base.HammingDistance);
|
||||
Assert.IsType<Npgsql.PostgresException>(exception.InnerException);
|
||||
}
|
||||
|
||||
public new class Fixture() : DistanceFunctionTests<int>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PostgresTestStore.Instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using PgVector.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using Xunit;
|
||||
|
||||
namespace PgVector.ConformanceTests;
|
||||
|
||||
public class PostgresEmbeddingGenerationTests(PostgresEmbeddingGenerationTests.StringVectorFixture stringVectorFixture, PostgresEmbeddingGenerationTests.RomOfFloatVectorFixture romOfFloatVectorFixture)
|
||||
: EmbeddingGenerationTests<int>(stringVectorFixture, romOfFloatVectorFixture), IClassFixture<PostgresEmbeddingGenerationTests.StringVectorFixture>, IClassFixture<PostgresEmbeddingGenerationTests.RomOfFloatVectorFixture>
|
||||
{
|
||||
public new class StringVectorFixture : EmbeddingGenerationTests<int>.StringVectorFixture
|
||||
{
|
||||
public override TestStore TestStore => PostgresTestStore.Instance;
|
||||
|
||||
public override VectorStore CreateVectorStore(IEmbeddingGenerator? embeddingGenerator)
|
||||
=> PostgresTestStore.Instance.GetVectorStore(new() { EmbeddingGenerator = embeddingGenerator });
|
||||
|
||||
public override Func<IServiceCollection, IServiceCollection>[] DependencyInjectionStoreRegistrationDelegates =>
|
||||
[
|
||||
services => services
|
||||
.AddSingleton(PostgresTestStore.Instance.DataSource)
|
||||
.AddPostgresVectorStore()
|
||||
];
|
||||
|
||||
public override Func<IServiceCollection, IServiceCollection>[] DependencyInjectionCollectionRegistrationDelegates =>
|
||||
[
|
||||
services => services
|
||||
.AddSingleton(PostgresTestStore.Instance.DataSource)
|
||||
.AddPostgresCollection<int, RecordWithAttributes>(this.CollectionName)
|
||||
];
|
||||
}
|
||||
|
||||
public new class RomOfFloatVectorFixture : EmbeddingGenerationTests<int>.RomOfFloatVectorFixture
|
||||
{
|
||||
public override TestStore TestStore => PostgresTestStore.Instance;
|
||||
|
||||
public override VectorStore CreateVectorStore(IEmbeddingGenerator? embeddingGenerator)
|
||||
=> PostgresTestStore.Instance.GetVectorStore(new() { EmbeddingGenerator = embeddingGenerator });
|
||||
|
||||
public override Func<IServiceCollection, IServiceCollection>[] DependencyInjectionStoreRegistrationDelegates =>
|
||||
[
|
||||
services => services
|
||||
.AddSingleton(PostgresTestStore.Instance.DataSource)
|
||||
.AddPostgresVectorStore(),
|
||||
];
|
||||
|
||||
public override Func<IServiceCollection, IServiceCollection>[] DependencyInjectionCollectionRegistrationDelegates =>
|
||||
[
|
||||
services => services
|
||||
.AddSingleton(PostgresTestStore.Instance.DataSource)
|
||||
.AddPostgresCollection<int, RecordWithAttributes>(this.CollectionName),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using PgVector.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using Xunit;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace PgVector.ConformanceTests;
|
||||
|
||||
#pragma warning disable CS0252 // Possible unintended reference comparison; left hand side needs cast
|
||||
|
||||
public class PostgresFilterTests(PostgresFilterTests.Fixture fixture)
|
||||
: FilterTests<int>(fixture), IClassFixture<PostgresFilterTests.Fixture>
|
||||
{
|
||||
public override async Task Not_over_Or()
|
||||
{
|
||||
// Test sends: WHERE (NOT (("Int" = 8) OR ("String" = 'foo')))
|
||||
// There's a NULL string in the database, and relational null semantics in conjunction with negation makes the default implementation fail.
|
||||
await Assert.ThrowsAsync<FailException>(() => base.Not_over_Or());
|
||||
|
||||
// Compensate by adding a null check:
|
||||
await this.TestFilterAsync(
|
||||
r => r.String != null && !(r.Int == 8 || r.String == "foo"),
|
||||
r => r["String"] != null && !((int)r["Int"]! == 8 || r["String"] == "foo"));
|
||||
}
|
||||
|
||||
public override async Task NotEqual_with_string()
|
||||
{
|
||||
// As above, null semantics + negation
|
||||
await Assert.ThrowsAsync<FailException>(() => base.NotEqual_with_string());
|
||||
|
||||
await this.TestFilterAsync(
|
||||
r => r.String != null && r.String != "foo",
|
||||
r => r["String"] != null && r["String"] != "foo");
|
||||
}
|
||||
|
||||
public new class Fixture : FilterTests<int>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PostgresTestStore.Instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using PgVector.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using Xunit;
|
||||
|
||||
namespace PgVector.ConformanceTests;
|
||||
|
||||
public class PostgresHybridSearchTests(
|
||||
PostgresHybridSearchTests.VectorAndStringFixture vectorAndStringFixture,
|
||||
PostgresHybridSearchTests.MultiTextFixture multiTextFixture)
|
||||
: HybridSearchTests<long>(vectorAndStringFixture, multiTextFixture),
|
||||
IClassFixture<PostgresHybridSearchTests.VectorAndStringFixture>,
|
||||
IClassFixture<PostgresHybridSearchTests.MultiTextFixture>
|
||||
{
|
||||
public new class VectorAndStringFixture : HybridSearchTests<long>.VectorAndStringFixture
|
||||
{
|
||||
public override TestStore TestStore => PostgresTestStore.Instance;
|
||||
}
|
||||
|
||||
public new class MultiTextFixture : HybridSearchTests<long>.MultiTextFixture
|
||||
{
|
||||
public override TestStore TestStore => PostgresTestStore.Instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using PgVector.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.Xunit;
|
||||
using Xunit;
|
||||
|
||||
namespace PgVector.ConformanceTests;
|
||||
|
||||
public class PostgresIndexKindTests(PostgresIndexKindTests.Fixture fixture)
|
||||
: IndexKindTests<int>(fixture), IClassFixture<PostgresIndexKindTests.Fixture>
|
||||
{
|
||||
[ConditionalFact]
|
||||
public virtual Task Hnsw()
|
||||
=> this.Test(IndexKind.Hnsw);
|
||||
|
||||
public new class Fixture() : IndexKindTests<int>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PostgresTestStore.Instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
# PostgreSQL Vector Store Conformance Tests
|
||||
|
||||
This project contains conformance tests for the PostgreSQL (pgvector) Vector Store implementation.
|
||||
|
||||
## Running the Tests
|
||||
|
||||
By default, the tests will automatically use a testcontainer to spin up a PostgreSQL instance with the pgvector extension. Docker must be running on your machine for this to work.
|
||||
|
||||
### Using an External PostgreSQL Instance
|
||||
|
||||
If you want to run the tests against an external PostgreSQL instance (e.g., a cloud database, local PostgreSQL server, or any other instance), you can provide a connection string through one of the following methods:
|
||||
|
||||
**Note**: The external PostgreSQL instance must have the `pgvector` extension available. The tests will attempt to create the extension if it doesn't exist.
|
||||
|
||||
#### Option 1: Environment Variable
|
||||
|
||||
Set the `Postgres__ConnectionString` environment variable:
|
||||
|
||||
```bash
|
||||
# Bash/Linux/macOS
|
||||
export Postgres__ConnectionString="Host=myserver.postgres.database.azure.com;Database=mydb;Username=myuser;Password=mypassword;"
|
||||
|
||||
# PowerShell
|
||||
$env:Postgres__ConnectionString = "Host=myserver.postgres.database.azure.com;Database=mydb;Username=myuser;Password=mypassword;"
|
||||
```
|
||||
|
||||
#### Option 2: Configuration File
|
||||
|
||||
Create a `testsettings.development.json` file in this directory with the following content:
|
||||
|
||||
```json
|
||||
{
|
||||
"Postgres": {
|
||||
"ConnectionString": "Host=myserver.postgres.database.azure.com;Database=mydb;Username=myuser;Password=mypassword;"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This file is git-ignored and safe for local development.
|
||||
|
||||
#### Option 3: User Secrets
|
||||
|
||||
```bash
|
||||
cd test/VectorData/PgVector.ConformanceTests
|
||||
dotnet user-secrets set "Postgres:ConnectionString" "Host=myserver.postgres.database.azure.com;Database=mydb;Username=myuser;Password=mypassword;"
|
||||
```
|
||||
|
||||
## Benefits of Using an External Instance
|
||||
|
||||
Using an external PostgreSQL instance can be beneficial when:
|
||||
- You want to avoid the overhead of spinning up Docker containers
|
||||
- You need to test against a specific PostgreSQL version or configuration
|
||||
- You want faster test execution (no container startup time)
|
||||
- You're running tests in an environment where Docker is not available
|
||||
- You need to test against cloud-hosted PostgreSQL (e.g., Azure Database for PostgreSQL, AWS RDS)
|
||||
|
||||
## Prerequisites for External Instances
|
||||
|
||||
The external PostgreSQL instance must:
|
||||
- Have the `pgvector` extension installed and available
|
||||
- Have sufficient permissions for the connecting user to:
|
||||
- Create the vector extension (if not already created)
|
||||
- Create tables and indexes
|
||||
- Insert, update, and delete data
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace PgVector.ConformanceTests.Support;
|
||||
|
||||
#pragma warning disable CA1810 // Initialize all static fields when those fields are declared
|
||||
|
||||
internal static class PostgresTestEnvironment
|
||||
{
|
||||
public static readonly string? ConnectionString;
|
||||
|
||||
public static bool IsConnectionStringDefined => ConnectionString is not null;
|
||||
|
||||
static PostgresTestEnvironment()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddJsonFile(path: "testsettings.json", optional: true)
|
||||
.AddJsonFile(path: "testsettings.development.json", optional: true)
|
||||
.AddEnvironmentVariables()
|
||||
.AddUserSecrets<PostgresTestStore>()
|
||||
.Build();
|
||||
|
||||
var postgresSection = configuration.GetSection("Postgres");
|
||||
ConnectionString = postgresSection["ConnectionString"];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel.Connectors.PgVector;
|
||||
using Npgsql;
|
||||
using Testcontainers.PostgreSql;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
|
||||
namespace PgVector.ConformanceTests.Support;
|
||||
|
||||
#pragma warning disable CA1001 // Type owns disposable fields but is not disposable
|
||||
|
||||
internal sealed class PostgresTestStore : TestStore
|
||||
{
|
||||
public static PostgresTestStore Instance { get; } = new();
|
||||
|
||||
private static readonly PostgreSqlContainer s_container = new PostgreSqlBuilder()
|
||||
.WithImage("pgvector/pgvector:pg18")
|
||||
.Build();
|
||||
|
||||
private NpgsqlDataSource? _dataSource;
|
||||
private string? _connectionString;
|
||||
private bool _useExternalInstance;
|
||||
|
||||
public NpgsqlDataSource DataSource => this._dataSource ?? throw new InvalidOperationException("Not initialized");
|
||||
|
||||
public string ConnectionString => this._connectionString ?? throw new InvalidOperationException("Not initialized");
|
||||
|
||||
public PostgresVectorStore GetVectorStore(PostgresVectorStoreOptions options)
|
||||
{
|
||||
// The DataSource is shared with the static instance, we don't want any of the tests to dispose it.
|
||||
return new(this.DataSource, ownsDataSource: false, options);
|
||||
}
|
||||
|
||||
private PostgresTestStore()
|
||||
{
|
||||
}
|
||||
|
||||
protected override async Task StartAsync()
|
||||
{
|
||||
// Determine connection string source
|
||||
if (PostgresTestEnvironment.IsConnectionStringDefined)
|
||||
{
|
||||
this._connectionString = PostgresTestEnvironment.ConnectionString!;
|
||||
this._useExternalInstance = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use testcontainer if no external connection string is provided
|
||||
await s_container.StartAsync();
|
||||
|
||||
NpgsqlConnectionStringBuilder connectionStringBuilder = new()
|
||||
{
|
||||
Host = s_container.Hostname,
|
||||
Port = s_container.GetMappedPublicPort(5432),
|
||||
Username = PostgreSqlBuilder.DefaultUsername,
|
||||
Password = PostgreSqlBuilder.DefaultPassword,
|
||||
Database = PostgreSqlBuilder.DefaultDatabase
|
||||
};
|
||||
|
||||
this._connectionString = connectionStringBuilder.ConnectionString;
|
||||
this._useExternalInstance = false;
|
||||
}
|
||||
|
||||
NpgsqlDataSourceBuilder dataSourceBuilder = new(this._connectionString!);
|
||||
dataSourceBuilder.UseVector();
|
||||
this._dataSource = dataSourceBuilder.Build();
|
||||
|
||||
// It's a shared static instance, we don't want any of the tests to dispose it.
|
||||
this.DefaultVectorStore = new PostgresVectorStore(this._dataSource, ownsDataSource: false);
|
||||
}
|
||||
|
||||
protected override async Task StopAsync()
|
||||
{
|
||||
if (this._dataSource is not null)
|
||||
{
|
||||
await this._dataSource.DisposeAsync();
|
||||
}
|
||||
|
||||
// Only stop the container if we started it
|
||||
if (!this._useExternalInstance)
|
||||
{
|
||||
await s_container.StopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using PgVector.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.TypeTests;
|
||||
using Xunit;
|
||||
|
||||
namespace PgVector.ConformanceTests.TypeTests;
|
||||
|
||||
public class PostgresDataTypeTests(PostgresDataTypeTests.Fixture fixture)
|
||||
: DataTypeTests<Guid, DataTypeTests<Guid>.DefaultRecord>(fixture), IClassFixture<PostgresDataTypeTests.Fixture>
|
||||
{
|
||||
// Npgsql maps DateTime to timestamptz by default, and so requires UTC DateTimes (the base test uses Unspecified).
|
||||
public override Task DateTime()
|
||||
=> Assert.ThrowsAsync<ArgumentException>(base.DateTime);
|
||||
|
||||
public virtual Task DateTime_utc()
|
||||
=> this.Test<DateTime>(
|
||||
"DateTime",
|
||||
System.DateTime.SpecifyKind(new DateTime(2020, 1, 1, 12, 30, 45), DateTimeKind.Utc),
|
||||
System.DateTime.SpecifyKind(new DateTime(2021, 2, 3, 13, 40, 55), DateTimeKind.Utc),
|
||||
instantiationExpression: () => System.DateTime.SpecifyKind(new DateTime(2020, 1, 1, 12, 30, 45), DateTimeKind.Utc));
|
||||
|
||||
// PostgreSQL does not support representing an offset, so only DateTimeOffsets with offset=0 are supported.
|
||||
public override Task DateTimeOffset()
|
||||
=> Assert.ThrowsAsync<ArgumentException>(base.DateTimeOffset);
|
||||
|
||||
// PostgreSQL does not support representing an offset, so only DateTimeOffsets with offset=0 are supported.
|
||||
public virtual Task DateTimeOffset_with_offset_zero()
|
||||
=> this.Test<DateTimeOffset>(
|
||||
"DateTimeOffset",
|
||||
new DateTimeOffset(2020, 1, 1, 12, 30, 45, TimeSpan.FromHours(0)),
|
||||
new DateTimeOffset(2021, 2, 3, 13, 40, 55, TimeSpan.FromHours(0)),
|
||||
instantiationExpression: () => new DateTimeOffset(2020, 1, 1, 12, 30, 45, TimeSpan.FromHours(0)));
|
||||
|
||||
public new class Fixture : DataTypeTests<Guid, DataTypeTests<Guid>.DefaultRecord>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PostgresTestStore.Instance;
|
||||
|
||||
public override Type[] UnsupportedDefaultTypes { get; } =
|
||||
[
|
||||
typeof(byte),
|
||||
];
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using Pgvector;
|
||||
using PgVector.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.TypeTests;
|
||||
using VectorData.ConformanceTests.Xunit;
|
||||
using Xunit;
|
||||
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
|
||||
namespace PgVector.ConformanceTests.TypeTests;
|
||||
|
||||
public class PostgresEmbeddingTypeTests(PostgresEmbeddingTypeTests.Fixture fixture)
|
||||
: EmbeddingTypeTests<int>(fixture), IClassFixture<PostgresEmbeddingTypeTests.Fixture>
|
||||
{
|
||||
#if NET
|
||||
[ConditionalFact]
|
||||
public virtual Task ReadOnlyMemory_of_Half()
|
||||
=> this.Test<ReadOnlyMemory<Half>>(
|
||||
new ReadOnlyMemory<Half>([(byte)1, (byte)2, (byte)3]),
|
||||
new ReadOnlyMemoryEmbeddingGenerator<Half>([(byte)1, (byte)2, (byte)3]),
|
||||
vectorEqualityAsserter: (e, a) => Assert.Equal(e.Span.ToArray(), a.Span.ToArray()));
|
||||
|
||||
[ConditionalFact]
|
||||
public virtual Task Embedding_of_Half()
|
||||
=> this.Test<Embedding<Half>>(
|
||||
new Embedding<Half>(new ReadOnlyMemory<Half>([(byte)1, (byte)2, (byte)3])),
|
||||
new ReadOnlyMemoryEmbeddingGenerator<Half>([(byte)1, (byte)2, (byte)3]),
|
||||
vectorEqualityAsserter: (e, a) => Assert.Equal(e.Vector.Span.ToArray(), a.Vector.Span.ToArray()));
|
||||
|
||||
[ConditionalFact]
|
||||
public virtual Task Array_of_Half()
|
||||
=> this.Test<Half[]>(
|
||||
[(byte)1, (byte)2, (byte)3],
|
||||
new ReadOnlyMemoryEmbeddingGenerator<Half>([(byte)1, (byte)2, (byte)3]));
|
||||
#endif
|
||||
|
||||
[ConditionalFact]
|
||||
public virtual Task BitArray()
|
||||
=> this.Test<BitArray>(
|
||||
new BitArray([true, false, true]),
|
||||
new BinaryEmbeddingGenerator(new BitArray([true, false, true])),
|
||||
distanceFunction: DistanceFunction.HammingDistance);
|
||||
|
||||
[ConditionalFact]
|
||||
public virtual Task BinaryEmbedding()
|
||||
=> this.Test<BinaryEmbedding>(
|
||||
new BinaryEmbedding(new([true, false, true])),
|
||||
new BinaryEmbeddingGenerator(new BitArray([true, false, true])),
|
||||
distanceFunction: DistanceFunction.HammingDistance,
|
||||
vectorEqualityAsserter: (e, a) => Assert.Equal(e.Vector, a.Vector));
|
||||
|
||||
[ConditionalFact]
|
||||
public virtual Task SparseVector()
|
||||
=> this.Test<SparseVector>(new SparseVector(new ReadOnlyMemory<float>([1, 2, 3])), embeddingGenerator: null);
|
||||
|
||||
// TODO: Figure out the embedding generation story for sparsevec - need an Embedding wrapper
|
||||
|
||||
public new class Fixture : EmbeddingTypeTests<int>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PostgresTestStore.Instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using PgVector.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.TypeTests;
|
||||
using VectorData.ConformanceTests.Xunit;
|
||||
using Xunit;
|
||||
|
||||
namespace PgVector.ConformanceTests.TypeTests;
|
||||
|
||||
public class PostgresKeyTypeTests(PostgresKeyTypeTests.Fixture fixture)
|
||||
: KeyTypeTests(fixture), IClassFixture<PostgresKeyTypeTests.Fixture>
|
||||
{
|
||||
[ConditionalFact]
|
||||
public virtual Task Int() => this.Test<int>(8, supportsAutoGeneration: true);
|
||||
|
||||
[ConditionalFact]
|
||||
public virtual Task Long() => this.Test<long>(8L, supportsAutoGeneration: true);
|
||||
|
||||
[ConditionalFact]
|
||||
public virtual Task String() => this.Test<string>("foo", "bar");
|
||||
|
||||
public new class Fixture : KeyTypeTests.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PostgresTestStore.Instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
// Optional: Provide a connection string to use an external PostgreSQL instance instead of testcontainers
|
||||
// This is useful for running tests against external PostgreSQL instances or cloud databases
|
||||
// If not specified, tests will automatically use a testcontainer
|
||||
"Postgres": {
|
||||
"ConnectionString": ""
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user