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
3 changes: 2 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
- Use `ToModel()`/`ToDto()` in `Rest/Dto/Extensions.cs` for mapping

## Enum & Wire Format
- Use `ToEnumMemberString()`/`FromEnumMemberString<T>()` for conveting enums to and from strings
- Use `ToEnumMemberString()`/`FromEnumMemberString<T>()` for converting enums to and from strings
- Always prefer enums for permission actions and resource types
- Stick to .NET defaults for JSON serialization, using JsonStringEnumMemberName on enum values for specifying the string conversion, and System.Text.Json.Serialization.JsonStringEnumConverter on properties and fields to speciy the actual conversion on the properties.

## Testing
- Unit: xUnit, mock HTTP handler for REST
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ jobs:
strategy:
fail-fast: false
matrix:
version: ["1.31.20", "1.32.17", "1.33.5", "1.34.0"]
version: ["1.31.20", "1.32.17", "1.33.5", "1.34.0", "1.35.2"]
uses: ./.github/workflows/test-on-weaviate-version.yml
secrets: inherit
with:
Expand Down
36 changes: 18 additions & 18 deletions docs/PROPERTY_SYSTEM.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,55 +58,55 @@ The `DataType` enum defines all supported Weaviate data types with `[EnumMember]
```csharp
public enum DataType
{
[System.Runtime.Serialization.EnumMember(Value = "unknown")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("unknown")]
Unknown,

// Text types
[System.Runtime.Serialization.EnumMember(Value = "text")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("text")]
Text,
[System.Runtime.Serialization.EnumMember(Value = "text[]")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("text[]")]
TextArray,

// Numeric types
[System.Runtime.Serialization.EnumMember(Value = "int")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("int")]
Int,
[System.Runtime.Serialization.EnumMember(Value = "int[]")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("int[]")]
IntArray,
[System.Runtime.Serialization.EnumMember(Value = "number")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("number")]
Number,
[System.Runtime.Serialization.EnumMember(Value = "number[]")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("number[]")]
NumberArray,

// Boolean types
[System.Runtime.Serialization.EnumMember(Value = "boolean")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("boolean")]
Bool,
[System.Runtime.Serialization.EnumMember(Value = "boolean[]")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("boolean[]")]
BoolArray,

// Date types
[System.Runtime.Serialization.EnumMember(Value = "date")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("date")]
Date,
[System.Runtime.Serialization.EnumMember(Value = "date[]")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("date[]")]
DateArray,

// UUID types
[System.Runtime.Serialization.EnumMember(Value = "uuid")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("uuid")]
Uuid,
[System.Runtime.Serialization.EnumMember(Value = "uuid[]")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("uuid[]")]
UuidArray,

// Special types
[System.Runtime.Serialization.EnumMember(Value = "geoCoordinates")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("geoCoordinates")]
GeoCoordinate,
[System.Runtime.Serialization.EnumMember(Value = "blob")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("blob")]
Blob,
[System.Runtime.Serialization.EnumMember(Value = "phoneNumber")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("phoneNumber")]
PhoneNumber,

// Object types
[System.Runtime.Serialization.EnumMember(Value = "object")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("object")]
Object,
[System.Runtime.Serialization.EnumMember(Value = "object[]")]
[System.Text.Json.Serialization.JsonStringEnumMemberName("object[]")]
ObjectArray
}
```
Expand Down
2 changes: 1 addition & 1 deletion src/Weaviate.Client.Tests/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ public static string SortJsonDocument(JsonDocument document)
/// <summary>
/// Recursively sorts JsonNode properties
/// </summary>
private static JsonNode? SortJsonNode(JsonNode? node)
public static JsonNode? SortJsonNode(JsonNode? node)
{
if (node == null)
return null;
Expand Down
139 changes: 139 additions & 0 deletions src/Weaviate.Client.Tests/Integration/TestObjectTTL.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
using Weaviate.Client.Models;

namespace Weaviate.Client.Tests.Integration;

/// <summary>
/// The object TTL tests class
/// </summary>
/// <seealso cref="IntegrationTests"/>
public partial class ObjectTTL : IntegrationTests
{
/// <summary>
/// Initializes this instance
/// </summary>
/// <returns>The value task</returns>
public override async ValueTask InitializeAsync()
{
await base.InitializeAsync();

RequireVersion("1.35.0");
}

[Fact]
public async Task Test_ObjectTTL_Creation()
{
var collection = await CollectionFactory(
objectTTLConfig: ObjectTTLConfig.ByCreationTime(
TimeSpan.FromDays(30),
filterExpiredObjects: true
),
invertedIndexConfig: new InvertedIndexConfig { IndexTimestamps = true }
);

var config = await collection.Config.Get(TestContext.Current.CancellationToken);
Assert.NotNull(config.ObjectTTLConfig);
Assert.Equal("_creationTimeUnix", config.ObjectTTLConfig.DeleteOn);
Assert.Equal(30 * 24 * 3600, config.ObjectTTLConfig.DefaultTTL);
Assert.True(config.ObjectTTLConfig.FilterExpiredObjects);
}

[Fact]
public async Task Test_ObjectTTL_UpdateTime()
{
var collection = await CollectionFactory(
objectTTLConfig: ObjectTTLConfig.ByUpdateTime(
TimeSpan.FromDays(30),
filterExpiredObjects: true
),
invertedIndexConfig: new InvertedIndexConfig { IndexTimestamps = true }
);

var config = await collection.Config.Get(TestContext.Current.CancellationToken);
Assert.NotNull(config.ObjectTTLConfig);
Assert.Equal("_lastUpdateTimeUnix", config.ObjectTTLConfig.DeleteOn);
Assert.True(config.ObjectTTLConfig.FilterExpiredObjects);
Assert.Equal(30 * 24 * 3600, config.ObjectTTLConfig.DefaultTTL);
}

[Fact]
public async Task Test_ObjectTTL_CustomProperty()
{
var collection = await CollectionFactory(
properties: new[]
{
new Property { Name = "customDate", DataType = DataType.Date },
},
objectTTLConfig: ObjectTTLConfig.ByDateProperty(
"customDate",
-1,
filterExpiredObjects: false
),
invertedIndexConfig: new InvertedIndexConfig { IndexTimestamps = true }
);

var config = await collection.Config.Get(TestContext.Current.CancellationToken);
Assert.NotNull(config.ObjectTTLConfig);
Assert.Equal("customDate", config.ObjectTTLConfig.DeleteOn);
Assert.Equal(-1, config.ObjectTTLConfig.DefaultTTL);
Assert.False(config.ObjectTTLConfig.FilterExpiredObjects);
}

[Fact]
public async Task Test_ObjectTTL_Update()
{
var collection = await CollectionFactory<object>(
properties: new[]
{
new Property { Name = "customDate", DataType = DataType.Date },
new Property { Name = "customDate2", DataType = DataType.Date },
},
invertedIndexConfig: new InvertedIndexConfig { IndexTimestamps = true }
);

var conf = await collection.Config.Get(TestContext.Current.CancellationToken);
Assert.NotNull(conf.ObjectTTLConfig);
Assert.False(conf.ObjectTTLConfig.Enabled);

// Update to customDate
await collection.Config.Update(
c => c.ObjectTTLConfig.ByDateProperty("customDate", 3600, filterExpiredObjects: true),
cancellationToken: TestContext.Current.CancellationToken
);
conf = await collection.Config.Get(TestContext.Current.CancellationToken);
Assert.NotNull(conf.ObjectTTLConfig);
Assert.Equal("customDate", conf.ObjectTTLConfig.DeleteOn);
Assert.Equal(3600, conf.ObjectTTLConfig.DefaultTTL);
Assert.True(conf.ObjectTTLConfig.FilterExpiredObjects);

// Update to update time
await collection.Config.Update(
c => c.ObjectTTLConfig.ByUpdateTime(3600, filterExpiredObjects: false),
cancellationToken: TestContext.Current.CancellationToken
);
conf = await collection.Config.Get(TestContext.Current.CancellationToken);
Assert.NotNull(conf.ObjectTTLConfig);
Assert.Equal("_lastUpdateTimeUnix", conf.ObjectTTLConfig.DeleteOn);
Assert.Equal(3600, conf.ObjectTTLConfig.DefaultTTL);
Assert.False(conf.ObjectTTLConfig.FilterExpiredObjects);

// Update to creation time
await collection.Config.Update(
c => c.ObjectTTLConfig.ByCreationTime(600),
cancellationToken: TestContext.Current.CancellationToken
);
conf = await collection.Config.Get(TestContext.Current.CancellationToken);
Assert.NotNull(conf.ObjectTTLConfig);
Assert.Equal("_creationTimeUnix", conf.ObjectTTLConfig.DeleteOn);
Assert.Equal(600, conf.ObjectTTLConfig.DefaultTTL);
Assert.False(conf.ObjectTTLConfig.FilterExpiredObjects);

// Disable TTL
await collection.Config.Update(
c => c.ObjectTTLConfig.Disable(),
cancellationToken: TestContext.Current.CancellationToken
);
conf = await collection.Config.Get(TestContext.Current.CancellationToken);
Assert.NotNull(conf.ObjectTTLConfig);
Assert.False(conf.ObjectTTLConfig.Enabled);
}
}
12 changes: 9 additions & 3 deletions src/Weaviate.Client.Tests/Integration/_Integration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ public async Task<CollectionClient> CollectionFactory<TData>(CollectionCreatePar
/// <param name="rerankerConfig">The reranker config</param>
/// <param name="generativeConfig">The generative config</param>
/// <param name="collectionNamePartSeparator">The collection name part separator</param>
/// <param name="objectTTLConfig">The object TTL configuration</param>
/// <returns>A task containing the collection client</returns>
public async Task<CollectionClient> CollectionFactory<TData>(
string? name = null,
Expand All @@ -243,7 +244,8 @@ public async Task<CollectionClient> CollectionFactory<TData>(
ShardingConfig? shardingConfig = null,
IRerankerConfig? rerankerConfig = null,
IGenerativeConfig? generativeConfig = null,
string collectionNamePartSeparator = "_"
string collectionNamePartSeparator = "_",
ObjectTTLConfig? objectTTLConfig = null
)
{
name = MakeUniqueCollectionName<TData>(name, collectionNamePartSeparator);
Expand Down Expand Up @@ -272,6 +274,7 @@ public async Task<CollectionClient> CollectionFactory<TData>(
ShardingConfig = shardingConfig,
RerankerConfig = rerankerConfig,
GenerativeConfig = generativeConfig,
ObjectTTLConfig = objectTTLConfig,
};

return await CollectionFactory<TData>(c);
Expand All @@ -292,6 +295,7 @@ public async Task<CollectionClient> CollectionFactory<TData>(
/// <param name="rerankerConfig">The reranker config</param>
/// <param name="generativeConfig">The generative config</param>
/// <param name="collectionNamePartSeparator">The collection name part separator</param>
/// <param name="objectTTLConfig">The object TTL configuration</param>
/// <returns>A task containing the collection client</returns>
protected async Task<CollectionClient> CollectionFactory(
string? name = null,
Expand All @@ -305,7 +309,8 @@ protected async Task<CollectionClient> CollectionFactory(
ShardingConfig? shardingConfig = null,
IRerankerConfig? rerankerConfig = null,
IGenerativeConfig? generativeConfig = null,
string collectionNamePartSeparator = "_"
string collectionNamePartSeparator = "_",
ObjectTTLConfig? objectTTLConfig = null
)
{
return await CollectionFactory<object>(
Expand All @@ -320,7 +325,8 @@ protected async Task<CollectionClient> CollectionFactory(
shardingConfig,
rerankerConfig,
generativeConfig,
collectionNamePartSeparator
collectionNamePartSeparator,
objectTTLConfig
);
}

Expand Down
5 changes: 1 addition & 4 deletions src/Weaviate.Client.Tests/Unit/PermissionInfoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,9 @@ public async Task RolesClient_GetRole_WithFutureAction_ThrowsWeaviateClientExcep
);

// Act & Assert: should throw WeaviateClientException with upgrade guidance.
var ex = await Assert.ThrowsAsync<WeaviateClientException>(async () =>
var ex = await Assert.ThrowsAnyAsync<WeaviateClientException>(async () =>
await client.Roles.Get(roleName, TestContext.Current.CancellationToken)
);
Assert.Contains("future_new_action", ex.Message);
Assert.Contains("PermissionAction", ex.Message);
Assert.Contains("client", ex.Message.ToLower());
}

/// <summary>
Expand Down
20 changes: 13 additions & 7 deletions src/Weaviate.Client.Tests/Unit/TestCollection.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Text.Json;
using System.Text.Json.JsonDiffPatch;
using System.Text.Json.Nodes;
using Weaviate.Client.Models;

namespace Weaviate.Client.Tests.Unit;
Expand Down Expand Up @@ -389,6 +391,12 @@ public void Collection_Import_Export_Are_Equal()
}},
""usingBlockMaxWAND"": true
}},
""objectTtlConfig"": {{
""defaultTtl"": 0,
""deleteOn"": ""_creationTimeUnix"",
""enabled"": false,
""filterExpiredObjects"": false
}},
""multiTenancyConfig"": {{
""autoTenantActivation"": false,
""autoTenantCreation"": false,
Expand Down Expand Up @@ -511,14 +519,12 @@ public void Collection_Import_Export_Are_Equal()
);

// Parse as JsonElement for semantic comparison (ignoring property order)
using var expectedDoc = JsonDocument.Parse(expectedJson);
using var actualDoc = JsonDocument.Parse(actualJson);
var expectedDoc = JsonNode.Parse(expectedJson);
var actualDoc = JsonNode.Parse(actualJson);

// Use JsonElement.DeepEquals for semantic comparison
Assert.True(
JsonElement.DeepEquals(expectedDoc.RootElement, actualDoc.RootElement),
$"JSON structures differ:\nExpected:\n{expectedJson}\n\nActual:\n{actualJson}"
);
var diff = expectedDoc.Diff(actualDoc);

Assert.True(diff == null, diff?.ToJsonString());
}

/// <summary>
Expand Down
19 changes: 13 additions & 6 deletions src/Weaviate.Client.Tests/Unit/TestInvertedIndexConfig.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Text.Json.JsonDiffPatch;
using System.Text.Json.Nodes;
using Weaviate.Client.Models;
using Weaviate.Client.Rest;

Expand Down Expand Up @@ -334,16 +336,21 @@ public void Serialize_InvertedIndexConfig()
},
};

var expectedJson =
@"{""bm25"":{""b"":0.7,""k1"":1.3},""cleanupIntervalSeconds"":30,""indexNullState"":true,""indexPropertyLength"":true,""indexTimestamps"":true,""stopwords"":{""additions"":[""plus""],""preset"":""none"",""removals"":[""minus""]}}";
var expectedJson = JsonNode.Parse(
@"{""bm25"":{""b"":0.7,""k1"":1.3},""cleanupIntervalSeconds"":30,""indexNullState"":true,""indexPropertyLength"":true,""indexTimestamps"":true,""stopwords"":{""additions"":[""plus""],""preset"":""none"",""removals"":[""minus""]}}"
);

// Act
var json = System.Text.Json.JsonSerializer.Serialize(
config,
WeaviateRestClient.RestJsonSerializerOptions
var json = JsonNode.Parse(
System.Text.Json.JsonSerializer.Serialize(
config,
WeaviateRestClient.RestJsonSerializerOptions
)
);

// Assert
Assert.True(JsonComparer.AreJsonEqual(expectedJson, json));
var diff = expectedJson.Diff(json);

Assert.True(diff == null, diff?.ToJsonString());
}
}
1 change: 1 addition & 0 deletions src/Weaviate.Client.Tests/Weaviate.Client.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="PublicApiGenerator" Version="11.5.4" />
<PackageReference Include="System.Linq.Async" Version="6.0.3" />
<PackageReference Include="SystemTextJson.JsonDiffPatch" Version="2.0.0" />
<PackageReference Include="xunit.v3" Version="3.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Loading