Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Current package versions:

## Unreleased

- (none)
- Fix logic inversion with `ARGREP NOCASE`, and add `IsReversed` to simplify ordering. ([#3087 by @mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/3087))

## 2.13.10

Expand Down
60 changes: 45 additions & 15 deletions src/StackExchange.Redis/ArrayGrepRequest.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using RESPite;

Expand All @@ -16,12 +17,14 @@ private enum LocalFlags : byte
{
None = 0,
IsFrozen = 1 << 0,
CaseSensitive = 1 << 1,
CaseInsensitive = 1 << 1,
IsIntersection = 1 << 2,
StartSpecified = 1 << 3,
EndSpecified = 1 << 4,
LimitSpecified = 1 << 5,
IncludeValues = 1 << 6,
Reversed = 1 << 7,
// warning: next flag needs : ushort
}

private void Freeze() => _flags |= LocalFlags.IsFrozen;
Expand Down Expand Up @@ -131,13 +134,37 @@ public long? Limit
private long _limit;

/// <summary>
/// Indicates whether matches are performed in a case-insensitive manner.
/// Indicates whether matches are performed in a case-sensitive manner.
/// </summary>
/// <remarks>Corresponds to the <c>NOCASE</c> parameter.</remarks>
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Prefer " + nameof(IsCaseInsensitive))]
public bool IsCaseSensitive
{
get => GetFlag(LocalFlags.CaseSensitive);
set => SetFlag(LocalFlags.CaseSensitive, value);
get => !IsCaseInsensitive;
set => IsCaseInsensitive = !value;
}

/// <summary>
/// Indicates whether matches are performed in a case-insensitive manner.
/// </summary>
/// <remarks>Corresponds to the <c>NOCASE</c> parameter.</remarks>
public bool IsCaseInsensitive
{
get => GetFlag(LocalFlags.CaseInsensitive);
set => SetFlag(LocalFlags.CaseInsensitive, value);
}

/// <summary>
/// Indicates whether the query order should be reversed; this is equivalent to
/// reversing the order of <see cref="Start"/> and <see cref="End"/>.
/// </summary>
/// <remarks>Corresponds to the <c>NOCASE</c> parameter.</remarks>
public bool IsReversed
{
get => GetFlag(LocalFlags.Reversed);
set => SetFlag(LocalFlags.Reversed, value);
}

/// <summary>
Expand Down Expand Up @@ -317,35 +344,38 @@ public override int ArgCount
}

if (request.IsIntersection) count++;
if (request.IsCaseSensitive) count++;
if (request.IsCaseInsensitive) count++;
if (request.IncludeValues) count++;
var limit = request.Limit;
if (limit.HasValue) count += 2;
return count;
}
}

protected override void WriteImpl(PhysicalConnection physical)
private static void AddIndex(PhysicalConnection physical, RedisArrayIndex? index, ReadOnlySpan<byte> fallback)
{
physical.WriteHeader(Command, ArgCount);
physical.WriteBulkString(key);
var index = request.Start;
if (index.HasValue)
{
physical.WriteBulkString(index.GetValueOrDefault().Value);
}
else
{
physical.WriteRaw("$1\r\n-\r\n"u8);
physical.WriteRaw(fallback);
}
index = request.End;
if (index.HasValue)
}
protected override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(Command, ArgCount);
physical.WriteBulkString(key);
if (request.IsReversed)
{
physical.WriteBulkString(index.GetValueOrDefault().Value);
AddIndex(physical, request.End, "$1\r\n+\r\n"u8);
AddIndex(physical, request.Start, "$1\r\n-\r\n"u8);
}
else
{
physical.WriteRaw("$1\r\n+\r\n"u8);
AddIndex(physical, request.Start, "$1\r\n-\r\n"u8);
AddIndex(physical, request.End, "$1\r\n+\r\n"u8);
}
var pCount = request.Count;
for (int i = 0; i < pCount; i++)
Expand All @@ -354,7 +384,7 @@ protected override void WriteImpl(PhysicalConnection physical)
}

if (request.IsIntersection) physical.WriteRaw("$3\r\nAND\r\n"u8);
if (request.IsCaseSensitive) physical.WriteRaw("$6\r\nNOCASE\r\n"u8);
if (request.IsCaseInsensitive) physical.WriteRaw("$6\r\nNOCASE\r\n"u8);
if (request.IncludeValues) physical.WriteRaw("$10\r\nWITHVALUES\r\n"u8);
var limit = request.Limit;
if (limit.HasValue)
Expand Down
4 changes: 4 additions & 0 deletions src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
#nullable enable
[SER006]StackExchange.Redis.ArrayGrepRequest.IsCaseInsensitive.get -> bool
[SER006]StackExchange.Redis.ArrayGrepRequest.IsCaseInsensitive.set -> void
[SER006]StackExchange.Redis.ArrayGrepRequest.IsReversed.get -> bool
[SER006]StackExchange.Redis.ArrayGrepRequest.IsReversed.set -> void
22 changes: 14 additions & 8 deletions tests/StackExchange.Redis.Tests/ArrayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ public async Task GrepBasics()
Assert.Equal(4, await db.ArraySetAsync(key, [Entry(0, "RedisArray"), Entry(1, "redis-match"), Entry(2, "array-only"), Entry(3, "plain")]));
var andNoCase = CreateGrep(ArrayGrepRequest.Predicate.Match("redis"), ArrayGrepRequest.Predicate.Glob("*array*"));
andNoCase.IsIntersection = true;
andNoCase.IsCaseSensitive = true;
andNoCase.IsCaseInsensitive = true;
AssertIndexEntries(await db.ArrayGrepAsync(key, andNoCase), 0);

await db.KeyDeleteAsync(key);
Expand All @@ -264,7 +264,7 @@ public async Task GrepRegexAndErrors()
AssertIndexEntries(await db.ArrayGrepAsync(key, CreateGrep(ArrayGrepRequest.Predicate.Regex("^.*[0-9]{3}$"))), 0, 2, 3);

var noCase = CreateGrep(ArrayGrepRequest.Predicate.Regex("^foo[0-9]+$"));
noCase.IsCaseSensitive = true;
noCase.IsCaseInsensitive = true;
AssertIndexEntries(await db.ArrayGrepAsync(key, noCase), 0, 3);

await db.KeyDeleteAsync(key);
Expand All @@ -277,29 +277,35 @@ public async Task GrepRegexAndErrors()

AssertIndexEntries(await db.ArrayGrepAsync(key, CreateGrep(ArrayGrepRequest.Predicate.Regex("foo|bar"))), 0, 1, 3, 5, 6);
noCase = CreateGrep(ArrayGrepRequest.Predicate.Regex("foo|bar"));
noCase.IsCaseSensitive = true;
noCase.IsCaseInsensitive = true;
AssertIndexEntries(await db.ArrayGrepAsync(key, noCase), 0, 1, 3, 4, 5, 6);

// and same again, with reversed start/end
noCase = CreateGrep(ArrayGrepRequest.Predicate.Regex("foo|bar"));
noCase.IsCaseInsensitive = true;
noCase.IsReversed = true;
AssertIndexEntries(await db.ArrayGrepAsync(key, noCase), 6, 5, 4, 3, 1, 0);

noCase = CreateGrep(ArrayGrepRequest.Predicate.Regex("^(foo|bar)$"));
noCase.IsCaseSensitive = true;
noCase.IsCaseInsensitive = true;
AssertIndexEntries(await db.ArrayGrepAsync(key, noCase), 0, 1, 4);

noCase = CreateGrep(ArrayGrepRequest.Predicate.Regex("^(foo|bar)"));
noCase.IsCaseSensitive = true;
noCase.IsCaseInsensitive = true;
AssertIndexEntries(await db.ArrayGrepAsync(key, noCase), 0, 1, 3, 4);

noCase = CreateGrep(ArrayGrepRequest.Predicate.Regex("(foo|bar)$"));
noCase.IsCaseSensitive = true;
noCase.IsCaseInsensitive = true;
AssertIndexEntries(await db.ArrayGrepAsync(key, noCase), 0, 1, 3, 4, 5, 6);

noCase = CreateGrep(ArrayGrepRequest.Predicate.Regex("alpha|alps"));
noCase.IsCaseSensitive = true;
noCase.IsCaseInsensitive = true;
AssertIndexEntries(await db.ArrayGrepAsync(key, noCase), 8, 9);

await db.KeyDeleteAsync(key);
Assert.Equal(4, await db.ArraySetAsync(key, [Entry(0, "item-foo-123"), Entry(1, "ITEM-BAR-456"), Entry(2, "item-baz"), Entry(3, "plain")]));
noCase = CreateGrep(ArrayGrepRequest.Predicate.Regex("^item-(foo|bar)-[0-9]{3}$"));
noCase.IsCaseSensitive = true;
noCase.IsCaseInsensitive = true;
AssertIndexEntries(await db.ArrayGrepAsync(key, noCase), 0, 1);

await db.KeyDeleteAsync(key);
Expand Down
Loading