Skip to content

Commit

Permalink
feat: Add the ability to compare existing index definitions to the pr…
Browse files Browse the repository at this point in the history
…ojected one for a type.
  • Loading branch information
James Abbott committed Sep 2, 2024
1 parent 7a35e47 commit 085eaaf
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 1 deletion.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ var provider = new RedisConnectionProvider("redis://localhost:6379");
provider.Connection.CreateIndex(typeof(Customer));
```

Redis OM provides limited support for schema and data migration at this time. We provide a small extension method `IndexDefinitionEquals` on the `RedisIndexInfo` type that you may opt in to use to determine when to re-create your indexes when your types change. An example implementation of this would look like:

```csharp
var provider = new RedisConnectionProvider("redis://localhost:6379");
var definition = provider.Connection.GetIndexInfo(typeof(Customer));

if (definition.IndexDefinitionEquals(typeof(Customer)) == false)
{
provider.Connection.DropIndex(typeof(Customer));
}

provider.Connection.CreateIndex(typeof(Customer));
```


### Indexing Embedded Documents

There are two methods for indexing embedded documents with Redis.OM, an embedded document is a complex object, e.g. if our `Customer` model had an `Address` property with the following model:
Expand Down
70 changes: 69 additions & 1 deletion src/Redis.OM/Modeling/RedisIndex.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Redis.OM;
using Redis.OM.Modeling;

Expand All @@ -8,8 +9,75 @@ namespace Redis.OM.Modeling
/// <summary>
/// A utility class for serializing objects into Redis Indices.
/// </summary>
internal static class RedisIndex
public static class RedisIndex
{
/// <summary>
/// Verifies whether the given index schema definition matches the current definition.
/// </summary>
/// <param name="redisIndexInfo">The index definition.</param>
/// <param name="type">The type to be indexed.</param>
/// <returns>A bool indicating whether the current index definition has drifted from the current definition, which may be used to determine when to re-create an index..</returns>
public static bool IndexDefinitionEquals(this RedisIndexInfo redisIndexInfo, Type type)
{
var serialisedDefinition = SerializeIndex(type);
var existingSet = redisIndexInfo.Attributes?.Select(a => (Property: a.Attribute!, a.Type!)).OrderBy(a => a.Property);
var isJson = redisIndexInfo.IndexDefinition?.Identifier == "JSON";

if (serialisedDefinition.Length < 5)
{
throw new ArgumentException($"Could not parse the index definition for type: {type.Name}.");
}

if (serialisedDefinition)

if (redisIndexInfo.IndexName != serialisedDefinition[0])
{
return false;
}

if (redisIndexInfo.IndexDefinition?.Identifier?.Equals(serialisedDefinition[2], StringComparison.OrdinalIgnoreCase) == false)
{
return false;
}

if (redisIndexInfo.IndexDefinition?.Prefixes.FirstOrDefault().Equals(serialisedDefinition[5]) == false)
{
return false;
}

var target = redisIndexInfo.Attributes?.SelectMany(a =>
{
var attr = new List<string>();
if (a.Identifier == null)
{
return Array.Empty<string>();
}
if (isJson)
{
attr.Add(a.Identifier);
attr.Add("AS");
}
attr.Add(a.Attribute!);
if (a.Type != null)
{
attr.Add(a.Type);
}
if (a.Sortable == true)
{
attr.Add("SORTABLE");
}
return attr.ToArray();
});

return target.SequenceEqual(serialisedDefinition.Skip(7));
}

/// <summary>
/// Pull out the Document attribute from a Type.
/// </summary>
Expand Down
59 changes: 59 additions & 0 deletions test/Redis.OM.Unit.Tests/RediSearchTests/RedisIndexTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Redis.OM.Contracts;
using Redis.OM.Modeling;
using Xunit;

Expand All @@ -20,6 +21,34 @@ public class TestPersonClassHappyPath
public string[] NickNames { get; set; }
}

[Document(IndexName = "TestPersonClassHappyPath-idx", Prefixes = new []{"Simple"}, StorageType = StorageType.Hash)]
public class TestPersonClassHappyPathWithMutatedDefinition
{
public string Name { get; set; }
[Indexed(Sortable = true)]
public int Age { get; set; }
public double Height { get; set; }
}

[Document(IndexName = "SerialisedJson-idx", Prefixes = new []{"Simple"}, StorageType = StorageType.Json)]
public class SerialisedJsonType
{
[Searchable(Sortable = true)]
public string Name { get; set; }

public int Age { get; set; }
}

[Document(IndexName = "SerialisedJson-idx", Prefixes = new []{"Simple"}, StorageType = StorageType.Json)]
public class SerialisedJsonTypeNotMatch
{
[Searchable(Sortable = true)]
public string Name { get; set; }

[Indexed(Sortable = true)]
public int Age { get; set; }
}

[Document(IndexName = "TestPersonClassHappyPath-idx", StorageType = StorageType.Hash, Prefixes = new []{"Person:"})]
public class TestPersonClassOverridenPrefix
{
Expand Down Expand Up @@ -198,5 +227,35 @@ public async Task TestGetIndexInfoWhichDoesNotExistAsync()
var indexInfo = await connection.GetIndexInfoAsync(typeof(TestPersonClassHappyPath));
Assert.Null(indexInfo);
}

[Fact]
public async Task TestGetIndexInfoWhichDoesNotMatchExisting()
{
var host = Environment.GetEnvironmentVariable("STANDALONE_HOST_PORT") ?? "localhost";
var provider = new RedisConnectionProvider($"redis://{host}");
var connection = provider.Connection;

await connection.DropIndexAsync(typeof(TestPersonClassHappyPath));
await connection.CreateIndexAsync(typeof(TestPersonClassHappyPath));
var indexInfo = await connection.GetIndexInfoAsync(typeof(TestPersonClassHappyPath));

Assert.False(indexInfo.IndexDefinitionEquals(typeof(TestPersonClassHappyPathWithMutatedDefinition)));
Assert.True(indexInfo.IndexDefinitionEquals(typeof(TestPersonClassHappyPath)));
}

[Fact]
public async Task TestGetIndexInfoWhichDoesNotMatchExistingJson()
{
var host = Environment.GetEnvironmentVariable("STANDALONE_HOST_PORT") ?? "localhost";
var provider = new RedisConnectionProvider($"redis://{host}");
var connection = provider.Connection;

await connection.DropIndexAsync(typeof(SerialisedJsonType));
await connection.CreateIndexAsync(typeof(SerialisedJsonType));
var indexInfo = await connection.GetIndexInfoAsync(typeof(SerialisedJsonType));

Assert.False(indexInfo.IndexDefinitionEquals(typeof(SerialisedJsonTypeNotMatch)));
Assert.True(indexInfo.IndexDefinitionEquals(typeof(SerialisedJsonType)));
}
}
}

0 comments on commit 085eaaf

Please sign in to comment.