26 Commits

Author SHA1 Message Date
88f11a854f Add compat namespace functions
- Update package release notes
2024-08-17 14:08:46 -04:00
e2948ea6a1 Add ordered* tests for SQLite
- Ignore "n:" prefix for SQLite fields
- Remove byFIeld functions
2024-08-17 12:38:14 -04:00
d993f71788 Reorg SQLite tests 2024-08-16 22:54:27 -04:00
fba8ec7bc5 Finish ordered tests for PostgreSQL
- Rename tests, remove tested obsolete byField funcs
2024-08-16 22:04:23 -04:00
8e33299e22 WIP on ordered tests, test reorg 2024-08-15 22:32:25 -04:00
e07844570a Fix numeric field rendering in order by 2024-08-15 19:16:00 -04:00
bac3bd2ef0 Add *Ordered functions
- WIP on PostgreSQL numeric ordering
2024-08-14 22:13:46 -04:00
edd4d006da First cut of field order by function 2024-08-13 09:03:05 -04:00
96f5e1515d Remove byField functions 2024-08-12 09:07:25 -04:00
d382699a17 Merge branch 'wip-1' of https://git.bitbadger.solutions/bit-badger/BitBadger.Documents into wip-1 2024-08-11 17:06:07 -04:00
0c308c5f33 Add ToString for FieldMatch; add tests 2024-08-11 17:05:58 -04:00
77a6aaa583 Ignore IDE settings 2024-08-11 12:10:07 -04:00
d9d37f110d Add tests for PG parameter types
- Alter whereById to expect a parameter
2024-08-10 19:51:13 -04:00
e0fb793ec9 WIP on Postgres type derivation 2024-08-10 15:07:59 -04:00
74e5b77edb Complete common migration, WIP on tests 2024-08-10 09:56:59 -04:00
98bc83ac17 Pull count, exists, update to common 2024-08-09 22:55:12 -04:00
35755df99a WIP on pulling code up to Common 2024-08-09 20:15:08 -04:00
b1c3991e11 WIP on supporting numeric fields 2024-08-09 18:42:10 -04:00
85750e19f2 Add Postgres Query byFields tests 2024-08-08 22:36:48 -04:00
d8f64417e5 Add byFields to SQLite implementation
- Add fieldNameParams/FieldNames to Postgres
(syncs names between both implementations)
2024-08-08 19:45:25 -04:00
202fca272e Prefer seq over list for parameters
- This greatly reduces the duplicated functions needed
between F# and C#; F# lists satisfy the sequence reqs
2024-08-08 17:32:44 -04:00
8a15bdce2e Add byFields to PostgreSQL impl
- Swap howMatched and fields throughout
- Existing tests pass; still need new ones for byFields
2024-08-07 23:23:28 -04:00
d131eda56e Remove internal calls to deprecated funcs 2024-08-07 20:20:10 -04:00
e2232e91bb Add whereByFields code / tests
- Add wrapper class for unnamed field parameters
- Support table qualifiers by field
- Support dot access to document fields/sub-fields
2024-08-07 16:39:15 -04:00
e96c449324 WIP on PG Query.whereByFIelds 2024-08-07 08:30:28 -04:00
433302d995 WIP on field enhancements 2024-08-04 18:59:32 -04:00
16 changed files with 807 additions and 1589 deletions

3
.gitignore vendored
View File

@@ -397,6 +397,3 @@ FodyWeavers.xsd
# JetBrains Rider # JetBrains Rider
*.sln.iml *.sln.iml
**/.idea **/.idea
# Test run files
src/*-tests.txt

View File

@@ -1,7 +1,5 @@
namespace BitBadger.Documents namespace BitBadger.Documents
open System.Security.Cryptography
/// The types of logical operations available for JSON fields /// The types of logical operations available for JSON fields
[<Struct>] [<Struct>]
type Op = type Op =
@@ -150,70 +148,6 @@ type ParameterName() =
currentIdx <- currentIdx + 1 currentIdx <- currentIdx + 1
$"@field{currentIdx}" $"@field{currentIdx}"
#if NET6_0
open System.Text
#endif
/// Automatically-generated document ID strategies
[<Struct>]
type AutoId =
/// No automatic IDs will be generated
| Disabled
/// Generate a MAX-plus-1 numeric value for documents
| Number
/// Generate a GUID for each document (as a lowercase, no-dashes, 32-character string)
| Guid
/// Generate a random string of hexadecimal characters for each document
| RandomString
with
/// Generate a GUID string
static member GenerateGuid () =
System.Guid.NewGuid().ToString "N"
/// Generate a string of random hexadecimal characters
static member GenerateRandomString (length: int) =
#if NET8_0_OR_GREATER
RandomNumberGenerator.GetHexString(length, lowercase = true)
#else
RandomNumberGenerator.GetBytes((length / 2) + 1)
|> Array.fold (fun (str: StringBuilder) byt -> str.Append(byt.ToString "x2")) (StringBuilder length)
|> function it -> it.Length <- length; it.ToString()
#endif
/// Does the given document need an automatic ID generated?
static member NeedsAutoId<'T> strategy (document: 'T) idProp =
match strategy with
| Disabled -> false
| _ ->
let prop = document.GetType().GetProperty idProp
if isNull prop then invalidOp $"{idProp} not found in document"
else
match strategy with
| Number ->
if prop.PropertyType = typeof<int8> then
let value = prop.GetValue document :?> int8
value = int8 0
elif prop.PropertyType = typeof<int16> then
let value = prop.GetValue document :?> int16
value = int16 0
elif prop.PropertyType = typeof<int> then
let value = prop.GetValue document :?> int
value = 0
elif prop.PropertyType = typeof<int64> then
let value = prop.GetValue document :?> int64
value = int64 0
else invalidOp "Document ID was not a number; cannot auto-generate a Number ID"
| Guid | RandomString ->
if prop.PropertyType = typeof<string> then
let value =
prop.GetValue document
|> Option.ofObj
|> Option.map (fun it -> it :?> string)
|> Option.defaultValue ""
value = ""
else invalidOp "Document ID was not a string; cannot auto-generate GUID or random string"
| Disabled -> false
/// The required document serialization implementation /// The required document serialization implementation
type IDocumentSerializer = type IDocumentSerializer =
@@ -266,7 +200,7 @@ module Configuration =
serializerValue serializerValue
/// The serialized name of the ID field for documents /// The serialized name of the ID field for documents
let mutable private idFieldValue = "Id" let mutable idFieldValue = "Id"
/// Specify the name of the ID field for documents /// Specify the name of the ID field for documents
[<CompiledName "UseIdField">] [<CompiledName "UseIdField">]
@@ -277,32 +211,6 @@ module Configuration =
[<CompiledName "IdField">] [<CompiledName "IdField">]
let idField () = let idField () =
idFieldValue idFieldValue
/// The automatic ID strategy used by the library
let mutable private autoIdValue = Disabled
/// Specify the automatic ID generation strategy used by the library
[<CompiledName "UseAutoIdStrategy">]
let useAutoIdStrategy it =
autoIdValue <- it
/// Retrieve the currently configured automatic ID generation strategy
[<CompiledName "AutoIdStrategy">]
let autoIdStrategy () =
autoIdValue
/// The length of automatically generated random strings
let mutable private idStringLengthValue = 16
/// Specify the length of automatically generated random strings
[<CompiledName "UseIdStringLength">]
let useIdStringLength length =
idStringLengthValue <- length
/// Retrieve the currently configured length of automatically generated random strings
[<CompiledName "IdStringLength">]
let idStringLength () =
idStringLengthValue
/// Query construction functions /// Query construction functions
@@ -401,13 +309,10 @@ module Query =
{ it with Name = parts[0] }, Some $" {parts[1]}" { it with Name = parts[0] }, Some $" {parts[1]}"
else it, None) else it, None)
|> Seq.map (fun (field, direction) -> |> Seq.map (fun (field, direction) ->
if field.Name.StartsWith "n:" then match dialect, field.Name.StartsWith "n:" with
let f = { field with Name = field.Name[2..] } | PostgreSQL, true -> $"({ { field with Name = field.Name[2..] }.Path PostgreSQL})::numeric"
match dialect with PostgreSQL -> $"({f.Path PostgreSQL})::numeric" | SQLite -> f.Path SQLite | SQLite, true -> { field with Name = field.Name[2..] }.Path SQLite
elif field.Name.StartsWith "i:" then | _, _ -> field.Path dialect
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 "") |> function path -> path + defaultArg direction "")
|> String.concat ", " |> String.concat ", "
|> function it -> $" ORDER BY {it}" |> function it -> $" ORDER BY {it}"

View File

@@ -6,8 +6,7 @@
<AssemblyVersion>4.0.0.0</AssemblyVersion> <AssemblyVersion>4.0.0.0</AssemblyVersion>
<FileVersion>4.0.0.0</FileVersion> <FileVersion>4.0.0.0</FileVersion>
<VersionPrefix>4.0.0</VersionPrefix> <VersionPrefix>4.0.0</VersionPrefix>
<VersionSuffix>rc2</VersionSuffix> <PackageReleaseNotes>Change ByField to ByFields; support dot-access to nested document fields; add Find*Ordered functions/methods; see project site for breaking changes and compatibility</PackageReleaseNotes>
<PackageReleaseNotes>From rc1: add case-insensitive ordering. From v3.1: Change ByField to ByFields; support dot-access to nested document fields; add Find*Ordered functions/methods; see project site for breaking changes and compatibility</PackageReleaseNotes>
<Authors>danieljsummers</Authors> <Authors>danieljsummers</Authors>
<Company>Bit Badger Solutions</Company> <Company>Bit Badger Solutions</Company>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>

View File

@@ -312,23 +312,7 @@ module WithProps =
/// Insert a new document /// Insert a new document
[<CompiledName "Insert">] [<CompiledName "Insert">]
let insert<'TDoc> tableName (document: 'TDoc) sqlProps = let insert<'TDoc> tableName (document: 'TDoc) sqlProps =
let query = Custom.nonQuery (Query.insert tableName) [ jsonParam "@data" document ] sqlProps
match Configuration.autoIdStrategy () with
| Disabled -> Query.insert tableName
| strategy ->
let idField = Configuration.idField ()
let dataParam =
if AutoId.NeedsAutoId strategy document idField then
match strategy with
| Number ->
$"' || (SELECT COALESCE(MAX((data->>'{idField}')::numeric), 0) + 1 FROM {tableName}) || '"
| Guid -> $"\"{AutoId.GenerateGuid()}\""
| RandomString -> $"\"{AutoId.GenerateRandomString(Configuration.idStringLength ())}\""
| Disabled -> "@data"
|> function it -> $"""@data::jsonb || ('{{"{idField}":{it}}}')::jsonb"""
else "@data"
(Query.insert tableName).Replace("@data", dataParam)
Custom.nonQuery query [ jsonParam "@data" document ] sqlProps
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
[<CompiledName "Save">] [<CompiledName "Save">]

View File

@@ -35,11 +35,11 @@ module Extensions =
/// Insert a new document /// Insert a new document
member conn.insert<'TDoc> tableName (document: 'TDoc) = member conn.insert<'TDoc> tableName (document: 'TDoc) =
WithConn.Document.insert<'TDoc> tableName document conn WithConn.insert<'TDoc> tableName document conn
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
member conn.save<'TDoc> tableName (document: 'TDoc) = member conn.save<'TDoc> tableName (document: 'TDoc) =
WithConn.Document.save tableName document conn WithConn.save tableName document conn
/// Count all documents in a table /// Count all documents in a table
member conn.countAll tableName = member conn.countAll tableName =
@@ -159,12 +159,12 @@ type SqliteConnectionCSharpExtensions =
/// Insert a new document /// Insert a new document
[<Extension>] [<Extension>]
static member inline Insert<'TDoc>(conn, tableName, document: 'TDoc) = static member inline Insert<'TDoc>(conn, tableName, document: 'TDoc) =
WithConn.Document.insert<'TDoc> tableName document conn WithConn.insert<'TDoc> tableName document conn
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
[<Extension>] [<Extension>]
static member inline Save<'TDoc>(conn, tableName, document: 'TDoc) = static member inline Save<'TDoc>(conn, tableName, document: 'TDoc) =
WithConn.Document.save<'TDoc> tableName document conn WithConn.save<'TDoc> tableName document conn
/// Count all documents in a table /// Count all documents in a table
[<Extension>] [<Extension>]

View File

@@ -258,34 +258,15 @@ module WithConn =
let ensureFieldIndex tableName indexName fields conn = let ensureFieldIndex tableName indexName fields conn =
Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields SQLite) [] conn Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields SQLite) [] conn
/// Commands to add documents /// Insert a new document
[<AutoOpen>] [<CompiledName "Insert">]
module Document = let insert<'TDoc> tableName (document: 'TDoc) conn =
Custom.nonQuery (Query.insert tableName) [ jsonParam "@data" document ] conn
/// Insert a new document
[<CompiledName "Insert">] /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
let insert<'TDoc> tableName (document: 'TDoc) conn = [<CompiledName "Save">]
let query = let save<'TDoc> tableName (document: 'TDoc) conn =
match Configuration.autoIdStrategy () with Custom.nonQuery (Query.save tableName) [ jsonParam "@data" document ] conn
| Disabled -> Query.insert tableName
| strategy ->
let idField = Configuration.idField ()
let dataParam =
if AutoId.NeedsAutoId strategy document idField then
match strategy with
| Number -> $"(SELECT coalesce(max(data->>'{idField}'), 0) + 1 FROM {tableName})"
| Guid -> $"'{AutoId.GenerateGuid()}'"
| RandomString -> $"'{AutoId.GenerateRandomString(Configuration.idStringLength ())}'"
| Disabled -> "@data"
|> function it -> $"json_set(@data, '$.{idField}', {it})"
else "@data"
(Query.insert tableName).Replace("@data", dataParam)
Custom.nonQuery query [ jsonParam "@data" document ] conn
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
[<CompiledName "Save">]
let save<'TDoc> tableName (document: 'TDoc) conn =
Custom.nonQuery (Query.save tableName) [ jsonParam "@data" document ] conn
/// Commands to count documents /// Commands to count documents
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
@@ -566,13 +547,13 @@ module Document =
[<CompiledName "Insert">] [<CompiledName "Insert">]
let insert<'TDoc> tableName (document: 'TDoc) = let insert<'TDoc> tableName (document: 'TDoc) =
use conn = Configuration.dbConn () use conn = Configuration.dbConn ()
WithConn.Document.insert tableName document conn WithConn.insert tableName document conn
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
[<CompiledName "Save">] [<CompiledName "Save">]
let save<'TDoc> tableName (document: 'TDoc) = let save<'TDoc> tableName (document: 'TDoc) =
use conn = Configuration.dbConn () use conn = Configuration.dbConn ()
WithConn.Document.save tableName document conn WithConn.save tableName document conn
/// Commands to count documents /// Commands to count documents

File diff suppressed because it is too large Load Diff

View File

@@ -170,11 +170,7 @@ public static class PostgresCSharpTests
Expect.isTrue(false, "The parameter was not a StringArray type"); Expect.isTrue(false, "The parameter was not a StringArray type");
} }
}) })
]), ])
TestCase("None succeeds", () =>
{
Expect.isEmpty(Parameters.None, "The no-params sequence should be empty");
})
]); ]);
/// <summary> /// <summary>
@@ -314,6 +310,15 @@ public static class PostgresCSharpTests
}) })
]); ]);
/// <summary>
/// Tests which do not hit the database
/// </summary>
private static readonly Test Unit = TestList("Unit",
[
ParametersTests,
QueryTests
]);
private static readonly List<JsonDocument> TestDocuments = private static readonly List<JsonDocument> TestDocuments =
[ [
new() { Id = "one", Value = "FIRST!", NumValue = 0 }, new() { Id = "one", Value = "FIRST!", NumValue = 0 },
@@ -543,83 +548,6 @@ public static class PostgresCSharpTests
{ {
// This is what should have happened // This is what should have happened
} }
}),
TestCase("succeeds when adding a numeric auto ID", async () =>
{
try
{
Configuration.UseAutoIdStrategy(AutoId.Number);
Configuration.UseIdField("Key");
await using var db = PostgresDb.BuildDb();
var before = await Count.All(PostgresDb.TableName);
Expect.equal(before, 0, "There should be no documents in the table");
await Document.Insert(PostgresDb.TableName, new NumIdDocument { Text = "one" });
await Document.Insert(PostgresDb.TableName, new NumIdDocument { Text = "two" });
await Document.Insert(PostgresDb.TableName, new NumIdDocument { Key = 77, Text = "three" });
await Document.Insert(PostgresDb.TableName, new NumIdDocument { Text = "four" });
var after = await Find.AllOrdered<NumIdDocument>(PostgresDb.TableName, [Field.Named("n:Key")]);
Expect.hasLength(after, 4, "There should have been 4 documents returned");
Expect.sequenceEqual(after.Select(x => x.Key), [1, 2, 77, 78],
"The IDs were not generated correctly");
}
finally
{
Configuration.UseAutoIdStrategy(AutoId.Disabled);
Configuration.UseIdField("Id");
}
}),
TestCase("succeeds when adding a GUID auto ID", async () =>
{
try
{
Configuration.UseAutoIdStrategy(AutoId.Guid);
await using var db = PostgresDb.BuildDb();
var before = await Count.All(PostgresDb.TableName);
Expect.equal(before, 0, "There should be no documents in the table");
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "one" });
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "two" });
await Document.Insert(PostgresDb.TableName, new JsonDocument { Id = "abc123", Value = "three" });
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "four" });
var after = await Find.All<JsonDocument>(PostgresDb.TableName);
Expect.hasLength(after, 4, "There should have been 4 documents returned");
Expect.equal(after.Count(x => x.Id.Length == 32), 3, "Three of the IDs should have been GUIDs");
Expect.equal(after.Count(x => x.Id == "abc123"), 1, "The provided ID should have been used as-is");
}
finally
{
Configuration.UseAutoIdStrategy(AutoId.Disabled);
}
}),
TestCase("succeeds when adding a RandomString auto ID", async () =>
{
try
{
Configuration.UseAutoIdStrategy(AutoId.RandomString);
Configuration.UseIdStringLength(44);
await using var db = PostgresDb.BuildDb();
var before = await Count.All(PostgresDb.TableName);
Expect.equal(before, 0, "There should be no documents in the table");
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "one" });
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "two" });
await Document.Insert(PostgresDb.TableName, new JsonDocument { Id = "abc123", Value = "three" });
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "four" });
var after = await Find.All<JsonDocument>(PostgresDb.TableName);
Expect.hasLength(after, 4, "There should have been 4 documents returned");
Expect.equal(after.Count(x => x.Id.Length == 44), 3,
"Three of the IDs should have been 44-character random strings");
Expect.equal(after.Count(x => x.Id == "abc123"), 1, "The provided ID should have been used as-is");
}
finally
{
Configuration.UseAutoIdStrategy(AutoId.Disabled);
Configuration.UseIdStringLength(16);
}
}) })
]), ]),
TestList("Save", TestList("Save",

View File

@@ -325,86 +325,9 @@ public static class SqliteCSharpTests
{ {
// This is what is supposed to happen // This is what is supposed to happen
} }
}),
TestCase("succeeds when adding a numeric auto ID", async () =>
{
try
{
Configuration.UseAutoIdStrategy(AutoId.Number);
Configuration.UseIdField("Key");
await using var db = await SqliteDb.BuildDb();
var before = await Count.All(SqliteDb.TableName);
Expect.equal(before, 0L, "There should be no documents in the table");
await Document.Insert(SqliteDb.TableName, new NumIdDocument { Text = "one" });
await Document.Insert(SqliteDb.TableName, new NumIdDocument { Text = "two" });
await Document.Insert(SqliteDb.TableName, new NumIdDocument { Key = 77, Text = "three" });
await Document.Insert(SqliteDb.TableName, new NumIdDocument { Text = "four" });
var after = await Find.AllOrdered<NumIdDocument>(SqliteDb.TableName, [Field.Named("Key")]);
Expect.hasLength(after, 4, "There should have been 4 documents returned");
Expect.sequenceEqual(after.Select(x => x.Key), [1, 2, 77, 78],
"The IDs were not generated correctly");
}
finally
{
Configuration.UseAutoIdStrategy(AutoId.Disabled);
Configuration.UseIdField("Id");
}
}),
TestCase("succeeds when adding a GUID auto ID", async () =>
{
try
{
Configuration.UseAutoIdStrategy(AutoId.Guid);
await using var db = await SqliteDb.BuildDb();
var before = await Count.All(SqliteDb.TableName);
Expect.equal(before, 0L, "There should be no documents in the table");
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "one" });
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "two" });
await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "abc123", Value = "three" });
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "four" });
var after = await Find.All<JsonDocument>(SqliteDb.TableName);
Expect.hasLength(after, 4, "There should have been 4 documents returned");
Expect.equal(after.Count(x => x.Id.Length == 32), 3, "Three of the IDs should have been GUIDs");
Expect.equal(after.Count(x => x.Id == "abc123"), 1, "The provided ID should have been used as-is");
}
finally
{
Configuration.UseAutoIdStrategy(AutoId.Disabled);
}
}),
TestCase("succeeds when adding a RandomString auto ID", async () =>
{
try
{
Configuration.UseAutoIdStrategy(AutoId.RandomString);
Configuration.UseIdStringLength(44);
await using var db = await SqliteDb.BuildDb();
var before = await Count.All(SqliteDb.TableName);
Expect.equal(before, 0L, "There should be no documents in the table");
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "one" });
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "two" });
await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "abc123", Value = "three" });
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "four" });
var after = await Find.All<JsonDocument>(SqliteDb.TableName);
Expect.hasLength(after, 4, "There should have been 4 documents returned");
Expect.equal(after.Count(x => x.Id.Length == 44), 3,
"Three of the IDs should have been 44-character random strings");
Expect.equal(after.Count(x => x.Id == "abc123"), 1, "The provided ID should have been used as-is");
}
finally
{
Configuration.UseAutoIdStrategy(AutoId.Disabled);
Configuration.UseIdStringLength(16);
}
}) })
]), ]),
TestList("Save", TestList("Document.Save",
[ [
TestCase("succeeds when a document is inserted", async () => TestCase("succeeds when a document is inserted", async () =>
{ {
@@ -627,9 +550,8 @@ public static class SqliteCSharpTests
var docs = await Find.ByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any, var docs = await Find.ByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
[Field.GT("NumValue", 15)], [Field.Named("Id")]); [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", Expect.equal(string.Join('|', docs.Select(x => x.Id)), "five|four",
"The documents were not sorted correctly"); "There should have been two documents returned");
}), }),
TestCase("succeeds when documents are not found", async () => TestCase("succeeds when documents are not found", async () =>
{ {
@@ -638,31 +560,8 @@ public static class SqliteCSharpTests
var docs = await Find.ByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any, var docs = await Find.ByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
[Field.GT("NumValue", 15)], [Field.Named("Id DESC")]); [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", Expect.equal(string.Join('|', docs.Select(x => x.Id)), "four|five",
"The documents were not sorted correctly"); "There should have been two documents returned");
}),
TestCase("succeeds when sorting case-sensitively", async () =>
{
await using var db = await SqliteDb.BuildDb();
await LoadDocs();
var docs = await Find.ByFieldsOrdered<JsonDocument>(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<JsonDocument>(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", TestList("FirstByFields",

View File

@@ -1,11 +1,5 @@
namespace BitBadger.Documents.Tests.CSharp; namespace BitBadger.Documents.Tests.CSharp;
public class NumIdDocument
{
public int Key { get; set; } = 0;
public string Text { get; set; } = "";
}
public class SubDocument public class SubDocument
{ {
public string Foo { get; set; } = ""; public string Foo { get; set; } = "";

View File

@@ -6,8 +6,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Types.fs" />
<Compile Include="CommonTests.fs" /> <Compile Include="CommonTests.fs" />
<Compile Include="Types.fs" />
<Compile Include="PostgresTests.fs" /> <Compile Include="PostgresTests.fs" />
<Compile Include="PostgresExtensionTests.fs" /> <Compile Include="PostgresExtensionTests.fs" />
<Compile Include="SqliteTests.fs" /> <Compile Include="SqliteTests.fs" />

View File

@@ -6,472 +6,320 @@ open Expecto
/// Test table name /// Test table name
let tbl = "test_table" let tbl = "test_table"
/// Unit tests for the Op DU
let opTests = testList "Op" [
test "EQ succeeds" {
Expect.equal (string EQ) "=" "The equals operator was not correct"
}
test "GT succeeds" {
Expect.equal (string GT) ">" "The greater than operator was not correct"
}
test "GE succeeds" {
Expect.equal (string GE) ">=" "The greater than or equal to operator was not correct"
}
test "LT succeeds" {
Expect.equal (string LT) "<" "The less than operator was not correct"
}
test "LE succeeds" {
Expect.equal (string LE) "<=" "The less than or equal to operator was not correct"
}
test "NE succeeds" {
Expect.equal (string NE) "<>" "The not equal to operator was not correct"
}
test "BT succeeds" {
Expect.equal (string BT) "BETWEEN" """The "between" operator was not correct"""
}
test "EX succeeds" {
Expect.equal (string EX) "IS NOT NULL" """The "exists" operator was not correct"""
}
test "NEX succeeds" {
Expect.equal (string NEX) "IS NULL" """The "not exists" operator was not correct"""
}
]
/// Unit tests for the Field class
let fieldTests = testList "Field" [
test "EQ succeeds" {
let field = Field.EQ "Test" 14
Expect.equal field.Name "Test" "Field name incorrect"
Expect.equal field.Op EQ "Operator incorrect"
Expect.equal field.Value 14 "Value incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
test "GT succeeds" {
let field = Field.GT "Great" "night"
Expect.equal field.Name "Great" "Field name incorrect"
Expect.equal field.Op GT "Operator incorrect"
Expect.equal field.Value "night" "Value incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
test "GE succeeds" {
let field = Field.GE "Nice" 88L
Expect.equal field.Name "Nice" "Field name incorrect"
Expect.equal field.Op GE "Operator incorrect"
Expect.equal field.Value 88L "Value incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
test "LT succeeds" {
let field = Field.LT "Lesser" "seven"
Expect.equal field.Name "Lesser" "Field name incorrect"
Expect.equal field.Op LT "Operator incorrect"
Expect.equal field.Value "seven" "Value incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
test "LE succeeds" {
let field = Field.LE "Nobody" "KNOWS";
Expect.equal field.Name "Nobody" "Field name incorrect"
Expect.equal field.Op LE "Operator incorrect"
Expect.equal field.Value "KNOWS" "Value incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
test "NE succeeds" {
let field = Field.NE "Park" "here"
Expect.equal field.Name "Park" "Field name incorrect"
Expect.equal field.Op NE "Operator incorrect"
Expect.equal field.Value "here" "Value incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
test "BT succeeds" {
let field = Field.BT "Age" 18 49
Expect.equal field.Name "Age" "Field name incorrect"
Expect.equal field.Op BT "Operator incorrect"
Expect.sequenceEqual (field.Value :?> obj list) [ 18; 49 ] "Value incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
test "EX succeeds" {
let field = Field.EX "Groovy"
Expect.equal field.Name "Groovy" "Field name incorrect"
Expect.equal field.Op EX "Operator incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
test "NEX succeeds" {
let field = Field.NEX "Rad"
Expect.equal field.Name "Rad" "Field name incorrect"
Expect.equal field.Op NEX "Operator incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
testList "NameToPath" [
test "succeeds for PostgreSQL and a simple name" {
Expect.equal "data->>'Simple'" (Field.NameToPath "Simple" PostgreSQL) "Path not constructed correctly"
}
test "succeeds for SQLite and a simple name" {
Expect.equal "data->>'Simple'" (Field.NameToPath "Simple" SQLite) "Path not constructed correctly"
}
test "succeeds for PostgreSQL and a nested name" {
Expect.equal
"data#>>'{A,Long,Path,to,the,Property}'"
(Field.NameToPath "A.Long.Path.to.the.Property" PostgreSQL)
"Path not constructed correctly"
}
test "succeeds for SQLite and a nested name" {
Expect.equal
"data->>'A'->>'Long'->>'Path'->>'to'->>'the'->>'Property'"
(Field.NameToPath "A.Long.Path.to.the.Property" SQLite)
"Path not constructed correctly"
}
]
test "WithParameterName succeeds" {
let field = (Field.EQ "Bob" "Tom").WithParameterName "@name"
Expect.isSome field.ParameterName "The parameter name should have been filled"
Expect.equal "@name" field.ParameterName.Value "The parameter name is incorrect"
}
test "WithQualifier succeeds" {
let field = (Field.EQ "Bill" "Matt").WithQualifier "joe"
Expect.isSome field.Qualifier "The table qualifier should have been filled"
Expect.equal "joe" field.Qualifier.Value "The table qualifier is incorrect"
}
testList "Path" [
test "succeeds for a PostgreSQL single field with no qualifier" {
let field = Field.GE "SomethingCool" 18
Expect.equal "data->>'SomethingCool'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
}
test "succeeds for a PostgreSQL single field with a qualifier" {
let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" }
Expect.equal "this.data->>'SomethingElse'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
}
test "succeeds for a PostgreSQL nested field with no qualifier" {
let field = Field.EQ "My.Nested.Field" "howdy"
Expect.equal "data#>>'{My,Nested,Field}'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
}
test "succeeds for a PostgreSQL nested field with a qualifier" {
let field = { Field.EQ "Nest.Away" "doc" with Qualifier = Some "bird" }
Expect.equal "bird.data#>>'{Nest,Away}'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
}
test "succeeds for a SQLite single field with no qualifier" {
let field = Field.GE "SomethingCool" 18
Expect.equal "data->>'SomethingCool'" (field.Path SQLite) "The SQLite path is incorrect"
}
test "succeeds for a SQLite single field with a qualifier" {
let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" }
Expect.equal "this.data->>'SomethingElse'" (field.Path SQLite) "The SQLite path is incorrect"
}
test "succeeds for a SQLite nested field with no qualifier" {
let field = Field.EQ "My.Nested.Field" "howdy"
Expect.equal "data->>'My'->>'Nested'->>'Field'" (field.Path SQLite) "The SQLite path is incorrect"
}
test "succeeds for a SQLite nested field with a qualifier" {
let field = { Field.EQ "Nest.Away" "doc" with Qualifier = Some "bird" }
Expect.equal "bird.data->>'Nest'->>'Away'" (field.Path SQLite) "The SQLite path is incorrect"
}
]
]
/// Unit tests for the FieldMatch DU
let fieldMatchTests = testList "FieldMatch.ToString" [
test "succeeds for Any" {
Expect.equal (string Any) "OR" "SQL for Any is incorrect"
}
test "succeeds for All" {
Expect.equal (string All) "AND" "SQL for All is incorrect"
}
]
/// Unit tests for the ParameterName class
let parameterNameTests = testList "ParameterName.Derive" [
test "succeeds with existing name" {
let name = ParameterName()
Expect.equal (name.Derive(Some "@taco")) "@taco" "Name should have been @taco"
Expect.equal (name.Derive None) "@field0" "Counter should not have advanced for named field"
}
test "succeeds with non-existent name" {
let name = ParameterName()
Expect.equal (name.Derive None) "@field0" "Anonymous field name should have been returned"
Expect.equal (name.Derive None) "@field1" "Counter should have advanced from previous call"
Expect.equal (name.Derive None) "@field2" "Counter should have advanced from previous call"
Expect.equal (name.Derive None) "@field3" "Counter should have advanced from previous call"
}
]
/// Unit tests for the AutoId DU
let autoIdTests = testList "AutoId" [
test "GenerateGuid succeeds" {
let autoId = AutoId.GenerateGuid()
Expect.isNotNull autoId "The GUID auto-ID should not have been null"
Expect.stringHasLength autoId 32 "The GUID auto-ID should have been 32 characters long"
Expect.equal autoId (autoId.ToLowerInvariant ()) "The GUID auto-ID should have been lowercase"
}
test "GenerateRandomString succeeds" {
[ 6; 8; 12; 20; 32; 57; 64 ]
|> List.iter (fun length ->
let autoId = AutoId.GenerateRandomString length
Expect.isNotNull autoId $"Random string ({length}) should not have been null"
Expect.stringHasLength autoId length $"Random string should have been {length} characters long"
Expect.equal autoId (autoId.ToLowerInvariant ()) $"Random string ({length}) should have been lowercase")
}
testList "NeedsAutoId" [
test "succeeds when no auto ID is configured" {
Expect.isFalse (AutoId.NeedsAutoId Disabled (obj ()) "id") "Disabled auto-ID never needs an automatic ID"
}
test "fails for any when the ID property is not found" {
Expect.throwsT<System.InvalidOperationException>
(fun () -> AutoId.NeedsAutoId Number {| Key = "" |} "Id" |> ignore)
"Non-existent ID property should have thrown an exception"
}
test "succeeds for byte when the ID is zero" {
Expect.isTrue (AutoId.NeedsAutoId Number {| Id = int8 0 |} "Id") "Zero ID should have returned true"
}
test "succeeds for byte when the ID is non-zero" {
Expect.isFalse (AutoId.NeedsAutoId Number {| Id = int8 4 |} "Id") "Non-zero ID should have returned false"
}
test "succeeds for short when the ID is zero" {
Expect.isTrue (AutoId.NeedsAutoId Number {| Id = int16 0 |} "Id") "Zero ID should have returned true"
}
test "succeeds for short when the ID is non-zero" {
Expect.isFalse (AutoId.NeedsAutoId Number {| Id = int16 7 |} "Id") "Non-zero ID should have returned false"
}
test "succeeds for int when the ID is zero" {
Expect.isTrue (AutoId.NeedsAutoId Number {| Id = 0 |} "Id") "Zero ID should have returned true"
}
test "succeeds for int when the ID is non-zero" {
Expect.isFalse (AutoId.NeedsAutoId Number {| Id = 32 |} "Id") "Non-zero ID should have returned false"
}
test "succeeds for long when the ID is zero" {
Expect.isTrue (AutoId.NeedsAutoId Number {| Id = 0L |} "Id") "Zero ID should have returned true"
}
test "succeeds for long when the ID is non-zero" {
Expect.isFalse (AutoId.NeedsAutoId Number {| Id = 80L |} "Id") "Non-zero ID should have returned false"
}
test "fails for number when the ID is not a number" {
Expect.throwsT<System.InvalidOperationException>
(fun () -> AutoId.NeedsAutoId Number {| Id = "" |} "Id" |> ignore)
"Numeric ID against a string should have thrown an exception"
}
test "succeeds for GUID when the ID is blank" {
Expect.isTrue (AutoId.NeedsAutoId Guid {| Id = "" |} "Id") "Blank ID should have returned true"
}
test "succeeds for GUID when the ID is filled" {
Expect.isFalse (AutoId.NeedsAutoId Guid {| Id = "abc" |} "Id") "Filled ID should have returned false"
}
test "fails for GUID when the ID is not a string" {
Expect.throwsT<System.InvalidOperationException>
(fun () -> AutoId.NeedsAutoId Guid {| Id = 8 |} "Id" |> ignore)
"String ID against a number should have thrown an exception"
}
test "succeeds for RandomString when the ID is blank" {
Expect.isTrue (AutoId.NeedsAutoId RandomString {| Id = "" |} "Id") "Blank ID should have returned true"
}
test "succeeds for RandomString when the ID is filled" {
Expect.isFalse (AutoId.NeedsAutoId RandomString {| Id = "x" |} "Id") "Filled ID should have returned false"
}
test "fails for RandomString when the ID is not a string" {
Expect.throwsT<System.InvalidOperationException>
(fun () -> AutoId.NeedsAutoId RandomString {| Id = 33 |} "Id" |> ignore)
"String ID against a number should have thrown an exception"
}
]
]
/// Unit tests for the Configuration module
let configurationTests = testList "Configuration" [
test "useSerializer succeeds" {
try
Configuration.useSerializer
{ new IDocumentSerializer with
member _.Serialize<'T>(it: 'T) : string = """{"Overridden":true}"""
member _.Deserialize<'T>(it: string) : 'T = Unchecked.defaultof<'T>
}
let serialized = Configuration.serializer().Serialize {| Foo = "howdy"; Bar = "bye" |}
Expect.equal serialized """{"Overridden":true}""" "Specified serializer was not used"
let deserialized = Configuration.serializer().Deserialize<obj> """{"Something":"here"}"""
Expect.isNull deserialized "Specified serializer should have returned null"
finally
Configuration.useSerializer DocumentSerializer.``default``
}
test "serializer returns configured serializer" {
Expect.isTrue (obj.ReferenceEquals(DocumentSerializer.``default``, Configuration.serializer ()))
"Serializer should have been the same"
}
test "useIdField / idField succeeds" {
try
Expect.equal (Configuration.idField ()) "Id" "The default configured ID field was incorrect"
Configuration.useIdField "id"
Expect.equal (Configuration.idField ()) "id" "useIdField did not set the ID field"
finally
Configuration.useIdField "Id"
}
test "useAutoIdStrategy / autoIdStrategy succeeds" {
try
Expect.equal (Configuration.autoIdStrategy ()) Disabled "The default auto-ID strategy was incorrect"
Configuration.useAutoIdStrategy Guid
Expect.equal (Configuration.autoIdStrategy ()) Guid "The auto-ID strategy was not set correctly"
finally
Configuration.useAutoIdStrategy Disabled
}
test "useIdStringLength / idStringLength succeeds" {
try
Expect.equal (Configuration.idStringLength ()) 16 "The default ID string length was incorrect"
Configuration.useIdStringLength 33
Expect.equal (Configuration.idStringLength ()) 33 "The ID string length was not set correctly"
finally
Configuration.useIdStringLength 16
}
]
/// Unit tests for the Query module
let queryTests = testList "Query" [
test "statementWhere succeeds" {
Expect.equal (Query.statementWhere "x" "y") "x WHERE y" "Statements not combined correctly"
}
testList "Definition" [
test "ensureTableFor succeeds" {
Expect.equal
(Query.Definition.ensureTableFor "my.table" "JSONB")
"CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)"
"CREATE TABLE statement not constructed correctly"
}
testList "ensureKey" [
test "succeeds when a schema is present" {
Expect.equal
(Query.Definition.ensureKey "test.table" PostgreSQL)
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'Id'))"
"CREATE INDEX for key statement with schema not constructed correctly"
}
test "succeeds when a schema is not present" {
Expect.equal
(Query.Definition.ensureKey "table" SQLite)
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data->>'Id'))"
"CREATE INDEX for key statement without schema not constructed correctly"
}
]
testList "ensureIndexOn" [
test "succeeds for multiple fields and directions" {
Expect.equal
(Query.Definition.ensureIndexOn
"test.table" "gibberish" [ "taco"; "guac DESC"; "salsa ASC" ] PostgreSQL)
([ "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table "
"((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)" ]
|> String.concat "")
"CREATE INDEX for multiple field statement incorrect"
}
test "succeeds for nested PostgreSQL field" {
Expect.equal
(Query.Definition.ensureIndexOn tbl "nest" [ "a.b.c" ] PostgreSQL)
$"CREATE INDEX IF NOT EXISTS idx_{tbl}_nest ON {tbl} ((data#>>'{{a,b,c}}'))"
"CREATE INDEX for nested PostgreSQL field incorrect"
}
test "succeeds for nested SQLite field" {
Expect.equal
(Query.Definition.ensureIndexOn tbl "nest" [ "a.b.c" ] SQLite)
$"CREATE INDEX IF NOT EXISTS idx_{tbl}_nest ON {tbl} ((data->>'a'->>'b'->>'c'))"
"CREATE INDEX for nested SQLite field incorrect"
}
]
]
test "insert succeeds" {
Expect.equal (Query.insert tbl) $"INSERT INTO {tbl} VALUES (@data)" "INSERT statement not correct"
}
test "save succeeds" {
Expect.equal
(Query.save tbl)
$"INSERT INTO {tbl} VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data"
"INSERT ON CONFLICT UPDATE statement not correct"
}
test "count succeeds" {
Expect.equal (Query.count tbl) $"SELECT COUNT(*) AS it FROM {tbl}" "Count query not correct"
}
test "exists succeeds" {
Expect.equal
(Query.exists tbl "turkey")
$"SELECT EXISTS (SELECT 1 FROM {tbl} WHERE turkey) AS it"
"Exists query not correct"
}
test "find succeeds" {
Expect.equal (Query.find tbl) $"SELECT data FROM {tbl}" "Find query not correct"
}
test "update succeeds" {
Expect.equal (Query.update tbl) $"UPDATE {tbl} SET data = @data" "Update query not correct"
}
test "delete succeeds" {
Expect.equal (Query.delete tbl) $"DELETE FROM {tbl}" "Delete query not correct"
}
testList "orderBy" [
test "succeeds for no fields" {
Expect.equal (Query.orderBy [] PostgreSQL) "" "Order By should have been blank (PostgreSQL)"
Expect.equal (Query.orderBy [] SQLite) "" "Order By should have been blank (SQLite)"
}
test "succeeds for PostgreSQL with one field and no direction" {
Expect.equal
(Query.orderBy [ Field.Named "TestField" ] PostgreSQL)
" ORDER BY data->>'TestField'"
"Order By not constructed correctly"
}
test "succeeds for SQLite with one field and no direction" {
Expect.equal
(Query.orderBy [ Field.Named "TestField" ] SQLite)
" ORDER BY data->>'TestField'"
"Order By not constructed correctly"
}
test "succeeds for PostgreSQL with multiple fields and direction" {
Expect.equal
(Query.orderBy
[ Field.Named "Nested.Test.Field DESC"; Field.Named "AnotherField"; Field.Named "It DESC" ]
PostgreSQL)
" ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC"
"Order By not constructed correctly"
}
test "succeeds for SQLite with multiple fields and direction" {
Expect.equal
(Query.orderBy
[ Field.Named "Nested.Test.Field DESC"; Field.Named "AnotherField"; Field.Named "It DESC" ]
SQLite)
" ORDER BY data->>'Nested'->>'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC"
"Order By not constructed correctly"
}
test "succeeds for PostgreSQL numeric fields" {
Expect.equal
(Query.orderBy [ Field.Named "n:Test" ] PostgreSQL)
" ORDER BY (data->>'Test')::numeric"
"Order By not constructed correctly for numeric field"
}
test "succeeds for SQLite numeric fields" {
Expect.equal
(Query.orderBy [ Field.Named "n:Test" ] SQLite)
" 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"
}
]
]
/// Tests which do not hit the database /// Tests which do not hit the database
let all = testList "Common" [ let all =
opTests testList "Common" [
fieldTests testList "Op" [
fieldMatchTests test "EQ succeeds" {
parameterNameTests Expect.equal (string EQ) "=" "The equals operator was not correct"
autoIdTests }
queryTests test "GT succeeds" {
testSequenced configurationTests Expect.equal (string GT) ">" "The greater than operator was not correct"
] }
test "GE succeeds" {
Expect.equal (string GE) ">=" "The greater than or equal to operator was not correct"
}
test "LT succeeds" {
Expect.equal (string LT) "<" "The less than operator was not correct"
}
test "LE succeeds" {
Expect.equal (string LE) "<=" "The less than or equal to operator was not correct"
}
test "NE succeeds" {
Expect.equal (string NE) "<>" "The not equal to operator was not correct"
}
test "BT succeeds" {
Expect.equal (string BT) "BETWEEN" """The "between" operator was not correct"""
}
test "EX succeeds" {
Expect.equal (string EX) "IS NOT NULL" """The "exists" operator was not correct"""
}
test "NEX succeeds" {
Expect.equal (string NEX) "IS NULL" """The "not exists" operator was not correct"""
}
]
testList "Field" [
test "EQ succeeds" {
let field = Field.EQ "Test" 14
Expect.equal field.Name "Test" "Field name incorrect"
Expect.equal field.Op EQ "Operator incorrect"
Expect.equal field.Value 14 "Value incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
test "GT succeeds" {
let field = Field.GT "Great" "night"
Expect.equal field.Name "Great" "Field name incorrect"
Expect.equal field.Op GT "Operator incorrect"
Expect.equal field.Value "night" "Value incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
test "GE succeeds" {
let field = Field.GE "Nice" 88L
Expect.equal field.Name "Nice" "Field name incorrect"
Expect.equal field.Op GE "Operator incorrect"
Expect.equal field.Value 88L "Value incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
test "LT succeeds" {
let field = Field.LT "Lesser" "seven"
Expect.equal field.Name "Lesser" "Field name incorrect"
Expect.equal field.Op LT "Operator incorrect"
Expect.equal field.Value "seven" "Value incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
test "LE succeeds" {
let field = Field.LE "Nobody" "KNOWS";
Expect.equal field.Name "Nobody" "Field name incorrect"
Expect.equal field.Op LE "Operator incorrect"
Expect.equal field.Value "KNOWS" "Value incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
test "NE succeeds" {
let field = Field.NE "Park" "here"
Expect.equal field.Name "Park" "Field name incorrect"
Expect.equal field.Op NE "Operator incorrect"
Expect.equal field.Value "here" "Value incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
test "BT succeeds" {
let field = Field.BT "Age" 18 49
Expect.equal field.Name "Age" "Field name incorrect"
Expect.equal field.Op BT "Operator incorrect"
Expect.sequenceEqual (field.Value :?> obj list) [ 18; 49 ] "Value incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
test "EX succeeds" {
let field = Field.EX "Groovy"
Expect.equal field.Name "Groovy" "Field name incorrect"
Expect.equal field.Op EX "Operator incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
test "NEX succeeds" {
let field = Field.NEX "Rad"
Expect.equal field.Name "Rad" "Field name incorrect"
Expect.equal field.Op NEX "Operator incorrect"
Expect.isNone field.ParameterName "The default parameter name should be None"
Expect.isNone field.Qualifier "The default table qualifier should be None"
}
testList "NameToPath" [
test "succeeds for PostgreSQL and a simple name" {
Expect.equal
"data->>'Simple'" (Field.NameToPath "Simple" PostgreSQL) "Path not constructed correctly"
}
test "succeeds for SQLite and a simple name" {
Expect.equal
"data->>'Simple'" (Field.NameToPath "Simple" SQLite) "Path not constructed correctly"
}
test "succeeds for PostgreSQL and a nested name" {
Expect.equal
"data#>>'{A,Long,Path,to,the,Property}'"
(Field.NameToPath "A.Long.Path.to.the.Property" PostgreSQL)
"Path not constructed correctly"
}
test "succeeds for SQLite and a nested name" {
Expect.equal
"data->>'A'->>'Long'->>'Path'->>'to'->>'the'->>'Property'"
(Field.NameToPath "A.Long.Path.to.the.Property" SQLite)
"Path not constructed correctly"
}
]
test "WithParameterName succeeds" {
let field = (Field.EQ "Bob" "Tom").WithParameterName "@name"
Expect.isSome field.ParameterName "The parameter name should have been filled"
Expect.equal "@name" field.ParameterName.Value "The parameter name is incorrect"
}
test "WithQualifier succeeds" {
let field = (Field.EQ "Bill" "Matt").WithQualifier "joe"
Expect.isSome field.Qualifier "The table qualifier should have been filled"
Expect.equal "joe" field.Qualifier.Value "The table qualifier is incorrect"
}
testList "Path" [
test "succeeds for a PostgreSQL single field with no qualifier" {
let field = Field.GE "SomethingCool" 18
Expect.equal "data->>'SomethingCool'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
}
test "succeeds for a PostgreSQL single field with a qualifier" {
let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" }
Expect.equal
"this.data->>'SomethingElse'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
}
test "succeeds for a PostgreSQL nested field with no qualifier" {
let field = Field.EQ "My.Nested.Field" "howdy"
Expect.equal "data#>>'{My,Nested,Field}'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
}
test "succeeds for a PostgreSQL nested field with a qualifier" {
let field = { Field.EQ "Nest.Away" "doc" with Qualifier = Some "bird" }
Expect.equal "bird.data#>>'{Nest,Away}'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
}
test "succeeds for a SQLite single field with no qualifier" {
let field = Field.GE "SomethingCool" 18
Expect.equal "data->>'SomethingCool'" (field.Path SQLite) "The SQLite path is incorrect"
}
test "succeeds for a SQLite single field with a qualifier" {
let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" }
Expect.equal "this.data->>'SomethingElse'" (field.Path SQLite) "The SQLite path is incorrect"
}
test "succeeds for a SQLite nested field with no qualifier" {
let field = Field.EQ "My.Nested.Field" "howdy"
Expect.equal "data->>'My'->>'Nested'->>'Field'" (field.Path SQLite) "The SQLite path is incorrect"
}
test "succeeds for a SQLite nested field with a qualifier" {
let field = { Field.EQ "Nest.Away" "doc" with Qualifier = Some "bird" }
Expect.equal "bird.data->>'Nest'->>'Away'" (field.Path SQLite) "The SQLite path is incorrect"
}
]
]
testList "FieldMatch.ToString" [
test "succeeds for Any" {
Expect.equal (string Any) "OR" "SQL for Any is incorrect"
}
test "succeeds for All" {
Expect.equal (string All) "AND" "SQL for All is incorrect"
}
]
testList "ParameterName.Derive" [
test "succeeds with existing name" {
let name = ParameterName()
Expect.equal (name.Derive(Some "@taco")) "@taco" "Name should have been @taco"
Expect.equal (name.Derive None) "@field0" "Counter should not have advanced for named field"
}
test "succeeds with non-existent name" {
let name = ParameterName()
Expect.equal (name.Derive None) "@field0" "Anonymous field name should have been returned"
Expect.equal (name.Derive None) "@field1" "Counter should have advanced from previous call"
Expect.equal (name.Derive None) "@field2" "Counter should have advanced from previous call"
Expect.equal (name.Derive None) "@field3" "Counter should have advanced from previous call"
}
]
testList "Query" [
test "statementWhere succeeds" {
Expect.equal (Query.statementWhere "x" "y") "x WHERE y" "Statements not combined correctly"
}
testList "Definition" [
test "ensureTableFor succeeds" {
Expect.equal
(Query.Definition.ensureTableFor "my.table" "JSONB")
"CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)"
"CREATE TABLE statement not constructed correctly"
}
testList "ensureKey" [
test "succeeds when a schema is present" {
Expect.equal
(Query.Definition.ensureKey "test.table" PostgreSQL)
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'Id'))"
"CREATE INDEX for key statement with schema not constructed correctly"
}
test "succeeds when a schema is not present" {
Expect.equal
(Query.Definition.ensureKey "table" SQLite)
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data->>'Id'))"
"CREATE INDEX for key statement without schema not constructed correctly"
}
]
testList "ensureIndexOn" [
test "succeeds for multiple fields and directions" {
Expect.equal
(Query.Definition.ensureIndexOn
"test.table" "gibberish" [ "taco"; "guac DESC"; "salsa ASC" ] PostgreSQL)
([ "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table "
"((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)" ]
|> String.concat "")
"CREATE INDEX for multiple field statement incorrect"
}
test "succeeds for nested PostgreSQL field" {
Expect.equal
(Query.Definition.ensureIndexOn tbl "nest" [ "a.b.c" ] PostgreSQL)
$"CREATE INDEX IF NOT EXISTS idx_{tbl}_nest ON {tbl} ((data#>>'{{a,b,c}}'))"
"CREATE INDEX for nested PostgreSQL field incorrect"
}
test "succeeds for nested SQLite field" {
Expect.equal
(Query.Definition.ensureIndexOn tbl "nest" [ "a.b.c" ] SQLite)
$"CREATE INDEX IF NOT EXISTS idx_{tbl}_nest ON {tbl} ((data->>'a'->>'b'->>'c'))"
"CREATE INDEX for nested SQLite field incorrect"
}
]
]
test "insert succeeds" {
Expect.equal (Query.insert tbl) $"INSERT INTO {tbl} VALUES (@data)" "INSERT statement not correct"
}
test "save succeeds" {
Expect.equal
(Query.save tbl)
$"INSERT INTO {tbl} VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data"
"INSERT ON CONFLICT UPDATE statement not correct"
}
test "count succeeds" {
Expect.equal (Query.count tbl) $"SELECT COUNT(*) AS it FROM {tbl}" "Count query not correct"
}
test "exists succeeds" {
Expect.equal
(Query.exists tbl "turkey")
$"SELECT EXISTS (SELECT 1 FROM {tbl} WHERE turkey) AS it"
"Exists query not correct"
}
test "find succeeds" {
Expect.equal (Query.find tbl) $"SELECT data FROM {tbl}" "Find query not correct"
}
test "update succeeds" {
Expect.equal (Query.update tbl) $"UPDATE {tbl} SET data = @data" "Update query not correct"
}
test "delete succeeds" {
Expect.equal (Query.delete tbl) $"DELETE FROM {tbl}" "Delete query not correct"
}
testList "orderBy" [
test "succeeds for no fields" {
Expect.equal (Query.orderBy [] PostgreSQL) "" "Order By should have been blank (PostgreSQL)"
Expect.equal (Query.orderBy [] SQLite) "" "Order By should have been blank (SQLite)"
}
test "succeeds for PostgreSQL with one field and no direction" {
Expect.equal
(Query.orderBy [ Field.Named "TestField" ] PostgreSQL)
" ORDER BY data->>'TestField'"
"Order By not constructed correctly"
}
test "succeeds for SQLite with one field and no direction" {
Expect.equal
(Query.orderBy [ Field.Named "TestField" ] SQLite)
" ORDER BY data->>'TestField'"
"Order By not constructed correctly"
}
test "succeeds for PostgreSQL with multiple fields and direction" {
Expect.equal
(Query.orderBy
[ Field.Named "Nested.Test.Field DESC"; Field.Named "AnotherField"; Field.Named "It DESC" ]
PostgreSQL)
" ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC"
"Order By not constructed correctly"
}
test "succeeds for SQLite with multiple fields and direction" {
Expect.equal
(Query.orderBy
[ Field.Named "Nested.Test.Field DESC"; Field.Named "AnotherField"; Field.Named "It DESC" ]
SQLite)
" ORDER BY data->>'Nested'->>'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC"
"Order By not constructed correctly"
}
test "succeeds for PostgreSQL numeric fields" {
Expect.equal
(Query.orderBy [ Field.Named "n:Test" ] PostgreSQL)
" ORDER BY (data->>'Test')::numeric"
"Order By not constructed correctly for numeric field"
}
test "succeeds for SQLite numeric fields" {
Expect.equal
(Query.orderBy [ Field.Named "n:Test" ] SQLite)
" ORDER BY data->>'Test'"
"Order By not constructed correctly for numeric field"
}
]
]
]

View File

@@ -252,9 +252,18 @@ let queryTests = testList "Query" [
open ThrowawayDb.Postgres open ThrowawayDb.Postgres
open Types open Types
/// Documents to use for integration tests
let documents = [
{ Id = "one"; Value = "FIRST!"; NumValue = 0; Sub = None }
{ Id = "two"; Value = "another"; NumValue = 10; Sub = Some { Foo = "green"; Bar = "blue" } }
{ Id = "three"; Value = ""; NumValue = 4; Sub = None }
{ Id = "four"; Value = "purple"; NumValue = 17; Sub = Some { Foo = "green"; Bar = "red" } }
{ Id = "five"; Value = "purple"; NumValue = 18; Sub = None }
]
/// Load the test documents into the database /// Load the test documents into the database
let loadDocs () = backgroundTask { let loadDocs () = backgroundTask {
for doc in testDocuments do do! insert PostgresDb.TableName doc for doc in documents do do! insert PostgresDb.TableName doc
} }
/// Integration tests for the Configuration module of the PostgreSQL library /// Integration tests for the Configuration module of the PostgreSQL library
@@ -426,68 +435,6 @@ let documentTests = testList "Document" [
|> Async.RunSynchronously) |> Async.RunSynchronously)
"An exception should have been raised for duplicate document ID insert" "An exception should have been raised for duplicate document ID insert"
} }
testTask "succeeds when adding a numeric auto ID" {
try
Configuration.useAutoIdStrategy Number
Configuration.useIdField "Key"
use db = PostgresDb.BuildDb()
let! before = Count.all PostgresDb.TableName
Expect.equal before 0 "There should be no documents in the table"
do! insert PostgresDb.TableName { Key = 0; Text = "one" }
do! insert PostgresDb.TableName { Key = 0; Text = "two" }
do! insert PostgresDb.TableName { Key = 77; Text = "three" }
do! insert PostgresDb.TableName { Key = 0; Text = "four" }
let! after = Find.allOrdered<NumIdDocument> PostgresDb.TableName [ Field.Named "n:Key" ]
Expect.hasLength after 4 "There should have been 4 documents returned"
Expect.equal (after |> List.map _.Key) [ 1; 2; 77; 78 ] "The IDs were not generated correctly"
finally
Configuration.useAutoIdStrategy Disabled
Configuration.useIdField "Id"
}
testTask "succeeds when adding a GUID auto ID" {
try
Configuration.useAutoIdStrategy Guid
use db = PostgresDb.BuildDb()
let! before = Count.all PostgresDb.TableName
Expect.equal before 0 "There should be no documents in the table"
do! insert PostgresDb.TableName { emptyDoc with Value = "one" }
do! insert PostgresDb.TableName { emptyDoc with Value = "two" }
do! insert PostgresDb.TableName { emptyDoc with Id = "abc123"; Value = "three" }
do! insert PostgresDb.TableName { emptyDoc with Value = "four" }
let! after = Find.all<JsonDocument> PostgresDb.TableName
Expect.hasLength after 4 "There should have been 4 documents returned"
Expect.hasCountOf after 3u (fun doc -> doc.Id.Length = 32) "Three of the IDs should have been GUIDs"
Expect.hasCountOf after 1u (fun doc -> doc.Id = "abc123") "The provided ID should have been used as-is"
finally
Configuration.useAutoIdStrategy Disabled
}
testTask "succeeds when adding a RandomString auto ID" {
try
Configuration.useAutoIdStrategy RandomString
Configuration.useIdStringLength 44
use db = PostgresDb.BuildDb()
let! before = Count.all PostgresDb.TableName
Expect.equal before 0 "There should be no documents in the table"
do! insert PostgresDb.TableName { emptyDoc with Value = "one" }
do! insert PostgresDb.TableName { emptyDoc with Value = "two" }
do! insert PostgresDb.TableName { emptyDoc with Id = "abc123"; Value = "three" }
do! insert PostgresDb.TableName { emptyDoc with Value = "four" }
let! after = Find.all<JsonDocument> PostgresDb.TableName
Expect.hasLength after 4 "There should have been 4 documents returned"
Expect.hasCountOf
after 3u (fun doc -> doc.Id.Length = 44)
"Three of the IDs should have been 44-character random strings"
Expect.hasCountOf after 1u (fun doc -> doc.Id = "abc123") "The provided ID should have been used as-is"
finally
Configuration.useAutoIdStrategy Disabled
Configuration.useIdStringLength 16
}
] ]
testList "save" [ testList "save" [
testTask "succeeds when a document is inserted" { testTask "succeeds when a document is inserted" {

View File

@@ -118,9 +118,18 @@ let parametersTests = testList "Parameters" [
(** INTEGRATION TESTS **) (** INTEGRATION TESTS **)
/// Documents used for integration tests
let documents = [
{ Id = "one"; Value = "FIRST!"; NumValue = 0; Sub = None }
{ Id = "two"; Value = "another"; NumValue = 10; Sub = Some { Foo = "green"; Bar = "blue" } }
{ Id = "three"; Value = ""; NumValue = 4; Sub = None }
{ Id = "four"; Value = "purple"; NumValue = 17; Sub = Some { Foo = "green"; Bar = "red" } }
{ Id = "five"; Value = "purple"; NumValue = 18; Sub = None }
]
/// Load a table with the test documents /// Load a table with the test documents
let loadDocs () = backgroundTask { let loadDocs () = backgroundTask {
for doc in testDocuments do do! insert SqliteDb.TableName doc for doc in documents do do! insert SqliteDb.TableName doc
} }
/// Integration tests for the Configuration module of the SQLite library /// Integration tests for the Configuration module of the SQLite library
@@ -135,6 +144,32 @@ let configurationTests = testList "Configuration" [
finally finally
Configuration.useConnectionString "Data Source=:memory:" Configuration.useConnectionString "Data Source=:memory:"
} }
test "useSerializer succeeds" {
try
Configuration.useSerializer
{ new IDocumentSerializer with
member _.Serialize<'T>(it: 'T) : string = """{"Overridden":true}"""
member _.Deserialize<'T>(it: string) : 'T = Unchecked.defaultof<'T>
}
let serialized = Configuration.serializer().Serialize { Foo = "howdy"; Bar = "bye"}
Expect.equal serialized """{"Overridden":true}""" "Specified serializer was not used"
let deserialized = Configuration.serializer().Deserialize<obj> """{"Something":"here"}"""
Expect.isNull deserialized "Specified serializer should have returned null"
finally
Configuration.useSerializer DocumentSerializer.``default``
}
test "serializer returns configured serializer" {
Expect.isTrue (obj.ReferenceEquals(DocumentSerializer.``default``, Configuration.serializer ()))
"Serializer should have been the same"
}
test "useIdField / idField succeeds" {
Expect.equal (Configuration.idField ()) "Id" "The default configured ID field was incorrect"
Configuration.useIdField "id"
Expect.equal (Configuration.idField ()) "id" "useIdField did not set the ID field"
Configuration.useIdField "Id"
}
] ]
/// Integration tests for the Custom module of the SQLite library /// Integration tests for the Custom module of the SQLite library
@@ -274,68 +309,6 @@ let documentTests = testList "Document" [
insert SqliteDb.TableName {emptyDoc with Id = "test" } |> Async.AwaitTask |> Async.RunSynchronously) insert SqliteDb.TableName {emptyDoc with Id = "test" } |> Async.AwaitTask |> Async.RunSynchronously)
"An exception should have been raised for duplicate document ID insert" "An exception should have been raised for duplicate document ID insert"
} }
testTask "succeeds when adding a numeric auto ID" {
try
Configuration.useAutoIdStrategy Number
Configuration.useIdField "Key"
use! db = SqliteDb.BuildDb()
let! before = Count.all SqliteDb.TableName
Expect.equal before 0L "There should be no documents in the table"
do! insert SqliteDb.TableName { Key = 0; Text = "one" }
do! insert SqliteDb.TableName { Key = 0; Text = "two" }
do! insert SqliteDb.TableName { Key = 77; Text = "three" }
do! insert SqliteDb.TableName { Key = 0; Text = "four" }
let! after = Find.allOrdered<NumIdDocument> SqliteDb.TableName [ Field.Named "Key" ]
Expect.hasLength after 4 "There should have been 4 documents returned"
Expect.equal (after |> List.map _.Key) [ 1; 2; 77; 78 ] "The IDs were not generated correctly"
finally
Configuration.useAutoIdStrategy Disabled
Configuration.useIdField "Id"
}
testTask "succeeds when adding a GUID auto ID" {
try
Configuration.useAutoIdStrategy Guid
use! db = SqliteDb.BuildDb()
let! before = Count.all SqliteDb.TableName
Expect.equal before 0L "There should be no documents in the table"
do! insert SqliteDb.TableName { emptyDoc with Value = "one" }
do! insert SqliteDb.TableName { emptyDoc with Value = "two" }
do! insert SqliteDb.TableName { emptyDoc with Id = "abc123"; Value = "three" }
do! insert SqliteDb.TableName { emptyDoc with Value = "four" }
let! after = Find.all<JsonDocument> SqliteDb.TableName
Expect.hasLength after 4 "There should have been 4 documents returned"
Expect.hasCountOf after 3u (fun doc -> doc.Id.Length = 32) "Three of the IDs should have been GUIDs"
Expect.hasCountOf after 1u (fun doc -> doc.Id = "abc123") "The provided ID should have been used as-is"
finally
Configuration.useAutoIdStrategy Disabled
}
testTask "succeeds when adding a RandomString auto ID" {
try
Configuration.useAutoIdStrategy RandomString
Configuration.useIdStringLength 44
use! db = SqliteDb.BuildDb()
let! before = Count.all SqliteDb.TableName
Expect.equal before 0L "There should be no documents in the table"
do! insert SqliteDb.TableName { emptyDoc with Value = "one" }
do! insert SqliteDb.TableName { emptyDoc with Value = "two" }
do! insert SqliteDb.TableName { emptyDoc with Id = "abc123"; Value = "three" }
do! insert SqliteDb.TableName { emptyDoc with Value = "four" }
let! after = Find.all<JsonDocument> SqliteDb.TableName
Expect.hasLength after 4 "There should have been 4 documents returned"
Expect.hasCountOf
after 3u (fun doc -> doc.Id.Length = 44)
"Three of the IDs should have been 44-character random strings"
Expect.hasCountOf after 1u (fun doc -> doc.Id = "abc123") "The provided ID should have been used as-is"
finally
Configuration.useAutoIdStrategy Disabled
Configuration.useIdStringLength 16
}
] ]
testList "save" [ testList "save" [
testTask "succeeds when a document is inserted" { testTask "succeeds when a document is inserted" {
@@ -527,7 +500,6 @@ let findTests = testList "Find" [
let! docs = let! docs =
Find.byFieldsOrdered<JsonDocument> Find.byFieldsOrdered<JsonDocument>
SqliteDb.TableName Any [ Field.GT "NumValue" 15 ] [ Field.Named "Id" ] SqliteDb.TableName Any [ Field.GT "NumValue" 15 ] [ Field.Named "Id" ]
Expect.hasLength docs 2 "There should have been two documents returned"
Expect.equal Expect.equal
(docs |> List.map _.Id |> String.concat "|") "five|four" "The documents were not ordered correctly" (docs |> List.map _.Id |> String.concat "|") "five|four" "The documents were not ordered correctly"
} }
@@ -538,32 +510,9 @@ let findTests = testList "Find" [
let! docs = let! docs =
Find.byFieldsOrdered<JsonDocument> Find.byFieldsOrdered<JsonDocument>
SqliteDb.TableName Any [ Field.GT "NumValue" 15 ] [ Field.Named "Id DESC" ] SqliteDb.TableName Any [ Field.GT "NumValue" 15 ] [ Field.Named "Id DESC" ]
Expect.hasLength docs 2 "There should have been two documents returned"
Expect.equal Expect.equal
(docs |> List.map _.Id |> String.concat "|") "four|five" "The documents were not ordered correctly" (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<JsonDocument>
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<JsonDocument>
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" [ testList "firstByFields" [
testTask "succeeds when a document is found" { testTask "succeeds when a document is found" {

View File

@@ -1,9 +1,5 @@
module Types module Types
type NumIdDocument =
{ Key: int
Text: string }
type SubDocument = type SubDocument =
{ Foo: string { Foo: string
Bar: string } Bar: string }

View File

@@ -1,16 +1,13 @@
#!/bin/bash #!/bin/bash
echo --- Package Common library echo --- Package Common library
rm Common/bin/Release/BitBadger.Documents.Common.*.nupkg || true
dotnet pack Common/BitBadger.Documents.Common.fsproj -c Release dotnet pack Common/BitBadger.Documents.Common.fsproj -c Release
cp Common/bin/Release/BitBadger.Documents.Common.*.nupkg . cp Common/bin/Release/BitBadger.Documents.Common.*.nupkg .
echo --- Package PostgreSQL library echo --- Package PostgreSQL library
rm Postgres/bin/Release/BitBadger.Documents.Postgres*.nupkg || true
dotnet pack Postgres/BitBadger.Documents.Postgres.fsproj -c Release dotnet pack Postgres/BitBadger.Documents.Postgres.fsproj -c Release
cp Postgres/bin/Release/BitBadger.Documents.Postgres.*.nupkg . cp Postgres/bin/Release/BitBadger.Documents.Postgres.*.nupkg .
echo --- Package SQLite library echo --- Package SQLite library
rm Sqlite/bin/Release/BitBadger.Documents.Sqlite*.nupkg || true
dotnet pack Sqlite/BitBadger.Documents.Sqlite.fsproj -c Release dotnet pack Sqlite/BitBadger.Documents.Sqlite.fsproj -c Release
cp Sqlite/bin/Release/BitBadger.Documents.Sqlite.*.nupkg . cp Sqlite/bin/Release/BitBadger.Documents.Sqlite.*.nupkg .