From 27b8a83a7a1a8ea6c868904346e5f21fbc0577b9 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Wed, 21 Aug 2024 21:03:38 -0400 Subject: [PATCH] Add case-insensitive ordering --- src/Common/Library.fs | 11 +++++++---- src/Tests.CSharp/CommonCSharpTests.cs | 12 ++++++++++++ src/Tests.CSharp/SqliteCSharpTests.cs | 28 +++++++++++++++++++++++++-- src/Tests/CommonTests.fs | 12 ++++++++++++ src/Tests/SqliteTests.fs | 24 +++++++++++++++++++++++ 5 files changed, 81 insertions(+), 6 deletions(-) diff --git a/src/Common/Library.fs b/src/Common/Library.fs index 428c859..dcdd584 100644 --- a/src/Common/Library.fs +++ b/src/Common/Library.fs @@ -401,10 +401,13 @@ module Query = { it with Name = parts[0] }, Some $" {parts[1]}" else it, None) |> Seq.map (fun (field, direction) -> - match dialect, field.Name.StartsWith "n:" with - | PostgreSQL, true -> $"({ { field with Name = field.Name[2..] }.Path PostgreSQL})::numeric" - | SQLite, true -> { field with Name = field.Name[2..] }.Path SQLite - | _, _ -> field.Path dialect + if field.Name.StartsWith "n:" then + let f = { field with Name = field.Name[2..] } + match dialect with PostgreSQL -> $"({f.Path PostgreSQL})::numeric" | SQLite -> f.Path SQLite + elif field.Name.StartsWith "i:" then + let p = { field with Name = field.Name[2..] }.Path dialect + match dialect with PostgreSQL -> $"LOWER({p})" | SQLite -> $"{p} COLLATE NOCASE" + else field.Path dialect |> function path -> path + defaultArg direction "") |> String.concat ", " |> function it -> $" ORDER BY {it}" diff --git a/src/Tests.CSharp/CommonCSharpTests.cs b/src/Tests.CSharp/CommonCSharpTests.cs index 0a0ca67..397e29c 100644 --- a/src/Tests.CSharp/CommonCSharpTests.cs +++ b/src/Tests.CSharp/CommonCSharpTests.cs @@ -613,6 +613,18 @@ public static class CommonCSharpTests { Expect.equal(Query.OrderBy([Field.Named("n:Test")], Dialect.SQLite), " ORDER BY data->>'Test'", "Order By not constructed correctly for numeric field"); + }), + TestCase("succeeds for PostgreSQL case-insensitive ordering", () => + { + Expect.equal(Query.OrderBy([Field.Named("i:Test.Field DESC")], Dialect.PostgreSQL), + " ORDER BY LOWER(data#>>'{Test,Field}') DESC", + "Order By not constructed correctly for case-insensitive field"); + }), + TestCase("succeeds for SQLite case-insensitive ordering", () => + { + Expect.equal(Query.OrderBy([Field.Named("i:Test.Field ASC")], Dialect.SQLite), + " ORDER BY data->>'Test'->>'Field' COLLATE NOCASE ASC", + "Order By not constructed correctly for case-insensitive field"); }) ]) ]); diff --git a/src/Tests.CSharp/SqliteCSharpTests.cs b/src/Tests.CSharp/SqliteCSharpTests.cs index 2ea73ca..71d38b4 100644 --- a/src/Tests.CSharp/SqliteCSharpTests.cs +++ b/src/Tests.CSharp/SqliteCSharpTests.cs @@ -627,8 +627,9 @@ public static class SqliteCSharpTests var docs = await Find.ByFieldsOrdered(SqliteDb.TableName, FieldMatch.Any, [Field.GT("NumValue", 15)], [Field.Named("Id")]); + Expect.hasLength(docs, 2, "There should have been two documents returned"); Expect.equal(string.Join('|', docs.Select(x => x.Id)), "five|four", - "There should have been two documents returned"); + "The documents were not sorted correctly"); }), TestCase("succeeds when documents are not found", async () => { @@ -637,8 +638,31 @@ public static class SqliteCSharpTests var docs = await Find.ByFieldsOrdered(SqliteDb.TableName, FieldMatch.Any, [Field.GT("NumValue", 15)], [Field.Named("Id DESC")]); + Expect.hasLength(docs, 2, "There should have been two documents returned"); Expect.equal(string.Join('|', docs.Select(x => x.Id)), "four|five", - "There should have been two documents returned"); + "The documents were not sorted correctly"); + }), + TestCase("succeeds when sorting case-sensitively", async () => + { + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByFieldsOrdered(SqliteDb.TableName, FieldMatch.Any, + [Field.LE("NumValue", 10)], [Field.Named("Value")]); + Expect.hasLength(docs, 3, "There should have been three documents returned"); + Expect.equal(string.Join('|', docs.Select(x => x.Id)), "three|one|two", + "The documents were not sorted correctly"); + }), + TestCase("succeeds when sorting case-insensitively", async () => + { + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByFieldsOrdered(SqliteDb.TableName, FieldMatch.Any, + [Field.LE("NumValue", 10)], [Field.Named("i:Value")]); + Expect.hasLength(docs, 3, "There should have been three documents returned"); + Expect.equal(string.Join('|', docs.Select(x => x.Id)), "three|two|one", + "The documents were not sorted correctly"); }) ]), TestList("FirstByFields", diff --git a/src/Tests/CommonTests.fs b/src/Tests/CommonTests.fs index ad0da18..16bea05 100644 --- a/src/Tests/CommonTests.fs +++ b/src/Tests/CommonTests.fs @@ -450,6 +450,18 @@ let queryTests = testList "Query" [ " ORDER BY data->>'Test'" "Order By not constructed correctly for numeric field" } + test "succeeds for PostgreSQL case-insensitive ordering" { + Expect.equal + (Query.orderBy [ Field.Named "i:Test.Field DESC" ] PostgreSQL) + " ORDER BY LOWER(data#>>'{Test,Field}') DESC" + "Order By not constructed correctly for case-insensitive field" + } + test "succeeds for SQLite case-insensitive ordering" { + Expect.equal + (Query.orderBy [ Field.Named "i:Test.Field ASC" ] SQLite) + " ORDER BY data->>'Test'->>'Field' COLLATE NOCASE ASC" + "Order By not constructed correctly for case-insensitive field" + } ] ] diff --git a/src/Tests/SqliteTests.fs b/src/Tests/SqliteTests.fs index ebf0293..1a2e71d 100644 --- a/src/Tests/SqliteTests.fs +++ b/src/Tests/SqliteTests.fs @@ -527,6 +527,7 @@ let findTests = testList "Find" [ let! docs = Find.byFieldsOrdered SqliteDb.TableName Any [ Field.GT "NumValue" 15 ] [ Field.Named "Id" ] + Expect.hasLength docs 2 "There should have been two documents returned" Expect.equal (docs |> List.map _.Id |> String.concat "|") "five|four" "The documents were not ordered correctly" } @@ -537,9 +538,32 @@ let findTests = testList "Find" [ let! docs = Find.byFieldsOrdered SqliteDb.TableName Any [ Field.GT "NumValue" 15 ] [ Field.Named "Id DESC" ] + Expect.hasLength docs 2 "There should have been two documents returned" Expect.equal (docs |> List.map _.Id |> String.concat "|") "four|five" "The documents were not ordered correctly" } + testTask "succeeds when sorting case-sensitively" { + use! db = SqliteDb.BuildDb() + do! loadDocs () + + let! docs = + Find.byFieldsOrdered + SqliteDb.TableName All [ Field.LE "NumValue" 10 ] [ Field.Named "Value" ] + Expect.hasLength docs 3 "There should have been three documents returned" + Expect.equal + (docs |> List.map _.Id |> String.concat "|") "three|one|two" "Documents not ordered correctly" + } + testTask "succeeds when sorting case-insensitively" { + use! db = SqliteDb.BuildDb() + do! loadDocs () + + let! docs = + Find.byFieldsOrdered + SqliteDb.TableName All [ Field.LE "NumValue" 10 ] [ Field.Named "i:Value" ] + Expect.hasLength docs 3 "There should have been three documents returned" + Expect.equal + (docs |> List.map _.Id |> String.concat "|") "three|two|one" "Documents not ordered correctly" + } ] testList "firstByFields" [ testTask "succeeds when a document is found" {