Skip to content

Commit 6511d5c

Browse files
authored
Merge pull request #878 from Thorium/fixed-issue
Fix for #877 IndexOutOfRangeException
2 parents 99aba3b + f7662cd commit 6511d5c

8 files changed

Lines changed: 89 additions & 7 deletions

File tree

‎Directory.Build.props‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22
<PropertyGroup>
33
<!-- summary is not migrated from project.json, but you can use the <Description> property for that if needed. -->
4-
<PackageTags>f#, fsharp</PackageTags>
4+
<PackageTags>sql, database, typeprovider, f#, fsharp, dotnet, orm, linq</PackageTags>
55
<PackageProjectUrl>https://github.com/fsprojects/SQLProvider</PackageProjectUrl>
66
<PackageLicenseUrl>https://github.com/fsprojects/SQLProvider/blob/master/LICENSE.txt</PackageLicenseUrl>
77
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>

‎paket.dependencies‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ nuget Microsoft.Extensions.Logging.Abstractions 8.0.3
2626
group Build
2727

2828
source https://api.nuget.org/v3/index.json
29-
storage: none
29+
// build.fsx references packages/build/* directly, so this group needs the packages folder
30+
storage: packages
3031
framework: net6.0
3132

3233
nuget Microsoft.SourceLink.GitHub prerelease copy_local: true

‎src/SQLProvider.Common/SqlSchema.fs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ type QueryParameter =
5858
Direction = defaultArg direction ParameterDirection.Input
5959
Length = match length with Some x -> ValueSome x | None -> ValueNone }
6060

61+
/// Helpers for stored procedure parameter lists.
62+
module SprocParameters =
63+
/// Drops repeated named parameters (same name and direction), keeping the first occurrence.
64+
/// Unnamed parameters (e.g. a function's return value) are always kept.
65+
let distinctNamed (parameters: QueryParameter list) =
66+
let seen = System.Collections.Generic.HashSet<struct(string * ParameterDirection)>()
67+
parameters |> List.filter (fun p -> String.IsNullOrEmpty p.Name || seen.Add(struct(p.Name, p.Direction)))
68+
6169
/// Represents a database table column with its metadata and characteristics.
6270
/// Contains all information needed to generate typed properties and handle data conversion.
6371
[<Struct>]

‎src/SQLProvider.DesignTime/SqlDesignTime.fs‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,13 @@ module DesignTimeUtils =
124124
match con with
125125
| Some con ->
126126
let returnParams = sprocDefinition.ReturnColumns con param
127-
prov.GetSchemaCache().SprocsParams.AddOrUpdate(sprocname, returnParams, fun _ inputParams -> inputParams @ returnParams) |> ignore
127+
// InputOutput parameters are also return columns, so don't store them twice
128+
prov.GetSchemaCache().SprocsParams.AddOrUpdate(sprocname, returnParams, fun _ inputParams -> SprocParameters.distinctNamed (inputParams @ returnParams)) |> ignore
128129
returnParams
129130
| None ->
130131
let ok, pars = prov.GetSchemaCache().SprocsParams.TryGetValue sprocname
131132
if ok then
132-
pars |> List.filter (fun p ->
133+
pars |> SprocParameters.distinctNamed |> List.filter (fun p ->
133134
not (p.Name |> String.IsNullOrWhiteSpace)
134135
&& (p.Direction = ParameterDirection.Output
135136
|| p.Direction = ParameterDirection.InputOutput
@@ -323,7 +324,8 @@ module DesignTimeUtils =
323324
match con with
324325
| None ->
325326
match prov.GetSchemaCache().SprocsParams.TryGetValue sprocname with
326-
| true, x -> x
327+
// Schema files saved before de-duplication may list InputOutput parameters twice
328+
| true, x -> SprocParameters.distinctNamed x
327329
| false, _ -> []
328330
| Some con ->
329331
(lazy

‎src/SQLProvider.Runtime/Providers.MsSqlServer.Dynamic.fs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,9 @@ module MSSqlServerDynamic =
365365

366366
let inps =
367367
inputParameters
368-
|> Array.filter (fun p -> p.Direction <> ParameterDirection.ReturnValue)
368+
// Values are supplied only for Input/InputOutput parameters (the generated Invoke signature);
369+
// an offline ContextSchema can also list result-set and return-value columns here.
370+
|> Array.filter (fun p -> p.Direction = ParameterDirection.Input || p.Direction = ParameterDirection.InputOutput)
369371
|> Array.mapi(fun i ip ->
370372
let p = createCommandParameter ip values.[i]
371373
(ip.Ordinal, ip.Name, p))

‎src/SQLProvider.Runtime/Providers.MsSqlServer.fs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,9 @@ module MSSqlServer =
288288

289289
let inps =
290290
inputParameters
291-
|> Array.filter (fun p -> p.Direction <> ParameterDirection.ReturnValue)
291+
// Values are supplied only for Input/InputOutput parameters (the generated Invoke signature);
292+
// an offline ContextSchema can also list result-set and return-value columns here.
293+
|> Array.filter (fun p -> p.Direction = ParameterDirection.Input || p.Direction = ParameterDirection.InputOutput)
292294
|> Array.mapi(fun i ip ->
293295
let p = createCommandParameter ip values.[i]
294296
(ip.Ordinal, ip.Name, p))
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
module SprocParameterTests
2+
3+
// Issue #877: stored procedures compiled against an offline ContextSchema
4+
// have result-set / return-value columns in their parameter list.
5+
6+
open System.Data
7+
open FSharp.Data.Sql.Schema
8+
open FSharp.Data.Sql.Providers
9+
open NUnit.Framework
10+
11+
let private intType = TypeMapping.Create(typeof<int>.ToString(), DbType.Int32)
12+
let private cursorType = TypeMapping.Create(typeof<obj>.ToString(), DbType.Object, "cursor")
13+
14+
let private param name ordinal typeMapping direction =
15+
QueryParameter.Create(name, ordinal, typeMapping, direction)
16+
17+
[<Test>]
18+
let ``MSSQL sproc call ignores result set columns from an offline schema``() =
19+
let resultSet = param "ResultSet" 0 cursorType ParameterDirection.Output
20+
// Offline the cached parameter list has the result set appended to the real parameters
21+
let inputParameters =
22+
[| param "@a" 1 intType ParameterDirection.Input
23+
param "@b" 2 intType ParameterDirection.InputOutput
24+
resultSet |]
25+
let values = [| box 1; box 2 |]
26+
27+
let allParams, outps = MSSqlServer.executeSprocCommandCommon inputParameters [| resultSet |] values
28+
29+
let names = allParams |> Array.map (fun (_,name,_) -> name)
30+
Assert.AreEqual([| "@a"; "@b" |], names)
31+
let values = allParams |> Array.map (fun (_,_,p) -> p.Value)
32+
Assert.AreEqual([| box 1; box 2 |], values)
33+
Assert.AreEqual(1, outps.Length)
34+
35+
[<Test>]
36+
let ``MSSQL function call passes the return value parameter``() =
37+
let returnValue = param "ReturnValue" 0 intType ParameterDirection.ReturnValue
38+
let inputParameters =
39+
[| param "" 0 intType ParameterDirection.ReturnValue
40+
param "@a" 1 intType ParameterDirection.Input
41+
returnValue |]
42+
43+
let allParams, _ = MSSqlServer.executeSprocCommandCommon inputParameters [| returnValue |] [| box 5 |]
44+
45+
let names = allParams |> Array.map (fun (_,_,p) -> (p :?> IDataParameter).ParameterName)
46+
Assert.AreEqual([| "@RETURN_VALUE"; "@a" |], names)
47+
48+
[<Test>]
49+
let ``distinctNamed drops repeated named parameters only``() =
50+
let parameters =
51+
[ param "" 0 intType ParameterDirection.ReturnValue
52+
param "@a" 1 intType ParameterDirection.Input
53+
param "@b" 2 intType ParameterDirection.InputOutput
54+
param "ResultSet" 0 cursorType ParameterDirection.Output
55+
param "ReturnValue" 0 intType ParameterDirection.ReturnValue
56+
param "@b" 2 intType ParameterDirection.InputOutput ]
57+
58+
let result = SprocParameters.distinctNamed parameters
59+
60+
Assert.AreEqual(
61+
[ "", ParameterDirection.ReturnValue
62+
"@a", ParameterDirection.Input
63+
"@b", ParameterDirection.InputOutput
64+
"ResultSet", ParameterDirection.Output
65+
"ReturnValue", ParameterDirection.ReturnValue ],
66+
result |> List.map (fun p -> p.Name, p.Direction))

‎tests/SqlProvider.Tests/SqlProvider.Tests.fsproj‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<Compile Include="more\ValueOptionTests.fs" />
3333
<Compile Include="QueryTests.fs" />
3434
<Compile Include="CrudTests.fs" />
35+
<Compile Include="SprocParameterTests.fs" />
3536
<!--<Compile Include="PostgreSQLTests.fs" />-->
3637
<!-- MSSQL tests need a live SQL Server at design time; only built for net48. -->
3738
<Compile Include="SqlServerTests.fs" Condition="'$(TargetFramework)' == 'net48'" />

0 commit comments

Comments
 (0)