RC4 changes (#7)

- Add `In` and `InArray` comparisons
- Replace `Op` with `Comparison` (internal API, but was public)
- Spell out comparisons in `Field` constructor functions

Reviewed-on: #7
This commit was merged in pull request #7.
This commit is contained in:
2024-09-17 02:33:57 +00:00
parent 3bc662c984
commit 168bf0cd14
19 changed files with 883 additions and 569 deletions

View File

@@ -1,6 +1,5 @@
using Expecto.CSharp;
using Expecto;
using Microsoft.FSharp.Collections;
using Microsoft.FSharp.Core;
namespace BitBadger.Documents.Tests.CSharp;
@@ -22,45 +21,53 @@ internal class TestSerializer : IDocumentSerializer
public static class CommonCSharpTests
{
/// <summary>
/// Unit tests for the Op enum
/// Unit tests for the OpSql property of the Comparison discriminated union
/// </summary>
private static readonly Test OpTests = TestList("Op",
private static readonly Test OpTests = TestList("Comparison.OpSql",
[
TestCase("EQ succeeds", () =>
TestCase("Equal succeeds", () =>
{
Expect.equal(Op.EQ.ToString(), "=", "The equals operator was not correct");
Expect.equal(Comparison.NewEqual("").OpSql, "=", "The Equals SQL was not correct");
}),
TestCase("GT succeeds", () =>
TestCase("Greater succeeds", () =>
{
Expect.equal(Op.GT.ToString(), ">", "The greater than operator was not correct");
Expect.equal(Comparison.NewGreater("").OpSql, ">", "The Greater SQL was not correct");
}),
TestCase("GE succeeds", () =>
TestCase("GreaterOrEqual succeeds", () =>
{
Expect.equal(Op.GE.ToString(), ">=", "The greater than or equal to operator was not correct");
Expect.equal(Comparison.NewGreaterOrEqual("").OpSql, ">=", "The GreaterOrEqual SQL was not correct");
}),
TestCase("LT succeeds", () =>
TestCase("Less succeeds", () =>
{
Expect.equal(Op.LT.ToString(), "<", "The less than operator was not correct");
Expect.equal(Comparison.NewLess("").OpSql, "<", "The Less SQL was not correct");
}),
TestCase("LE succeeds", () =>
TestCase("LessOrEqual succeeds", () =>
{
Expect.equal(Op.LE.ToString(), "<=", "The less than or equal to operator was not correct");
Expect.equal(Comparison.NewLessOrEqual("").OpSql, "<=", "The LessOrEqual SQL was not correct");
}),
TestCase("NE succeeds", () =>
TestCase("NotEqual succeeds", () =>
{
Expect.equal(Op.NE.ToString(), "<>", "The not equal to operator was not correct");
Expect.equal(Comparison.NewNotEqual("").OpSql, "<>", "The NotEqual SQL was not correct");
}),
TestCase("BT succeeds", () =>
TestCase("Between succeeds", () =>
{
Expect.equal(Op.BT.ToString(), "BETWEEN", "The \"between\" operator was not correct");
Expect.equal(Comparison.NewBetween("", "").OpSql, "BETWEEN", "The Between SQL was not correct");
}),
TestCase("EX succeeds", () =>
TestCase("In succeeds", () =>
{
Expect.equal(Op.EX.ToString(), "IS NOT NULL", "The \"exists\" operator was not correct");
Expect.equal(Comparison.NewIn([]).OpSql, "IN", "The In SQL was not correct");
}),
TestCase("NEX succeeds", () =>
TestCase("InArray succeeds", () =>
{
Expect.equal(Op.NEX.ToString(), "IS NULL", "The \"not exists\" operator was not correct");
Expect.equal(Comparison.NewInArray("", []).OpSql, "?|", "The InArray SQL was not correct");
}),
TestCase("Exists succeeds", () =>
{
Expect.equal(Comparison.Exists.OpSql, "IS NOT NULL", "The Exists SQL was not correct");
}),
TestCase("NotExists succeeds", () =>
{
Expect.equal(Comparison.NotExists.OpSql, "IS NULL", "The NotExists SQL was not correct");
})
]);
@@ -69,101 +76,110 @@ public static class CommonCSharpTests
/// </summary>
private static readonly Test FieldTests = TestList("Field",
[
TestCase("EQ succeeds", () =>
TestCase("Equal succeeds", () =>
{
var field = Field.EQ("Test", 14);
var field = Field.Equal("Test", 14);
Expect.equal(field.Name, "Test", "Field name incorrect");
Expect.equal(field.Op, Op.EQ, "Operator incorrect");
Expect.equal(field.Value, 14, "Value incorrect");
Expect.equal(field.Comparison, Comparison.NewEqual(14), "Comparison incorrect");
}),
TestCase("GT succeeds", () =>
TestCase("Greater succeeds", () =>
{
var field = Field.GT("Great", "night");
var field = Field.Greater("Great", "night");
Expect.equal(field.Name, "Great", "Field name incorrect");
Expect.equal(field.Op, Op.GT, "Operator incorrect");
Expect.equal(field.Value, "night", "Value incorrect");
Expect.equal(field.Comparison, Comparison.NewGreater("night"), "Comparison incorrect");
}),
TestCase("GE succeeds", () =>
TestCase("GreaterOrEqual succeeds", () =>
{
var field = Field.GE("Nice", 88L);
var field = Field.GreaterOrEqual("Nice", 88L);
Expect.equal(field.Name, "Nice", "Field name incorrect");
Expect.equal(field.Op, Op.GE, "Operator incorrect");
Expect.equal(field.Value, 88L, "Value incorrect");
Expect.equal(field.Comparison, Comparison.NewGreaterOrEqual(88L), "Comparison incorrect");
}),
TestCase("LT succeeds", () =>
TestCase("Less succeeds", () =>
{
var field = Field.LT("Lesser", "seven");
var field = Field.Less("Lesser", "seven");
Expect.equal(field.Name, "Lesser", "Field name incorrect");
Expect.equal(field.Op, Op.LT, "Operator incorrect");
Expect.equal(field.Value, "seven", "Value incorrect");
Expect.equal(field.Comparison, Comparison.NewLess("seven"), "Comparison incorrect");
}),
TestCase("LE succeeds", () =>
TestCase("LessOrEqual succeeds", () =>
{
var field = Field.LE("Nobody", "KNOWS");
var field = Field.LessOrEqual("Nobody", "KNOWS");
Expect.equal(field.Name, "Nobody", "Field name incorrect");
Expect.equal(field.Op, Op.LE, "Operator incorrect");
Expect.equal(field.Value, "KNOWS", "Value incorrect");
Expect.equal(field.Comparison, Comparison.NewLessOrEqual("KNOWS"), "Comparison incorrect");
}),
TestCase("NE succeeds", () =>
TestCase("NotEqual succeeds", () =>
{
var field = Field.NE("Park", "here");
var field = Field.NotEqual("Park", "here");
Expect.equal(field.Name, "Park", "Field name incorrect");
Expect.equal(field.Op, Op.NE, "Operator incorrect");
Expect.equal(field.Value, "here", "Value incorrect");
Expect.equal(field.Comparison, Comparison.NewNotEqual("here"), "Comparison incorrect");
}),
TestCase("BT succeeds", () =>
TestCase("Between succeeds", () =>
{
var field = Field.BT("Age", 18, 49);
var field = Field.Between("Age", 18, 49);
Expect.equal(field.Name, "Age", "Field name incorrect");
Expect.equal(field.Op, Op.BT, "Operator incorrect");
Expect.equal(((FSharpList<object>)field.Value).ToArray(), [18, 49], "Value incorrect");
Expect.equal(field.Comparison, Comparison.NewBetween(18, 49), "Comparison incorrect");
}),
TestCase("EX succeeds", () =>
TestCase("In succeeds", () =>
{
var field = Field.EX("Groovy");
var field = Field.In("Here", [8, 16, 32]);
Expect.equal(field.Name, "Here", "Field name incorrect");
Expect.isTrue(field.Comparison.IsIn, "Comparison incorrect");
Expect.sequenceEqual(((Comparison.In)field.Comparison).Values, [8, 16, 32], "Value incorrect");
}),
TestCase("InArray succeeds", () =>
{
var field = Field.InArray("ArrayField", "table", ["x", "y", "z"]);
Expect.equal(field.Name, "ArrayField", "Field name incorrect");
Expect.isTrue(field.Comparison.IsInArray, "Comparison incorrect");
var it = (Comparison.InArray)field.Comparison;
Expect.equal(it.Table, "table", "Table name incorrect");
Expect.sequenceEqual(it.Values, ["x", "y", "z"], "Value incorrect");
}),
TestCase("Exists succeeds", () =>
{
var field = Field.Exists("Groovy");
Expect.equal(field.Name, "Groovy", "Field name incorrect");
Expect.equal(field.Op, Op.EX, "Operator incorrect");
Expect.isTrue(field.Comparison.IsExists, "Comparison incorrect");
}),
TestCase("NEX succeeds", () =>
TestCase("NotExists succeeds", () =>
{
var field = Field.NEX("Rad");
var field = Field.NotExists("Rad");
Expect.equal(field.Name, "Rad", "Field name incorrect");
Expect.equal(field.Op, Op.NEX, "Operator incorrect");
Expect.isTrue(field.Comparison.IsNotExists, "Comparison incorrect");
}),
TestList("NameToPath",
[
TestCase("succeeds for PostgreSQL and a simple name", () =>
{
Expect.equal("data->>'Simple'", Field.NameToPath("Simple", Dialect.PostgreSQL),
Expect.equal("data->>'Simple'", Field.NameToPath("Simple", Dialect.PostgreSQL, FieldFormat.AsSql),
"Path not constructed correctly");
}),
TestCase("succeeds for SQLite and a simple name", () =>
{
Expect.equal("data->>'Simple'", Field.NameToPath("Simple", Dialect.SQLite),
Expect.equal("data->>'Simple'", Field.NameToPath("Simple", Dialect.SQLite, FieldFormat.AsSql),
"Path not constructed correctly");
}),
TestCase("succeeds for PostgreSQL and a nested name", () =>
{
Expect.equal("data#>>'{A,Long,Path,to,the,Property}'",
Field.NameToPath("A.Long.Path.to.the.Property", Dialect.PostgreSQL),
Field.NameToPath("A.Long.Path.to.the.Property", Dialect.PostgreSQL, FieldFormat.AsSql),
"Path not constructed correctly");
}),
TestCase("succeeds for SQLite and a nested name", () =>
{
Expect.equal("data->>'A'->>'Long'->>'Path'->>'to'->>'the'->>'Property'",
Field.NameToPath("A.Long.Path.to.the.Property", Dialect.SQLite),
Expect.equal("data->'A'->'Long'->'Path'->'to'->'the'->>'Property'",
Field.NameToPath("A.Long.Path.to.the.Property", Dialect.SQLite, FieldFormat.AsSql),
"Path not constructed correctly");
})
]),
TestCase("WithParameterName succeeds", () =>
{
var field = Field.EQ("Bob", "Tom").WithParameterName("@name");
var field = Field.Equal("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");
}),
TestCase("WithQualifier succeeds", () =>
{
var field = Field.EQ("Bill", "Matt").WithQualifier("joe");
var field = Field.Equal("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");
}),
@@ -171,48 +187,51 @@ public static class CommonCSharpTests
[
TestCase("succeeds for a PostgreSQL single field with no qualifier", () =>
{
var field = Field.GE("SomethingCool", 18);
Expect.equal("data->>'SomethingCool'", field.Path(Dialect.PostgreSQL),
var field = Field.GreaterOrEqual("SomethingCool", 18);
Expect.equal("data->>'SomethingCool'", field.Path(Dialect.PostgreSQL, FieldFormat.AsSql),
"The PostgreSQL path is incorrect");
}),
TestCase("succeeds for a PostgreSQL single field with a qualifier", () =>
{
var field = Field.LT("SomethingElse", 9).WithQualifier("this");
Expect.equal("this.data->>'SomethingElse'", field.Path(Dialect.PostgreSQL),
var field = Field.Less("SomethingElse", 9).WithQualifier("this");
Expect.equal("this.data->>'SomethingElse'", field.Path(Dialect.PostgreSQL, FieldFormat.AsSql),
"The PostgreSQL path is incorrect");
}),
TestCase("succeeds for a PostgreSQL nested field with no qualifier", () =>
{
var field = Field.EQ("My.Nested.Field", "howdy");
Expect.equal("data#>>'{My,Nested,Field}'", field.Path(Dialect.PostgreSQL),
var field = Field.Equal("My.Nested.Field", "howdy");
Expect.equal("data#>>'{My,Nested,Field}'", field.Path(Dialect.PostgreSQL, FieldFormat.AsSql),
"The PostgreSQL path is incorrect");
}),
TestCase("succeeds for a PostgreSQL nested field with a qualifier", () =>
{
var field = Field.EQ("Nest.Away", "doc").WithQualifier("bird");
Expect.equal("bird.data#>>'{Nest,Away}'", field.Path(Dialect.PostgreSQL),
var field = Field.Equal("Nest.Away", "doc").WithQualifier("bird");
Expect.equal("bird.data#>>'{Nest,Away}'", field.Path(Dialect.PostgreSQL, FieldFormat.AsSql),
"The PostgreSQL path is incorrect");
}),
TestCase("succeeds for a SQLite single field with no qualifier", () =>
{
var field = Field.GE("SomethingCool", 18);
Expect.equal("data->>'SomethingCool'", field.Path(Dialect.SQLite), "The SQLite path is incorrect");
var field = Field.GreaterOrEqual("SomethingCool", 18);
Expect.equal("data->>'SomethingCool'", field.Path(Dialect.SQLite, FieldFormat.AsSql),
"The SQLite path is incorrect");
}),
TestCase("succeeds for a SQLite single field with a qualifier", () =>
{
var field = Field.LT("SomethingElse", 9).WithQualifier("this");
Expect.equal("this.data->>'SomethingElse'", field.Path(Dialect.SQLite), "The SQLite path is incorrect");
var field = Field.Less("SomethingElse", 9).WithQualifier("this");
Expect.equal("this.data->>'SomethingElse'", field.Path(Dialect.SQLite, FieldFormat.AsSql),
"The SQLite path is incorrect");
}),
TestCase("succeeds for a SQLite nested field with no qualifier", () =>
{
var field = Field.EQ("My.Nested.Field", "howdy");
Expect.equal("data->>'My'->>'Nested'->>'Field'", field.Path(Dialect.SQLite),
var field = Field.Equal("My.Nested.Field", "howdy");
Expect.equal("data->'My'->'Nested'->>'Field'", field.Path(Dialect.SQLite, FieldFormat.AsSql),
"The SQLite path is incorrect");
}),
TestCase("succeeds for a SQLite nested field with a qualifier", () =>
{
var field = Field.EQ("Nest.Away", "doc").WithQualifier("bird");
Expect.equal("bird.data->>'Nest'->>'Away'", field.Path(Dialect.SQLite), "The SQLite path is incorrect");
var field = Field.Equal("Nest.Away", "doc").WithQualifier("bird");
Expect.equal("bird.data->'Nest'->>'Away'", field.Path(Dialect.SQLite, FieldFormat.AsSql),
"The SQLite path is incorrect");
})
])
]);
@@ -529,7 +548,7 @@ public static class CommonCSharpTests
{
Expect.equal(
Query.Definition.EnsureIndexOn("tbl", "nest", ["a.b.c"], Dialect.SQLite),
"CREATE INDEX IF NOT EXISTS idx_tbl_nest ON tbl ((data->>'a'->>'b'->>'c'))",
"CREATE INDEX IF NOT EXISTS idx_tbl_nest ON tbl ((data->'a'->'b'->>'c'))",
"CREATE INDEX for nested SQLite field incorrect");
})
])
@@ -601,7 +620,7 @@ public static class CommonCSharpTests
Field.Named("Nested.Test.Field DESC"), Field.Named("AnotherField"),
Field.Named("It DESC")
], Dialect.SQLite),
" ORDER BY data->>'Nested'->>'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC",
" ORDER BY data->'Nested'->'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC",
"Order By not constructed correctly");
}),
TestCase("succeeds for PostgreSQL numeric fields", () =>
@@ -623,7 +642,7 @@ public static class CommonCSharpTests
TestCase("succeeds for SQLite case-insensitive ordering", () =>
{
Expect.equal(Query.OrderBy([Field.Named("i:Test.Field ASC NULLS LAST")], Dialect.SQLite),
" ORDER BY data->>'Test'->>'Field' COLLATE NOCASE ASC NULLS LAST",
" ORDER BY data->'Test'->>'Field' COLLATE NOCASE ASC NULLS LAST",
"Order By not constructed correctly for case-insensitive field");
})
])