Compare commits
26 Commits
v4-rc2
...
88f11a854f
| Author | SHA1 | Date | |
|---|---|---|---|
| 88f11a854f | |||
| e2948ea6a1 | |||
| d993f71788 | |||
| fba8ec7bc5 | |||
| 8e33299e22 | |||
| e07844570a | |||
| bac3bd2ef0 | |||
| edd4d006da | |||
| 96f5e1515d | |||
| d382699a17 | |||
| 0c308c5f33 | |||
| 77a6aaa583 | |||
| d9d37f110d | |||
| e0fb793ec9 | |||
| 74e5b77edb | |||
| 98bc83ac17 | |||
| 35755df99a | |||
| b1c3991e11 | |||
| 85750e19f2 | |||
| d8f64417e5 | |||
| 202fca272e | |||
| 8a15bdce2e | |||
| d131eda56e | |||
| e2232e91bb | |||
| e96c449324 | |||
| 433302d995 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -397,6 +397,3 @@ FodyWeavers.xsd
|
|||||||
# JetBrains Rider
|
# JetBrains Rider
|
||||||
*.sln.iml
|
*.sln.iml
|
||||||
**/.idea
|
**/.idea
|
||||||
|
|
||||||
# Test run files
|
|
||||||
src/*-tests.txt
|
|
||||||
|
|||||||
@@ -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}"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">]
|
||||||
|
|||||||
@@ -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>]
|
||||||
|
|||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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; } = "";
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|||||||
@@ -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" {
|
||||||
|
|||||||
@@ -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" {
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|||||||
@@ -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 .
|
||||||
|
|||||||
Reference in New Issue
Block a user