Files
microsoft--semantic-kernel/dotnet/test/VectorData/SqliteVec.UnitTests/SqliteCommandBuilderTests.cs
T
wehub-resource-sync b957a53def
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:21:23 +08:00

388 lines
14 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.VectorData;
using Microsoft.Extensions.VectorData.ProviderServices;
using Microsoft.SemanticKernel.Connectors.SqliteVec;
using Xunit;
namespace SemanticKernel.Connectors.SqliteVec.UnitTests;
/// <summary>
/// Unit tests for <see cref="SqliteCommandBuilder"/> class.
/// </summary>
public sealed class SqliteCommandBuilderTests : IDisposable
{
private readonly SqliteCommand _command;
private readonly SqliteConnection _connection;
public SqliteCommandBuilderTests()
{
this._command = new() { Connection = this._connection };
this._connection = new();
}
[Fact]
public void ItBuildsTableCountCommand()
{
// Arrange
const string TableName = "TestTable";
// Act
var command = SqliteCommandBuilder.BuildTableCountCommand(this._connection, TableName);
// Assert
Assert.Equal("SELECT count(*) FROM sqlite_master WHERE type='table' AND name=@tableName;", command.CommandText);
Assert.Equal("@tableName", command.Parameters[0].ParameterName);
Assert.Equal(TableName, command.Parameters[0].Value);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ItBuildsCreateTableCommand(bool ifNotExists)
{
// Arrange
const string TableName = "TestTable";
var columns = new List<SqliteColumn>
{
new("Column1", "Type1", isPrimary: true),
new("Column2", "Type2", isPrimary: false) { IsNullable = true, Configuration = new() { ["distance_metric"] = "l2" } },
new("Column3", "Type3", isPrimary: false) { IsNullable = false },
new("Column4", "Type4", isPrimary: false) { IsNullable = true },
};
// Act
var command = SqliteCommandBuilder.BuildCreateTableCommand(this._connection, TableName, columns, ifNotExists);
// Assert
Assert.Contains("CREATE TABLE", command.CommandText);
Assert.Contains(TableName, command.CommandText);
Assert.Equal(ifNotExists, command.CommandText.Contains("IF NOT EXISTS"));
Assert.Contains("\"Column1\" Type1 PRIMARY KEY", command.CommandText);
Assert.Contains("\"Column2\" Type2 distance_metric=l2", command.CommandText);
Assert.Contains("\"Column3\" Type3 NOT NULL", command.CommandText);
Assert.Contains("\"Column4\" Type4", command.CommandText);
Assert.DoesNotContain("\"Column4\" Type4 NOT NULL", command.CommandText);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ItBuildsCreateVirtualTableCommand(bool ifNotExists)
{
// Arrange
const string TableName = "TestTable";
var columns = new List<SqliteColumn>
{
new("Column1", "Type1", isPrimary: true),
new("Column2", "Type2", isPrimary: false) { IsNullable = true, Configuration = new() { ["distance_metric"] = "l2" } },
};
// Act
var command = SqliteCommandBuilder.BuildCreateVirtualTableCommand(this._connection, TableName, columns, ifNotExists);
// Assert
Assert.Contains("CREATE VIRTUAL TABLE", command.CommandText);
Assert.Contains(TableName, command.CommandText);
Assert.Contains("USING vec0", command.CommandText);
Assert.Equal(ifNotExists, command.CommandText.Contains("IF NOT EXISTS"));
Assert.Contains("Column1 Type1 PRIMARY KEY", command.CommandText);
Assert.Contains("Column2 Type2 distance_metric=l2", command.CommandText);
}
[Fact]
public void ItBuildsDropTableCommand()
{
// Arrange
const string TableName = "TestTable";
// Act
var command = SqliteCommandBuilder.BuildDropTableCommand(this._connection, TableName);
// Assert
Assert.Equal("DROP TABLE IF EXISTS \"TestTable\";", command.CommandText);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ItBuildsInsertCommand_without_autogenerated_key(bool replaceIfExists)
{
// Arrange
const string TableName = "TestTable";
var model = BuildModel(
[
new VectorStoreKeyProperty("Id", typeof(int)),
new VectorStoreDataProperty("Name", typeof(string)),
new VectorStoreDataProperty("Age", typeof(string)),
new VectorStoreDataProperty("Address", typeof(string)),
]);
var records = new List<Dictionary<string, object?>>
{
new() { ["Id"] = 1, ["Name"] = "NameValue1", ["Age"] = "AgeValue1", ["Address"] = "AddressValue1" },
new() { ["Id"] = 2, ["Name"] = "NameValue2", ["Age"] = "AgeValue2", ["Address"] = "AddressValue2" },
};
// Act
var command = SqliteCommandBuilder.BuildInsertCommand(
this._connection,
TableName,
model,
records,
generatedEmbeddings: null,
data: true,
replaceIfExists: replaceIfExists);
// Assert
Assert.Equal(replaceIfExists, command.CommandText.Contains("OR REPLACE"));
Assert.Contains($"INTO \"{TableName}\" (\"Id\", \"Name\", \"Age\", \"Address\")", command.CommandText);
Assert.Contains("VALUES (@Id0, @Name0, @Age0, @Address0)", command.CommandText);
Assert.Contains("VALUES (@Id1, @Name1, @Age1, @Address1)", command.CommandText);
Assert.DoesNotContain("RETURNING", command.CommandText);
Assert.Equal("@Id0", command.Parameters[0].ParameterName);
Assert.Equal(1, command.Parameters[0].Value);
Assert.Equal("@Name0", command.Parameters[1].ParameterName);
Assert.Equal("NameValue1", command.Parameters[1].Value);
Assert.Equal("@Age0", command.Parameters[2].ParameterName);
Assert.Equal("AgeValue1", command.Parameters[2].Value);
Assert.Equal("@Address0", command.Parameters[3].ParameterName);
Assert.Equal("AddressValue1", command.Parameters[3].Value);
Assert.Equal("@Id1", command.Parameters[4].ParameterName);
Assert.Equal(2, command.Parameters[4].Value);
Assert.Equal("@Name1", command.Parameters[5].ParameterName);
Assert.Equal("NameValue2", command.Parameters[5].Value);
Assert.Equal("@Age1", command.Parameters[6].ParameterName);
Assert.Equal("AgeValue2", command.Parameters[6].Value);
Assert.Equal("@Address1", command.Parameters[7].ParameterName);
Assert.Equal("AddressValue2", command.Parameters[7].Value);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ItBuildsInsertCommand_with_autogenerated_key(bool replaceIfExists)
{
// Arrange
const string TableName = "TestTable";
var model = BuildModel(
[
new VectorStoreKeyProperty("Id", typeof(int)),
new VectorStoreDataProperty("Name", typeof(string)),
new VectorStoreDataProperty("Age", typeof(string)),
new VectorStoreDataProperty("Address", typeof(string)),
]);
var records = new List<Dictionary<string, object?>>
{
new() { ["Id"] = default(int), ["Name"] = "NameValue1", ["Age"] = "AgeValue1", ["Address"] = "AddressValue1" },
new() { ["Id"] = default(int), ["Name"] = "NameValue2", ["Age"] = "AgeValue2", ["Address"] = "AddressValue2" },
};
// Act
var command = SqliteCommandBuilder.BuildInsertCommand(
this._connection,
TableName,
model,
records,
generatedEmbeddings: null,
data: true,
replaceIfExists: replaceIfExists);
// Assert
Assert.DoesNotContain("OR REPLACE", command.CommandText);
Assert.Contains($"INTO \"{TableName}\" (\"Name\", \"Age\", \"Address\")", command.CommandText);
Assert.Contains("VALUES (@Name0, @Age0, @Address0)", command.CommandText);
Assert.Contains("VALUES (@Name1, @Age1, @Address1)", command.CommandText);
Assert.Contains("RETURNING \"Id\"", command.CommandText);
Assert.Equal("@Name0", command.Parameters[0].ParameterName);
Assert.Equal("NameValue1", command.Parameters[0].Value);
Assert.Equal("@Name1", command.Parameters[3].ParameterName);
Assert.Equal("NameValue2", command.Parameters[3].Value);
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("Age")]
public void ItBuildsSelectCommand(string? orderByPropertyName)
{
// Arrange
const string TableName = "TestTable";
var model = BuildModel(
[
new VectorStoreKeyProperty("Id", typeof(string)),
new VectorStoreDataProperty("Name", typeof(string)),
new VectorStoreDataProperty("Age", typeof(string)),
new VectorStoreDataProperty("Address", typeof(string)),
]);
var conditions = new List<SqliteWhereCondition>
{
new SqliteWhereEqualsCondition("Name", "NameValue"),
new SqliteWhereInCondition("Age", [10, 20, 30]),
};
FilteredRecordRetrievalOptions<Dictionary<string, object?>> filterOptions = new();
if (!string.IsNullOrWhiteSpace(orderByPropertyName))
{
filterOptions.OrderBy = orderBy => orderBy.Ascending(record => record[orderByPropertyName]);
}
// Act
var command = SqliteCommandBuilder.BuildSelectDataCommand<Dictionary<string, object?>>(this._connection, TableName, model, conditions, filterOptions);
// Assert
Assert.Contains("SELECT \"Id\",\"Name\",\"Age\",\"Address\"", command.CommandText);
Assert.Contains($"FROM \"{TableName}\"", command.CommandText);
Assert.Contains("\"Name\" = @Name0", command.CommandText);
Assert.Contains("\"Age\" IN (@Age0, @Age1, @Age2)", command.CommandText);
Assert.Equal(!string.IsNullOrWhiteSpace(orderByPropertyName), command.CommandText.Contains($"ORDER BY \"{orderByPropertyName}\""));
Assert.Equal("@Name0", command.Parameters[0].ParameterName);
Assert.Equal("NameValue", command.Parameters[0].Value);
Assert.Equal("@Age0", command.Parameters[1].ParameterName);
Assert.Equal(10, command.Parameters[1].Value);
Assert.Equal("@Age1", command.Parameters[2].ParameterName);
Assert.Equal(20, command.Parameters[2].Value);
Assert.Equal("@Age2", command.Parameters[3].ParameterName);
Assert.Equal(30, command.Parameters[3].Value);
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("Age")]
public void ItBuildsSelectInnerJoinCommand(string? orderByPropertyName)
{
// Arrange
const string DataTable = "DataTable";
const string VectorTable = "VectorTable";
const string JoinColumnName = "Id";
var model = BuildModel(
[
new VectorStoreKeyProperty("Id", typeof(string)),
new VectorStoreDataProperty("Name", typeof(string)),
new VectorStoreVectorProperty("Age", typeof(ReadOnlyMemory<float>), 10),
new VectorStoreVectorProperty("Address", typeof(ReadOnlyMemory<float>), 10),
]);
var conditions = new List<SqliteWhereCondition>
{
new SqliteWhereEqualsCondition("Name", "NameValue"),
new SqliteWhereInCondition("Age", [10, 20, 30]),
};
FilteredRecordRetrievalOptions<Dictionary<string, object?>> filterOptions = new();
if (!string.IsNullOrWhiteSpace(orderByPropertyName))
{
filterOptions.OrderBy = orderBy => orderBy.Ascending(record => record[orderByPropertyName]);
}
// Act
var command = SqliteCommandBuilder.BuildSelectInnerJoinCommand(
this._connection,
VectorTable,
DataTable,
JoinColumnName,
model,
conditions,
true,
filterOptions);
// Assert
Assert.Contains("SELECT \"DataTable\".\"Id\",\"DataTable\".\"Name\",\"VectorTable\".\"Age\",\"VectorTable\".\"Address\"", command.CommandText);
Assert.Contains("FROM \"VectorTable\"", command.CommandText);
Assert.Contains("INNER JOIN \"DataTable\" ON \"VectorTable\".\"Id\" = \"DataTable\".\"Id\"", command.CommandText);
Assert.Contains("\"Name\" = @Name0", command.CommandText);
Assert.Contains("\"Age\" IN (@Age0, @Age1, @Age2)", command.CommandText);
Assert.Equal(!string.IsNullOrWhiteSpace(orderByPropertyName), command.CommandText.Contains($"ORDER BY \"DataTable\".\"{orderByPropertyName}\""));
Assert.Equal("@Name0", command.Parameters[0].ParameterName);
Assert.Equal("NameValue", command.Parameters[0].Value);
Assert.Equal("@Age0", command.Parameters[1].ParameterName);
Assert.Equal(10, command.Parameters[1].Value);
Assert.Equal("@Age1", command.Parameters[2].ParameterName);
Assert.Equal(20, command.Parameters[2].Value);
Assert.Equal("@Age2", command.Parameters[3].ParameterName);
Assert.Equal(30, command.Parameters[3].Value);
}
[Fact]
public void ItBuildsDeleteCommand()
{
// Arrange
const string TableName = "TestTable";
var conditions = new List<SqliteWhereCondition>
{
new SqliteWhereEqualsCondition("Name", "NameValue"),
new SqliteWhereInCondition("Age", [10, 20, 30]),
};
// Act
var command = SqliteCommandBuilder.BuildDeleteCommand(this._connection, TableName, conditions);
// Assert
Assert.Contains("DELETE FROM \"TestTable\"", command.CommandText);
Assert.Contains("\"Name\" = @Name0", command.CommandText);
Assert.Contains("\"Age\" IN (@Age0, @Age1, @Age2)", command.CommandText);
Assert.Equal("@Name0", command.Parameters[0].ParameterName);
Assert.Equal("NameValue", command.Parameters[0].Value);
Assert.Equal("@Age0", command.Parameters[1].ParameterName);
Assert.Equal(10, command.Parameters[1].Value);
Assert.Equal("@Age1", command.Parameters[2].ParameterName);
Assert.Equal(20, command.Parameters[2].Value);
Assert.Equal("@Age2", command.Parameters[3].ParameterName);
Assert.Equal(30, command.Parameters[3].Value);
}
public void Dispose()
{
this._command.Dispose();
this._connection.Dispose();
}
private static CollectionModel BuildModel(List<VectorStoreProperty> properties)
=> new SqliteModelBuilder()
.BuildDynamic(new() { Properties = properties }, defaultEmbeddingGenerator: null);
}