Skip to content
Merged
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 MEVD/src/PgVector/PgVector.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Version>1.0.0</Version>
<Version>1.0.1</Version>
<AssemblyName>CommunityToolkit.VectorData.PgVector</AssemblyName>
<RootNamespace>$(AssemblyName)</RootNamespace>
<TargetFrameworks>net10.0;net8.0;netstandard2.0;net462</TargetFrameworks>
Expand Down
52 changes: 33 additions & 19 deletions MEVD/src/PgVector/PostgresCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -488,33 +488,39 @@ private async Task InternalCreateCollectionAsync(bool ifNotExists, CancellationT

await using (connection)
{
var pgVersion = connection.PostgreSqlVersion;
Version pgVersion = connection.PostgreSqlVersion;

// Prepare the SQL commands.
using var batch = connection.CreateBatch();

// First, check if the pgvector extension is already installed in PostgreSQL, and then install it if not.
// Note that we do a separate check before doing CREATE EXTENSION IF EXISTS in order to know if it was actually created,
// since in that case we must also must call ReloadTypesAsync() at the Npgsql level
batch.BatchCommands.Add(new NpgsqlBatchCommand("SELECT EXISTS(SELECT * FROM pg_extension WHERE extname='vector')"));
batch.BatchCommands.Add(new NpgsqlBatchCommand("CREATE EXTENSION IF NOT EXISTS vector"));

bool extensionAlreadyExisted;

try
{
extensionAlreadyExisted = (bool)(await batch.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false))!;
}
catch (PostgresException e) when (e.SqlState == PostgresErrorCodes.UniqueViolation)
{
// CREATE EXTENSION IF NOT EXISTS is not atomic in PG, so concurrent sessions doing this at the same time
// may trigger a unique constraint violation. We ignore it and interpret it to mean that the extension
// already exists.
extensionAlreadyExisted = true;
}
// The check must be executed separately so that users without permission to create extensions can use an
// extension that was installed by an administrator.
bool extensionAlreadyExisted = await IsVectorExtensionInstalledAsync(connection, cancellationToken).ConfigureAwait(false);

Comment thread
adamsitnik marked this conversation as resolved.
if (!extensionAlreadyExisted)
{
batch.BatchCommands.Clear();
batch.BatchCommands.Add(new NpgsqlBatchCommand("CREATE EXTENSION IF NOT EXISTS vector"));

try
{
await batch.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
}
catch (PostgresException e) when (e.SqlState == PostgresErrorCodes.UniqueViolation)
{
// CREATE EXTENSION IF NOT EXISTS is not atomic in PG, so concurrent sessions doing this at the same time
// may trigger a unique constraint violation. We ignore it since the extension now exists.
}
catch (PostgresException e) when (e.SqlState == PostgresErrorCodes.InsufficientPrivilege)
{
bool extensionInstalledConcurrently = await IsVectorExtensionInstalledAsync(connection, cancellationToken).ConfigureAwait(false);
if (!extensionInstalledConcurrently)
{
throw;
}
}

await connection.ReloadTypesAsync().ConfigureAwait(false);
}

Expand Down Expand Up @@ -542,6 +548,14 @@ private Task RunOperationAsync(string operationName, Func<Task> operation)
operationName,
operation);

private static async Task<bool> IsVectorExtensionInstalledAsync(NpgsqlConnection connection, CancellationToken cancellationToken)
{
using NpgsqlCommand command = connection.CreateCommand();
command.CommandText = "SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname='vector')";

return (bool)(await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false))!;
}

private Task<T> RunOperationAsync<T>(string operationName, Func<Task<T>> operation)
=> VectorStoreErrorHandler.RunOperationAsync<T, NpgsqlException>(
_collectionMetadata,
Expand Down