Skip to content

Commit

Permalink
Fixed CursorParser to use '\' as escape instead of ':' (#7962)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Staib <[email protected]>
  • Loading branch information
onionhammer and michaelstaib committed Jan 29, 2025
1 parent b55d403 commit 6b01035
Show file tree
Hide file tree
Showing 27 changed files with 400 additions and 263 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ file static class Extensions
{
public static Snapshot AddSql(this Snapshot snapshot, List<string> queries)
{
snapshot.Add(string.Join("\n", queries), "SQL");
snapshot.Add(string.Join("\n", queries).Replace("@__p_1_startswith", "@__p_1_rewritten"), "SQL");
return snapshot;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

```text
.param set @__keys_0 '[1]'
.param set @__p_1_startswith 'Product%'
.param set @__p_1_rewritten 'Product%'
SELECT "p"."Id", "p"."AvailableStock", "p"."BrandId", "p"."Description", "p"."ImageFileName", "p"."MaxStockThreshold", "p"."Name", "p"."OnReorder", "p"."Price", "p"."RestockThreshold", "p"."TypeId"
FROM "Products" AS "p"
WHERE "p"."BrandId" IN (
SELECT "k"."value"
FROM json_each(@__keys_0) AS "k"
) AND "p"."Name" LIKE @__p_1_startswith ESCAPE '\'
) AND "p"."Name" LIKE @__p_1_rewritten ESCAPE '\'
```

## Result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace HotChocolate.Pagination.Expressions;
/// </summary>
public static class CursorParser
{
private const byte _escape = (byte)':';
private const byte _escape = (byte)'\\';
private const byte _separator = (byte)':';

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public static class CursorKeySerializerHelper

public static bool TryFormat(object? key, ICursorKeySerializer serializer, Span<byte> buffer, out int written)
{
var success = false;
bool success;

if (key is null)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System.Linq.Expressions;
using System.Text;
using System.Text.Unicode;
using HotChocolate.Pagination.Expressions;
using HotChocolate.Pagination.Serialization;

namespace HotChocolate.Data;

public class CursorFormatterTests
{
[Fact]
public void Format_Single_Key()
{
// arrange
var entity = new MyClass { Name = "test" };
Expression<Func<MyClass, object>> selector = x => x.Name;
var serializer = new StringCursorKeySerializer();

// act
var result = CursorFormatter.Format(entity, [new CursorKey(selector, serializer)]);

// assert
Assert.Equal("dGVzdA==", result);
Assert.Equal("test", Encoding.UTF8.GetString(Convert.FromBase64String(result)));
}

[Fact]
public void Format_Single_Key_With_Colon()
{
// arrange
var entity = new MyClass { Name = "test:test" };
Expression<Func<MyClass, object>> selector = x => x.Name;
var serializer = new StringCursorKeySerializer();

// act
var result = CursorFormatter.Format(entity, [new CursorKey(selector, serializer)]);

// assert
Assert.Equal("dGVzdFw6dGVzdA==", result);
Assert.Equal("test\\:test", Encoding.UTF8.GetString(Convert.FromBase64String(result)));
}

[Fact]
public void Format_Two_Keys()
{
// arrange
var entity = new MyClass { Name = "test", Description = "description" };
Expression<Func<MyClass, object?>> selector1 = x => x.Name;
Expression<Func<MyClass, object?>> selector2 = x => x.Description;
var serializer = new StringCursorKeySerializer();

// act
var result = CursorFormatter.Format(
entity,
[
new CursorKey(selector1, serializer),
new CursorKey(selector2, serializer)
]);

// assert
Assert.Equal("dGVzdDpkZXNjcmlwdGlvbg==", result);
Assert.Equal("test:description", Encoding.UTF8.GetString(Convert.FromBase64String(result)));
}

[Fact]
public void Format_Two_Keys_With_Colon()
{
// arrange
var entity = new MyClass { Name = "test:345", Description = "description:123" };
Expression<Func<MyClass, object?>> selector1 = x => x.Name;
Expression<Func<MyClass, object?>> selector2 = x => x.Description;
var serializer = new StringCursorKeySerializer();

// act
var result = CursorFormatter.Format(
entity,
[
new CursorKey(selector1, serializer),
new CursorKey(selector2, serializer)
]);

// assert
Assert.Equal("dGVzdFw6MzQ1OmRlc2NyaXB0aW9uXDoxMjM=", result);
Assert.Equal("test\\:345:description\\:123", Encoding.UTF8.GetString(Convert.FromBase64String(result)));
}

[Fact]
public void Format_And_Parse_Two_Keys_With_Colon()
{
// arrange
var entity = new MyClass { Name = "test:345", Description = "description:123" };
Expression<Func<MyClass, object?>> selector1 = x => x.Name;
Expression<Func<MyClass, object?>> selector2 = x => x.Description;
var serializer = new StringCursorKeySerializer();

// act
var formatted = CursorFormatter.Format(
entity,
[
new CursorKey(selector1, serializer),
new CursorKey(selector2, serializer)
]);
var parsed =CursorParser.Parse(
formatted,
[
new CursorKey(selector1, serializer),
new CursorKey(selector2, serializer)
]);

// assert
Assert.Equal("test:345", parsed[0]);
Assert.Equal("description:123", parsed[1]);
}

public class MyClass
{
public string Name { get; set; } = default!;

public string? Description { get; set; } = default!;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,20 @@ public static void TryFormat_BufferTooSmall_ReturnsFalse()
Assert.False(success);
Assert.Equal(0, written);
}

[Fact]
public static void String_With_Colon_Format_And_Parse()
{
// arrange
object key = "part1:part2";
Span<byte> buffer = new byte[1024];

// act
CursorKeySerializerHelper.TryFormat(key, _serializer, buffer, out var written);
var parsedString =CursorKeySerializerHelper.Parse(buffer.Slice(0, written), _serializer);


// assert
Assert.Equal(key, parsedString);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ public async Task GetDefaultPage_With_Nullable_Fallback_SecondPage()
.SetDocument(
"""
{
brandsNullableFallback(first: 2, after: "QnJhbmQxMToxMg==") {
brandsNullableFallback(first: 2, after: "QnJhbmRcOjExOjEy") {
edges {
cursor
}
Expand Down Expand Up @@ -1209,7 +1209,7 @@ private static async Task SeedAsync(string connectionString)
{
var brand = new Brand
{
Name = "Brand" + i,
Name = "Brand:" + i,
DisplayName = i % 2 == 0 ? "BrandDisplay" + i : null,
BrandDetails = new() { Country = new() { Name = "Country" + i } }
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,50 +25,50 @@ LIMIT @__p_0
"nodes": [
{
"id": 1,
"name": "Brand0"
"name": "Brand:0"
},
{
"id": 2,
"name": "Brand1"
"name": "Brand:1"
},
{
"id": 11,
"name": "Brand10"
"name": "Brand:10"
},
{
"id": 12,
"name": "Brand11"
"name": "Brand:11"
},
{
"id": 13,
"name": "Brand12"
"name": "Brand:12"
},
{
"id": 14,
"name": "Brand13"
"name": "Brand:13"
},
{
"id": 15,
"name": "Brand14"
"name": "Brand:14"
},
{
"id": 16,
"name": "Brand15"
"name": "Brand:15"
},
{
"id": 17,
"name": "Brand16"
"name": "Brand:16"
},
{
"id": 18,
"name": "Brand17"
"name": "Brand:17"
}
],
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "QnJhbmQwOjE=",
"endCursor": "QnJhbmQxNzoxOA=="
"startCursor": "QnJhbmRcOjA6MQ==",
"endCursor": "QnJhbmRcOjE3OjE4"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,50 +25,50 @@ LIMIT @__p_0
"nodes": [
{
"id": 1,
"name": "Brand0"
"name": "Brand:0"
},
{
"id": 2,
"name": "Brand1"
"name": "Brand:1"
},
{
"id": 11,
"name": "Brand10"
"name": "Brand:10"
},
{
"id": 12,
"name": "Brand11"
"name": "Brand:11"
},
{
"id": 13,
"name": "Brand12"
"name": "Brand:12"
},
{
"id": 14,
"name": "Brand13"
"name": "Brand:13"
},
{
"id": 15,
"name": "Brand14"
"name": "Brand:14"
},
{
"id": 16,
"name": "Brand15"
"name": "Brand:15"
},
{
"id": 17,
"name": "Brand16"
"name": "Brand:16"
},
{
"id": 18,
"name": "Brand17"
"name": "Brand:17"
}
],
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "QnJhbmQwOjE=",
"endCursor": "QnJhbmQxNzoxOA=="
"startCursor": "QnJhbmRcOjA6MQ==",
"endCursor": "QnJhbmRcOjE3OjE4"
}
}
}
Expand Down
Loading

0 comments on commit 6b01035

Please sign in to comment.