Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Linq support for FSharp options #3589

Merged
merged 19 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/FSharpTypes/FSharpTypes.fsproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>8.0</LangVersion>
<TargetFrameworks>net8.0;net7.0;net9.0</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<Compile Include="Library.fs"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\LinqTestsTypes\LinqTestsTypes.csproj" />
</ItemGroup>

</Project>
24 changes: 24 additions & 0 deletions src/FSharpTypes/Library.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
module FSharpTypes

open System
open System.Linq.Expressions
open Marten.Testing.Documents
open Microsoft.FSharp.Linq.RuntimeHelpers

type OrderId = Id of Guid

Expand All @@ -14,3 +17,24 @@ type RecordTypeOrderId = { Part1: string; Part2: string }

type ArbitraryClass() =
member this.Value = "ok"

let rec stripFSharpFunc (expression: Expression) =
match expression with
| :? MethodCallExpression as callExpression when callExpression.Method.Name = "ToFSharpFunc" ->
stripFSharpFunc callExpression.Arguments.[0]
| _ -> expression

let toLinqExpression expr =
expr
|> LeafExpressionConverter.QuotationToExpression
|> stripFSharpFunc
|> unbox<System.Linq.Expressions.Expression<System.Func<Target, bool>>>
let greaterThanWithFsharpDateOption =
<@ fun (o1: Target) -> o1.FSharpDateTimeOffsetOption >= Some DateTimeOffset.UtcNow @> |> toLinqExpression
let lesserThanWithFsharpDateOption = <@ (fun (o1: Target) -> o1.FSharpDateTimeOffsetOption <= Some DateTimeOffset.UtcNow ) @> |> toLinqExpression
let greaterThanWithFsharpDecimalOption = <@ (fun (o1: Target) -> o1.FSharpDecimalOption >= Some 5m ) @> |> toLinqExpression
let lesserThanWithFsharpDecimalOption = <@ (fun (o1: Target) -> o1.FSharpDecimalOption <= Some 5m ) @> |> toLinqExpression
let greaterThanWithFsharpStringOption = <@ (fun (o1: Target) -> o1.FSharpStringOption >= Some "MyString" ) @> |> toLinqExpression
let lesserThanWithFsharpStringOption = <@ (fun (o1: Target) -> o1.FSharpStringOption <= Some "MyString" ) @> |> toLinqExpression


30 changes: 29 additions & 1 deletion src/LinqTests/Acceptance/Support/DefaultQueryFixture.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Text.Json.Serialization;
using Marten;
using Marten.Services;
using Marten.Testing.Documents;
Expand All @@ -11,7 +12,6 @@ public DefaultQueryFixture()
{
Store = ProvisionStore("linq_querying");


DuplicatedFieldStore = ProvisionStore("duplicate_fields", o =>
{
o.Schema.For<Target>()
Expand All @@ -25,6 +25,30 @@ public DefaultQueryFixture()
.Duplicate(x => x.NumberArray);
});

FSharpFriendlyStore = ProvisionStore("fsharp_linq_querying", options =>
{
options.RegisterFSharpOptionValueTypes();
var serializerOptions = JsonFSharpOptions.Default().WithUnwrapOption().ToJsonSerializerOptions();
options.UseSystemTextJsonForSerialization(serializerOptions);
}, isFsharpTest: true);

FSharpFriendlyStoreWithDuplicatedField = ProvisionStore("fsharp_duplicated_fields", options =>
{
options.Schema.For<Target>()
.Duplicate(x => x.Number)
.Duplicate(x => x.Long)
.Duplicate(x => x.String)
.Duplicate(x => x.Date)
.Duplicate(x => x.Double)
.Duplicate(x => x.Flag)
.Duplicate(x => x.Color)
.Duplicate(x => x.NumberArray);

options.RegisterFSharpOptionValueTypes();
var serializerOptions = JsonFSharpOptions.Default().WithUnwrapOption().ToJsonSerializerOptions();
options.UseSystemTextJsonForSerialization(serializerOptions);
}, isFsharpTest: true);

SystemTextJsonStore = ProvisionStore("stj_linq", o =>
{
o.Serializer<SystemTextJsonSerializer>();
Expand All @@ -35,5 +59,9 @@ public DefaultQueryFixture()

public DocumentStore DuplicatedFieldStore { get; set; }

public DocumentStore FSharpFriendlyStore { get; set; }
public DocumentStore FSharpFriendlyStoreWithDuplicatedField { get; set; }

public DocumentStore Store { get; set; }
}

16 changes: 13 additions & 3 deletions src/LinqTests/Acceptance/Support/LinqTestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,27 @@ public static IEnumerable<object[]> GetDescriptions()
return _descriptions.Select(x => new object[] { x });
}

protected async Task assertTestCase(string description, IDocumentStore store)
protected async Task assertTestCaseWithDocuments(string description, IDocumentStore store, Target[] documents)
{
var index = _descriptions.IndexOf(description);

var testCase = testCases[index];
await using var session = store.QuerySession();


var logger = new TestOutputMartenLogger(TestOutput);

session.Logger = logger;

await testCase.Compare(session, Fixture.Documents, logger);
await testCase.Compare(session, documents, logger);
}

protected Task assertTestCase(string description, IDocumentStore store)
{
return assertTestCaseWithDocuments(description, store, Fixture.Documents);
}

protected Task assertFSharpTestCase(string description, IDocumentStore store)
{
return assertTestCaseWithDocuments(description, store, Fixture.FSharpDocuments);
}
}
17 changes: 15 additions & 2 deletions src/LinqTests/Acceptance/Support/TargetSchemaFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ namespace LinqTests.Acceptance.Support;

public abstract class TargetSchemaFixture: IDisposable
{
/*
* Newtonsoft.Json does not support saving Discriminated Unions unwrapped (included f# options) which causes serialization-related errors.
* We must therefore only include F# data in F#-related tests to avoid false negatives.
*/
public readonly Target[] Documents = Target.GenerateRandomData(1000).ToArray();
public readonly Target[] FSharpDocuments = Target.GenerateRandomData(1000, includeFSharpUnionTypes: true).ToArray();

private readonly IList<DocumentStore> _stores = new List<DocumentStore>();

Expand All @@ -21,7 +26,7 @@ public void Dispose()
}
}

internal DocumentStore ProvisionStore(string schema, Action<StoreOptions> configure = null)
internal DocumentStore ProvisionStore(string schema, Action<StoreOptions> configure = null, bool isFsharpTest = false)
{
var store = DocumentStore.For(x =>
{
Expand All @@ -33,7 +38,15 @@ internal DocumentStore ProvisionStore(string schema, Action<StoreOptions> config

store.Advanced.Clean.CompletelyRemoveAll();

store.BulkInsert(Documents);

if (isFsharpTest)
{
store.BulkInsert(FSharpDocuments);
}
else
{
store.BulkInsert(Documents);
}

_stores.Add(store);

Expand Down
48 changes: 48 additions & 0 deletions src/LinqTests/Acceptance/where_clauses_fsharp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Threading.Tasks;
using LinqTests.Acceptance.Support;
using Microsoft.FSharp.Core;
using Xunit.Abstractions;

namespace LinqTests.Acceptance;

public class where_clauses_fsharp: LinqTestContext<where_clauses_fsharp>
{
public where_clauses_fsharp(DefaultQueryFixture fixture, ITestOutputHelper output) : base(fixture)
{
TestOutput = output;
}

static where_clauses_fsharp()
{

@where(x => x.FSharpBoolOption == FSharpOption<bool>.Some(true));
@where(x => x.FSharpBoolOption == FSharpOption<bool>.Some(false));
@where(x => x.FSharpDateOption == FSharpOption<DateTime>.Some(DateTime.Now));
@where(x => x.FSharpIntOption == FSharpOption<int>.Some(300));
@where(x => x.FSharpStringOption == FSharpOption<string>.Some("My String"));
@where(x => x.FSharpLongOption == FSharpOption<long>.Some(5_000_000));

//Comparing options is not a valid syntax in C#, we therefore define these expressions in F#
@where(FSharpTypes.greaterThanWithFsharpDateOption);
@where(FSharpTypes.lesserThanWithFsharpDateOption);
@where(FSharpTypes.greaterThanWithFsharpStringOption);
@where(FSharpTypes.lesserThanWithFsharpStringOption);
@where(FSharpTypes.greaterThanWithFsharpDecimalOption);
@where(FSharpTypes.lesserThanWithFsharpDecimalOption);
}

[Theory]
[MemberData(nameof(GetDescriptions))]
public Task run_query(string description)
{
return assertFSharpTestCase(description, Fixture.FSharpFriendlyStore);
}

[Theory]
[MemberData(nameof(GetDescriptions))]
public Task with_duplicated_fields(string description)
{
return assertFSharpTestCase(description, Fixture.FSharpFriendlyStoreWithDuplicatedField);
}
}
7 changes: 3 additions & 4 deletions src/LinqTests/LinqTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\FSharpTypes\FSharpTypes.fsproj" />
<ProjectReference Include="..\Marten\Marten.csproj" />
<ProjectReference Include="..\Marten.Testing.OtherAssembly\Marten.Testing.OtherAssembly.csproj" />
<ProjectReference Include="..\Marten.Testing.ThirdAssembly\Marten.Testing.ThirdAssembly.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="FSharp.SystemTextJson" Version="1.3.13" />
<PackageReference Include="Jil" Version="3.0.0-alpha2" />
<PackageReference Include="Lamar" Version="14.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
Expand Down Expand Up @@ -55,9 +57,6 @@
<Compile Include="..\Marten.Testing\Documents\StringDoc.cs">
<Link>Documents\StringDoc.cs</Link>
</Compile>
<Compile Include="..\Marten.Testing\Documents\Target.cs">
<Link>Documents\Target.cs</Link>
</Compile>
<Compile Include="..\Marten.Testing\Documents\TargetIntId.cs">
<Link>Documents\TargetIntId.cs</Link>
</Compile>
Expand Down
Loading
Loading