Skip to content

Commit c0de3af

Browse files
issue 196
1 parent f1019cb commit c0de3af

File tree

5 files changed

+89
-81
lines changed

5 files changed

+89
-81
lines changed

src/SqlClient.Tests/ConfigurationTest.fs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,6 @@ open FSharp.Data
77

88
let adventureWorks = FSharp.Configuration.AppSettings<"app.config">.ConnectionStrings.AdventureWorks
99

10-
[<Fact>]
11-
let CheckValidFileName() =
12-
let expected = Some "c:\\mysqlfiles\\test.sql"
13-
Assert.Equal(expected, Configuration.GetValidFileName("test.sql", "c:\\mysqlfiles"))
14-
15-
Assert.Equal(expected, Configuration.GetValidFileName("test.sql", "c:\\mysqlfiles"))
16-
Assert.Equal(expected, Configuration.GetValidFileName("../test.sql", "c:\\mysqlfiles\\subfolder"))
17-
Assert.Equal(expected, Configuration.GetValidFileName("c:\\mysqlfiles/test.sql", "d:\\otherdrive"))
18-
Assert.Equal(expected, Configuration.GetValidFileName("../mysqlfiles/test.sql", "c:\\otherfolder"))
19-
Assert.Equal(expected, Configuration.GetValidFileName("a/b/c/../../../test.sql", "c:\\mysqlfiles"))
20-
2110
type Get42RelativePath = SqlCommandProvider<"sampleCommand.sql", ConnectionStrings.AdventureWorksNamed, ResolutionFolder="MySqlFolder">
2211

2312
type Get42 = SqlCommandProvider<"SELECT 42", ConnectionStrings.AdventureWorksNamed, ConfigFile = "appWithInclude.config">
@@ -28,6 +17,17 @@ type LongQuery = SqlCommandProvider<"
2817
", ConnectionStrings.AdventureWorksNamed>
2918

3019
#if DEBUG
20+
[<Fact>]
21+
let CheckValidFileName() =
22+
let expected = Some "c:\\mysqlfiles\\test.sql"
23+
Assert.Equal(expected, SqlCommandProvider.GetValidFileName("test.sql", "c:\\mysqlfiles"))
24+
25+
Assert.Equal(expected, SqlCommandProvider.GetValidFileName("test.sql", "c:\\mysqlfiles"))
26+
Assert.Equal(expected, SqlCommandProvider.GetValidFileName("../test.sql", "c:\\mysqlfiles\\subfolder"))
27+
Assert.Equal(expected, SqlCommandProvider.GetValidFileName("c:\\mysqlfiles/test.sql", "d:\\otherdrive"))
28+
Assert.Equal(expected, SqlCommandProvider.GetValidFileName("../mysqlfiles/test.sql", "c:\\otherfolder"))
29+
Assert.Equal(expected, SqlCommandProvider.GetValidFileName("a/b/c/../../../test.sql", "c:\\mysqlfiles"))
30+
3131
[<Fact>]
3232
let ``Wrong config file name`` () =
3333
Assert.Throws<FileNotFoundException>(

src/SqlClient.Tests/TypeProviderTest.fs

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,7 @@ let ResultsetExtendedWithTrailingColumn() =
220220
"
221221
Assert.Equal<_ list>([0..9], [ for x in cmd.Execute() -> x.Value ])
222222

223-
let resultsetRuntimeVerificationEnabled =
224-
lazy
225-
match Configuration.ConfigurationManager.GetSection("FSharp.Data.SqlClient") with
226-
| :? System.Collections.Specialized.NameValueCollection as xs ->
227-
string xs.["ResultsetRuntimeVerification"] = "true"
228-
| _ -> false
223+
open FSharp.Data.SqlClient
229224

230225
[<Fact>]
231226
let ResultsetRuntimeVerificationLessThanExpectedColumns() =
@@ -253,19 +248,25 @@ let ResultsetRuntimeVerificationLessThanExpectedColumns() =
253248
)
254249
SELECT * FROM XS
255250
"
256-
if resultsetRuntimeVerificationEnabled.Value
257-
then
251+
252+
Assert.False(SqlClient.Configuration.Current.ResultsetRuntimeVerification)
253+
254+
try
255+
SqlClient.Configuration.Current <- { ResultsetRuntimeVerification = true }
258256
let err = Assert.Throws<InvalidOperationException>(fun() -> cmd.Execute() |> Seq.toArray |> ignore)
259257
Assert.Equal<string>(
260258
"Expected at least 3 columns in result set but received only 2.",
261259
err.Message
262260
)
263-
else
264-
let err = Assert.Throws<IndexOutOfRangeException>(fun() -> cmd.Execute() |> Seq.toArray |> ignore)
265-
Assert.Equal<string>(
266-
"Index was outside the bounds of the array.",
267-
err.Message
268-
)
261+
finally
262+
SqlClient.Configuration.Current <- { ResultsetRuntimeVerification = false}
263+
264+
let err = Assert.Throws<IndexOutOfRangeException>(fun() -> cmd.Execute() |> Seq.toArray |> ignore)
265+
Assert.Equal<string>(
266+
"Index was outside the bounds of the array.",
267+
err.Message
268+
)
269+
269270

270271
[<Fact>]
271272
let ResultsetRuntimeVerificationDiffColumnTypes() =
@@ -293,19 +294,24 @@ let ResultsetRuntimeVerificationDiffColumnTypes() =
293294
SELECT * FROM XS
294295
"
295296

296-
if resultsetRuntimeVerificationEnabled.Value
297-
then
297+
Assert.False(Configuration.Current.ResultsetRuntimeVerification)
298+
299+
try
300+
Configuration.Current <- { ResultsetRuntimeVerification = true }
301+
298302
let err = Assert.Throws<InvalidOperationException>(fun() -> cmd.Execute() |> Seq.toArray |> ignore)
299303
Assert.Equal<string>(
300304
"""Expected column [Total] of type "System.Int32" at position 1 (0-based indexing) but received column [Now] of type "System.DateTime".""",
301305
err.Message
302306
)
303-
else
304-
let err = Assert.Throws<InvalidCastException>(fun() -> cmd.Execute() |> Seq.toArray |> ignore)
305-
Assert.Equal<string>(
306-
"Specified cast is not valid.",
307-
err.Message
308-
)
307+
finally
308+
Configuration.Current <- { ResultsetRuntimeVerification = false}
309+
310+
let err = Assert.Throws<InvalidCastException>(fun() -> cmd.Execute() |> Seq.toArray |> ignore)
311+
Assert.Equal<string>(
312+
"Specified cast is not valid.",
313+
err.Message
314+
)
309315

310316
module ``The undeclared parameter 'X' is used more than once in the batch being analyzed`` =
311317
[<Fact>]

src/SqlClient/Configuration.fs

Lines changed: 17 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -79,42 +79,20 @@ type internal DesignTimeConnectionString =
7979
else section.ConnectionString
8080
@@>
8181

82-
[<CompilerMessageAttribute("This API supports the FSharp.Data.SqlClient infrastructure and is not intended to be used directly from your code.", 101, IsHidden = true)>]
83-
type Configuration() =
84-
static let invalidPathChars = HashSet(Path.GetInvalidPathChars())
85-
static let invalidFileChars = HashSet(Path.GetInvalidFileNameChars())
86-
87-
static member GetValidFileName (file:string, resolutionFolder:string) =
88-
try
89-
if (file.Contains "\n") || (resolutionFolder.Contains "\n") then None else
90-
let f = Path.Combine(resolutionFolder, file)
91-
if invalidPathChars.Overlaps (Path.GetDirectoryName f) ||
92-
invalidFileChars.Overlaps (Path.GetFileName f) then None
93-
else
94-
// Canonicalizing the path may throw on bad input, the check above does not cover every error.
95-
Some (Path.GetFullPath f)
96-
with _ ->
97-
None
98-
99-
static member ParseTextAtDesignTime(commandTextOrPath : string, resolutionFolder, invalidateCallback) =
100-
match Configuration.GetValidFileName (commandTextOrPath, resolutionFolder) with
101-
| Some path when File.Exists path ->
102-
if Path.GetExtension(path) <> ".sql" then failwith "Only files with .sql extension are supported"
103-
let watcher = new FileSystemWatcher(Filter = Path.GetFileName path, Path = Path.GetDirectoryName path)
104-
watcher.Changed.Add(fun _ -> invalidateCallback())
105-
watcher.Renamed.Add(fun _ -> invalidateCallback())
106-
watcher.Deleted.Add(fun _ -> invalidateCallback())
107-
watcher.EnableRaisingEvents <- true
108-
let task = Task.Factory.StartNew(fun () ->
109-
use stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
110-
use reader = new StreamReader(stream)
111-
reader.ReadToEnd())
112-
if not (task.Wait(TimeSpan.FromSeconds(1.))) then failwithf "Couldn't read command from file %s" path
113-
task.Result, Some watcher
114-
| _ -> commandTextOrPath, None
115-
116-
117-
118-
119-
120-
82+
//this is mess. Clean up later.
83+
type Configuration = {
84+
ResultsetRuntimeVerification: bool
85+
}
86+
87+
namespace FSharp.Data
88+
89+
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
90+
[<AutoOpen>]
91+
module Configuration =
92+
let private guard = obj()
93+
let private current = ref { SqlClient.Configuration.ResultsetRuntimeVerification = false }
94+
95+
type SqlClient.Configuration with
96+
static member Current
97+
with get() = lock guard <| fun() -> !current
98+
and set value = lock guard <| fun() -> current := value

src/SqlClient/ISqlCommand.fs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,6 @@ type ``ISqlCommand Implementation``(cfg: DesignTimeConfig, connection: Connectio
9090

9191
let notImplemented _ : _ = raise <| NotImplementedException()
9292

93-
static let resultsetRuntimeVerification =
94-
lazy
95-
match ConfigurationManager.GetSection("FSharp.Data.SqlClient") with
96-
| :? NameValueCollection as xs ->
97-
match xs.["ResultsetRuntimeVerification"] with | null -> false | s -> s.ToLower() = "true"
98-
| _ -> false
99-
10093
let execute, asyncExecute, executeSingle, asyncExecuteSingle =
10194
match cfg.ResultType with
10295
| ResultType.DataReader ->
@@ -201,7 +194,7 @@ type ``ISqlCommand Implementation``(cfg: DesignTimeConfig, connection: Connectio
201194
//Execute/AsyncExecute versions
202195

203196
static member internal VerifyResultsetColumns(cursor: SqlDataReader, expected) =
204-
if resultsetRuntimeVerification.Value
197+
if Configuration.Current.ResultsetRuntimeVerification
205198
then
206199
if cursor.FieldCount < Array.length expected
207200
then

src/SqlClient/SqlCommandProvider.fs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ do()
2929
type public SqlCommandProvider(config : TypeProviderConfig) as this =
3030
inherit TypeProviderForNamespaces()
3131

32+
static let invalidPathChars = HashSet(Path.GetInvalidPathChars())
33+
static let invalidFileChars = HashSet(Path.GetInvalidFileNameChars())
34+
3235
let mutable watcher = null : IDisposable
3336

3437
let nameSpace = this.GetType().Namespace
@@ -102,7 +105,7 @@ type public SqlCommandProvider(config : TypeProviderConfig) as this =
102105
then resolutionFolder
103106
else Path.Combine (config.ResolutionFolder, resolutionFolder)
104107

105-
Configuration.ParseTextAtDesignTime(sqlStatementOrFile, sqlScriptResolutionFolder, invalidator)
108+
SqlCommandProvider.ParseTextAtDesignTime(sqlStatementOrFile, sqlScriptResolutionFolder, invalidator)
106109

107110
watcher' |> Option.iter (fun x -> watcher <- x)
108111

@@ -187,3 +190,31 @@ type public SqlCommandProvider(config : TypeProviderConfig) as this =
187190

188191
cmdProvidedType
189192

193+
static member internal GetValidFileName (file:string, resolutionFolder:string) =
194+
try
195+
if (file.Contains "\n") || (resolutionFolder.Contains "\n") then None else
196+
let f = Path.Combine(resolutionFolder, file)
197+
if invalidPathChars.Overlaps (Path.GetDirectoryName f) ||
198+
invalidFileChars.Overlaps (Path.GetFileName f) then None
199+
else
200+
// Canonicalizing the path may throw on bad input, the check above does not cover every error.
201+
Some (Path.GetFullPath f)
202+
with _ ->
203+
None
204+
205+
static member ParseTextAtDesignTime(commandTextOrPath : string, resolutionFolder, invalidateCallback) =
206+
match SqlCommandProvider.GetValidFileName (commandTextOrPath, resolutionFolder) with
207+
| Some path when File.Exists path ->
208+
if Path.GetExtension(path) <> ".sql" then failwith "Only files with .sql extension are supported"
209+
let watcher = new FileSystemWatcher(Filter = Path.GetFileName path, Path = Path.GetDirectoryName path)
210+
watcher.Changed.Add(fun _ -> invalidateCallback())
211+
watcher.Renamed.Add(fun _ -> invalidateCallback())
212+
watcher.Deleted.Add(fun _ -> invalidateCallback())
213+
watcher.EnableRaisingEvents <- true
214+
let task = System.Threading.Tasks.Task.Factory.StartNew(fun () ->
215+
use stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
216+
use reader = new StreamReader(stream)
217+
reader.ReadToEnd())
218+
if not (task.Wait(TimeSpan.FromSeconds(1.))) then failwithf "Couldn't read command from file %s" path
219+
task.Result, Some watcher
220+
| _ -> commandTextOrPath, None

0 commit comments

Comments
 (0)