feat: add FSharp database abstraction layer
I don't love having separate modules for ReadOnly vs. ReadWrite connections, but F# doesn't support covariance, so this is the best I could come up with for now.
This commit is contained in:
parent
44b978309a
commit
d745706082
1
.github/workflows/publish-release.yml
vendored
1
.github/workflows/publish-release.yml
vendored
|
@ -46,4 +46,5 @@ jobs:
|
||||||
dotnet nuget push "FruityFoundation.FsBase.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
|
dotnet nuget push "FruityFoundation.FsBase.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
|
||||||
dotnet nuget push "FruityFoundation.Db.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
|
dotnet nuget push "FruityFoundation.Db.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
|
||||||
dotnet nuget push "FruityFoundation.DataAccess.Abstractions.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
|
dotnet nuget push "FruityFoundation.DataAccess.Abstractions.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
|
||||||
|
dotnet nuget push "FruityFoundation.DataAccess.Abstractions.FSharp.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
|
||||||
dotnet nuget push "FruityFoundation.DataAccess.Core.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
|
dotnet nuget push "FruityFoundation.DataAccess.Core.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<Version>1.11.0</Version>
|
||||||
|
<Authors>Kyle Ratti</Authors>
|
||||||
|
<RepositoryUrl>https://github.com/kyleratti/FruityFoundation</RepositoryUrl>
|
||||||
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\LICENSE" Pack="true" PackagePath="" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="ReadOnlyDb.fs" />
|
||||||
|
<Compile Include="ReadWriteDb.fs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\FruityFoundation.DataAccess.Abstractions\FruityFoundation.DataAccess.Abstractions.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FSharp.Control.TaskSeq" Version="0.4.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,35 @@
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module FruityFoundation.DataAccess.Abstractions.FSharp.ReadOnlyDb
|
||||||
|
|
||||||
|
open System.Collections.Generic
|
||||||
|
open System.Threading
|
||||||
|
open FSharp.Control
|
||||||
|
open FruityFoundation.DataAccess.Abstractions
|
||||||
|
|
||||||
|
let private toKeyValuePair (parms : (string * obj) seq) =
|
||||||
|
parms
|
||||||
|
|> Seq.map (fun (name, value) -> KeyValuePair(name, value))
|
||||||
|
|
||||||
|
let query<'a> (connection : IDatabaseConnection<ReadOnly>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.Query<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let queryUnbuffered<'a> (connection : IDatabaseConnection<ReadOnly>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = taskSeq {
|
||||||
|
yield! connection.QueryUnbuffered<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let querySingle<'a> (connection : IDatabaseConnection<ReadOnly>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.QuerySingle<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let execute (connection : IDatabaseConnection<ReadOnly>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.Execute(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let executeScalar<'a> (connection : IDatabaseConnection<ReadOnly>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.ExecuteScalar<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let executeReader (connection : IDatabaseConnection<ReadOnly>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.ExecuteReader(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module FruityFoundation.DataAccess.Abstractions.FSharp.ReadWriteDb
|
||||||
|
|
||||||
|
open System.Collections.Generic
|
||||||
|
open System.Threading
|
||||||
|
open FSharp.Control
|
||||||
|
open FruityFoundation.DataAccess.Abstractions
|
||||||
|
|
||||||
|
let private toKeyValuePair (parms : (string * obj) seq) =
|
||||||
|
parms
|
||||||
|
|> Seq.map (fun (name, value) -> KeyValuePair(name, value))
|
||||||
|
|
||||||
|
let query<'a> (connection : IDatabaseConnection<ReadWrite>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.Query<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let queryUnbuffered<'a> (connection : IDatabaseConnection<ReadWrite>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = taskSeq {
|
||||||
|
yield! connection.QueryUnbuffered<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let querySingle<'a> (connection : IDatabaseConnection<ReadWrite>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.QuerySingle<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let execute (connection : IDatabaseConnection<ReadWrite>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.Execute(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let executeScalar<'a> (connection : IDatabaseConnection<ReadWrite>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.ExecuteScalar<'a>(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
let executeReader (connection : IDatabaseConnection<ReadWrite>) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
|
||||||
|
return! connection.ExecuteReader(sql, parms |> toKeyValuePair, cancellationToken)
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="ReadWriteDbTests.fs" />
|
||||||
|
<Compile Include="ReadOnlyDbTests.fs" />
|
||||||
|
<Compile Include="Program.fs"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||||
|
<PackageReference Include="FakeItEasy" Version="8.3.0" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
|
||||||
|
<PackageReference Include="NUnit" Version="3.14.0"/>
|
||||||
|
<PackageReference Include="NUnit.Analyzers" Version="3.9.0"/>
|
||||||
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\FruityFoundation.DataAccess.Abstractions.FSharp\FruityFoundation.DataAccess.Abstractions.FSharp.fsproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,4 @@
|
||||||
|
module Program =
|
||||||
|
|
||||||
|
[<EntryPoint>]
|
||||||
|
let main _ = 0
|
|
@ -0,0 +1,249 @@
|
||||||
|
module FruityFoundation.Tests.DataAccess.Abstractions.FSharp.ReadOnlyDbTests
|
||||||
|
|
||||||
|
open System.Collections.Generic
|
||||||
|
open System.Threading
|
||||||
|
open FSharp.Control
|
||||||
|
open FakeItEasy
|
||||||
|
open FruityFoundation.DataAccess.Abstractions
|
||||||
|
open FruityFoundation.DataAccess.Abstractions.FSharp
|
||||||
|
open NUnit.Framework
|
||||||
|
|
||||||
|
let fakeDbConnection : IDatabaseConnection<ReadOnly> = A.Fake<IDatabaseConnection<ReadOnly>> ()
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_Query_Calls_IDatabaseConnection_Query_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.query fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.Query(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_Query_Calls_IDatabaseConnection_Query_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.query fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.Query(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_QueryUnbuffered_Calls_IDatabaseConnection_Query_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms)
|
||||||
|
||> ReadOnlyDb.queryUnbuffered fakeDbConnection CancellationToken.None
|
||||||
|
|> TaskSeq.toArrayAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.QueryUnbuffered(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_QueryUnbuffered_Calls_IDatabaseConnection_Query_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms)
|
||||||
|
||> ReadOnlyDb.queryUnbuffered fakeDbConnection CancellationToken.None
|
||||||
|
|> TaskSeq.toArrayAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.QueryUnbuffered(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_QuerySingle_Calls_IDatabaseConnection_QuerySingle_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.querySingle fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.QuerySingle(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_QuerySingle_Calls_IDatabaseConnection_QuerySingle_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.querySingle fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.QuerySingle(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_Execute_Calls_IDatabaseConnection_Execute_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.execute fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.Execute(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_Execute_Calls_IDatabaseConnection_Execute_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.execute fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.Execute(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_ExecuteScalar_Calls_IDatabaseConnection_ExecuteScalar_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.executeScalar fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.ExecuteScalar(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_ExecuteScalar_Calls_IDatabaseConnection_ExecuteScalar_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.executeScalar fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.ExecuteScalar(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_ExecuteReader_Calls_IDatabaseConnection_ExecuteReader_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.executeReader fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.ExecuteReader(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_ExecuteReader_Calls_IDatabaseConnection_ExecuteReader_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadOnlyDb.executeReader fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.ExecuteReader(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
|
@ -0,0 +1,249 @@
|
||||||
|
module FruityFoundation.Tests.DataAccess.Abstractions.FSharp.ReadWriteDbTests
|
||||||
|
|
||||||
|
open System.Collections.Generic
|
||||||
|
open System.Threading
|
||||||
|
open FSharp.Control
|
||||||
|
open FakeItEasy
|
||||||
|
open FruityFoundation.DataAccess.Abstractions
|
||||||
|
open FruityFoundation.DataAccess.Abstractions.FSharp
|
||||||
|
open NUnit.Framework
|
||||||
|
|
||||||
|
let fakeDbConnection : IDatabaseConnection<ReadWrite> = A.Fake<IDatabaseConnection<ReadWrite>> ()
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_Query_Calls_IDatabaseConnection_Query_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.query fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.Query(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_Query_Calls_IDatabaseConnection_Query_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.query fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.Query(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_QueryUnbuffered_Calls_IDatabaseConnection_Query_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms)
|
||||||
|
||> ReadWriteDb.queryUnbuffered fakeDbConnection CancellationToken.None
|
||||||
|
|> TaskSeq.toArrayAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.QueryUnbuffered(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_QueryUnbuffered_Calls_IDatabaseConnection_Query_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms)
|
||||||
|
||> ReadWriteDb.queryUnbuffered fakeDbConnection CancellationToken.None
|
||||||
|
|> TaskSeq.toArrayAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.QueryUnbuffered(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_QuerySingle_Calls_IDatabaseConnection_QuerySingle_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.querySingle fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.QuerySingle(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_QuerySingle_Calls_IDatabaseConnection_QuerySingle_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.querySingle fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.QuerySingle(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_Execute_Calls_IDatabaseConnection_Execute_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.execute fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.Execute(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_Execute_Calls_IDatabaseConnection_Execute_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.execute fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.Execute(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_ExecuteScalar_Calls_IDatabaseConnection_ExecuteScalar_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.executeScalar fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.ExecuteScalar(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_ExecuteScalar_Calls_IDatabaseConnection_ExecuteScalar_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.executeScalar fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.ExecuteScalar(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_ExecuteReader_Calls_IDatabaseConnection_ExecuteReader_NoParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = Array.empty
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! _ = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.executeReader fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.ExecuteReader(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(Seq.isEmpty),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
[<Test>]
|
||||||
|
let Db_ExecuteReader_Calls_IDatabaseConnection_ExecuteReader_WithParms () = task {
|
||||||
|
// Arrange
|
||||||
|
let sql = "SELECT 1 FROM table"
|
||||||
|
let parms = [| ("@id", box 1) |]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let! result = task {
|
||||||
|
return! (sql, parms) ||> ReadWriteDb.executeReader fakeDbConnection CancellationToken.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
A.CallTo(fun () -> fakeDbConnection.ExecuteReader(
|
||||||
|
"SELECT 1 FROM table",
|
||||||
|
A<IEnumerable<KeyValuePair<string, obj>>>.That.Matches(fun x ->
|
||||||
|
Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
|
||||||
|
CancellationToken.None)).MustHaveHappenedOnceExactly ()
|
||||||
|
|> ignore
|
||||||
|
}
|
|
@ -20,6 +20,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruityFoundation.DataAccess
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruityFoundation.DataAccess.Core", "FruityFoundation.DataAccess.Core\FruityFoundation.DataAccess.Core.csproj", "{B65527CC-218A-4EA3-93DC-985713B5DFF4}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruityFoundation.DataAccess.Core", "FruityFoundation.DataAccess.Core\FruityFoundation.DataAccess.Core.csproj", "{B65527CC-218A-4EA3-93DC-985713B5DFF4}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FruityFoundation.DataAccess.Abstractions.FSharp", "FruityFoundation.DataAccess.Abstractions.FSharp\FruityFoundation.DataAccess.Abstractions.FSharp.fsproj", "{B2E0156B-B631-4C80-A129-59472D2D0A77}"
|
||||||
|
EndProject
|
||||||
|
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FruityFoundation.Tests.DataAccess.Abstractions.FSharp", "FruityFoundation.Tests.DataAccess.Abstractions.FSharp\FruityFoundation.Tests.DataAccess.Abstractions.FSharp.fsproj", "{27F4FB64-7A51-4315-BDAA-6EE07736C976}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -58,6 +62,14 @@ Global
|
||||||
{B65527CC-218A-4EA3-93DC-985713B5DFF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{B65527CC-218A-4EA3-93DC-985713B5DFF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{B65527CC-218A-4EA3-93DC-985713B5DFF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{B65527CC-218A-4EA3-93DC-985713B5DFF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{B65527CC-218A-4EA3-93DC-985713B5DFF4}.Release|Any CPU.Build.0 = Release|Any CPU
|
{B65527CC-218A-4EA3-93DC-985713B5DFF4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B2E0156B-B631-4C80-A129-59472D2D0A77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B2E0156B-B631-4C80-A129-59472D2D0A77}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B2E0156B-B631-4C80-A129-59472D2D0A77}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B2E0156B-B631-4C80-A129-59472D2D0A77}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{27F4FB64-7A51-4315-BDAA-6EE07736C976}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{27F4FB64-7A51-4315-BDAA-6EE07736C976}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{27F4FB64-7A51-4315-BDAA-6EE07736C976}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{27F4FB64-7A51-4315-BDAA-6EE07736C976}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{50A75644-A1C3-4495-9DEB-DBB12D9334B5} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
|
{50A75644-A1C3-4495-9DEB-DBB12D9334B5} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
|
||||||
|
@ -65,5 +77,7 @@ Global
|
||||||
{A64E73D3-EF87-4938-B01E-F9CC0B59F9DE} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
|
{A64E73D3-EF87-4938-B01E-F9CC0B59F9DE} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
|
||||||
{C003E247-C62E-4830-94E4-F274D8466A5C} = {5C3A014A-7931-4A36-95F0-5EFE15AB06A3}
|
{C003E247-C62E-4830-94E4-F274D8466A5C} = {5C3A014A-7931-4A36-95F0-5EFE15AB06A3}
|
||||||
{B65527CC-218A-4EA3-93DC-985713B5DFF4} = {5C3A014A-7931-4A36-95F0-5EFE15AB06A3}
|
{B65527CC-218A-4EA3-93DC-985713B5DFF4} = {5C3A014A-7931-4A36-95F0-5EFE15AB06A3}
|
||||||
|
{B2E0156B-B631-4C80-A129-59472D2D0A77} = {5C3A014A-7931-4A36-95F0-5EFE15AB06A3}
|
||||||
|
{27F4FB64-7A51-4315-BDAA-6EE07736C976} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
Loading…
Reference in New Issue
Block a user