Skip to content

Commit

Permalink
feat(seq): fix synchronous implementation dispose issues
Browse files Browse the repository at this point in the history
  • Loading branch information
Natalie Perret committed Nov 7, 2021
1 parent d36c390 commit f22a25d
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 116 deletions.
232 changes: 148 additions & 84 deletions Vp.FSharp.Sql.Tests/Mocks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,60 @@ type DbField' =

type Data =
{ Columns: DbField' list list
GetValues: int32 -> int32 -> Object list
GetValues: int32 -> int32 -> obj list
CountRows: int32 -> int32
CountResultSets: int32 }

type Response =
| Reader of (CommandBehavior -> DbDataReader)
| NonQuery of int32

type IIsDisposed =
inherit IDisposable
abstract member IsDisposed : bool with get

[<AbstractClass>]
type IsDisposedDbConnection() =

inherit DbConnection()
let mutable isDisposed = false
interface IIsDisposed with member this.IsDisposed = isDisposed
member this.CheckDisposed(value) =
if isDisposed then raise (ObjectDisposedException("Connection already disposed"))
else value
override this.Dispose(disposing) =
this.CheckDisposed()
base.Dispose(disposing)
isDisposed <- true

[<AbstractClass>]
type IsDisposedDbCommand() =
inherit DbCommand()

let mutable isDisposed = false
interface IIsDisposed with member this.IsDisposed = isDisposed
member this.CheckDisposed(value) =
if isDisposed then raise (ObjectDisposedException("Command already disposed"))
else value
override this.Dispose(disposing) =
this.CheckDisposed()
base.Dispose(disposing)
isDisposed <- true

[<AbstractClass>]
type IsDisposedDbDataReader() =
inherit DbDataReader()

let mutable isDisposed = false
interface IIsDisposed with member this.IsDisposed = isDisposed
member this.CheckDisposed(value) =
if isDisposed then raise (ObjectDisposedException("Data Reader already disposed"))
else value
override this.Dispose(disposing) =
this.CheckDisposed()
base.Dispose(disposing)
isDisposed <- true

let fakeData values columns =
{ Columns = columns
GetValues =
Expand Down Expand Up @@ -60,137 +106,155 @@ let makeConf logger =
let makeReader data _ =
let mutable currentRowIndex = -1
let mutable currentResultSetIndex = 0
{ new DbDataReader()
{ new IsDisposedDbDataReader()
with
member this.Depth with get() = 0
member this.FieldCount with get() =
member this.Depth with get () = this.CheckDisposed(0)
member this.FieldCount with get () =
List.length data.Columns.[currentResultSetIndex]
member this.HasRows with get() =
|> this.CheckDisposed
member this.HasRows with get () =
data.CountRows currentResultSetIndex > 0
member this.IsClosed with get() = true
member this.RecordsAffected with get() = 0
member this.Item
with get(ordinal: int32):Object = null
member this.Item
with get(name: string):Object = null
|> this.CheckDisposed
member this.IsClosed with get () = this.CheckDisposed(true)
member this.RecordsAffected with get () = this.CheckDisposed(0)
member this.Item with get (ordinal: int32): obj = this.CheckDisposed(null)
member this.Item with get (name: string): obj = this.CheckDisposed(null)
member this.GetDataTypeName (ordinal: int32) =
data.Columns.[currentResultSetIndex].[ordinal].NativeTypeName
member this.GetEnumerator () = null
|> this.CheckDisposed
member this.GetEnumerator () = this.CheckDisposed(null)
member this.GetFieldType (ordinal: int32) =
data.Columns.[currentResultSetIndex].[ordinal].FieldType
|> this.CheckDisposed
member this.GetName (ordinal: int32) =
data.Columns.[currentResultSetIndex].[ordinal].Name
member this.GetOrdinal (name: string) = 0
member this.GetBoolean (ordinal: int32) = true
member this.GetByte (ordinal: int32) = 0uy
member this.GetBytes (ordinal, dataOffset, buffer, bufferOffset, length) = 0L
member this.GetChar (ordinal: int32) = 'a'
member this.GetChars (ordinal, dataOffset, buffer, bufferOffset, length) = 0L
member this.GetDateTime (ordinal: int32) = DateTime.Today
member this.GetDecimal (ordinal: int32) = 0M
member this.GetDouble (ordinal: int32) = 0.0
member this.GetFloat (ordinal: int32) = 0F
member this.GetGuid (ordinal: int32) = Guid.Empty
member this.GetInt16 (ordinal: int32) = 0s
member this.GetInt32 (ordinal: int32) = 0
member this.GetInt64 (ordinal: int32) = 0L
member this.GetString (ordinal: int32) = ""
|> this.CheckDisposed
member this.GetOrdinal (name: string) = this.CheckDisposed(0)
member this.GetBoolean (ordinal: int32) = this.CheckDisposed(true)
member this.GetByte (ordinal: int32) = this.CheckDisposed(0uy)
member this.GetBytes (ordinal, dataOffset, buffer, bufferOffset, length) = this.CheckDisposed(0L)
member this.GetChar (ordinal: int32) = this.CheckDisposed('a')
member this.GetChars (ordinal, dataOffset, buffer, bufferOffset, length) = this.CheckDisposed(0L)
member this.GetDateTime (ordinal: int32) = this.CheckDisposed(DateTime.Today)
member this.GetDecimal (ordinal: int32) = this.CheckDisposed(0M)
member this.GetDouble (ordinal: int32) = this.CheckDisposed(0.0)
member this.GetFloat (ordinal: int32) = this.CheckDisposed(0F)
member this.GetGuid (ordinal: int32) = this.CheckDisposed(Guid.Empty)
member this.GetInt16 (ordinal: int32) = this.CheckDisposed(0s)
member this.GetInt32 (ordinal: int32) = this.CheckDisposed(0)
member this.GetInt64 (ordinal: int32) = this.CheckDisposed(0L)
member this.GetString (ordinal: int32) = this.CheckDisposed("")
member this.GetValue (ordinal: int32) =
(data.GetValues currentResultSetIndex currentRowIndex).[ordinal]
member this.GetValues values = 0
|> this.CheckDisposed
member this.GetValues values = this.CheckDisposed(0)
member this.IsDBNull (ordinal: int32) =
(data.GetValues currentResultSetIndex currentRowIndex).[ordinal] = null
|> this.CheckDisposed
member this.NextResult () =
currentResultSetIndex <- currentResultSetIndex + 1
currentRowIndex <- -1
currentResultSetIndex < data.CountResultSets
|> this.CheckDisposed
member this.Read () =
currentRowIndex <- currentRowIndex + 1
currentRowIndex < data.CountRows currentResultSetIndex
}
|> this.CheckDisposed
} :> DbDataReader

let makeCommand connection response =
let mutable connection = connection
let mutable cmdTxt = ""
{ new DbCommand()
{ new IsDisposedDbCommand()
with
member this.CommandText
with get() = cmdTxt
and set v = cmdTxt <- v
with get () = this.CheckDisposed(cmdTxt)
and set v = this.CheckDisposed(cmdTxt <- v)
member this.CommandTimeout
with get() = 0
and set v = ()
with get () = this.CheckDisposed(0)
and set v = this.CheckDisposed()
member this.CommandType
with get() = CommandType.Text
and set v = ()
with get () = this.CheckDisposed(CommandType.Text)
and set v = ()
member this.DbConnection
with get() = connection
and set v = connection <- v
with get () = this.CheckDisposed(connection)
and set v = this.CheckDisposed(connection <- v)
member this.DbParameterCollection
with get() = null
with get() = this.CheckDisposed(null)
member this.DbTransaction
with get() = null
and set v = ()
with get () = this.CheckDisposed(null)
and set v = this.CheckDisposed()
member this.DesignTimeVisible
with get() = false
and set v = ()
with get () = this.CheckDisposed(false)
and set v = this.CheckDisposed()
member this.UpdatedRowSource
with get() = UpdateRowSource()
and set v = ()
member this.Cancel () = ()
member this.CreateDbParameter () = null
with get () = this.CheckDisposed(UpdateRowSource())
and set v = this.CheckDisposed()
member this.Cancel () = this.CheckDisposed()
member this.CreateDbParameter () = this.CheckDisposed(null)
member this.ExecuteDbDataReader commandBehavior =
match response with
| Reader callBackReader -> callBackReader commandBehavior
| _ -> failwith "ExecuteDbDataReader"
|> this.CheckDisposed
member this.ExecuteNonQuery () =
match response with
| NonQuery response -> response
| _ -> failwith "ExecuteNonQuery"
member this.ExecuteScalar () = null
member this.Prepare () = ()
}
|> this.CheckDisposed
member this.ExecuteScalar () = this.CheckDisposed(null)
member this.Prepare () = this.CheckDisposed()
} :> DbCommand

let makeConnectionReader cs state openCallback closeCallback response =
let mutable connectionString = cs
{ new DbConnection()
{ new IsDisposedDbConnection()
with
member this.ConnectionString
with get() = connectionString
and set v = connectionString <- v
member this.Database
with get() = ""
member this.DataSource
with get() = ""
member this.ServerVersion
with get() = ""
member this.State
with get() = state
member this.Close () = closeCallback ()
member this.ChangeDatabase dbName = ()
member this.Open() = openCallback ()
member this.BeginDbTransaction isolationLevel = failwith "BeginDbTransaction not called"
member this.CreateDbCommand () = makeCommand this response
}
with get () = this.CheckDisposed(connectionString)
and set v = this.CheckDisposed(connectionString <- v)
member this.Database with get () = this.CheckDisposed("")
member this.DataSource with get () = this.CheckDisposed("")
member this.ServerVersion with get () = this.CheckDisposed("")
member this.State with get () = this.CheckDisposed(state)
member this.Close () =
closeCallback ()
|> this.CheckDisposed
member this.ChangeDatabase dbName = this.CheckDisposed()
member this.Open() =
openCallback ()
|> this.CheckDisposed
member this.BeginDbTransaction isolationLevel =
failwith "BeginDbTransaction not called"
|> this.CheckDisposed

member this.CreateDbCommand () =
makeCommand this response
|> this.CheckDisposed
} :> DbConnection

let makeConnection cs state openCallback closeCallback response =
let mutable connectionString = cs
{ new DbConnection()
{ new IsDisposedDbConnection()
with
member this.ConnectionString
with get() = connectionString
and set v = connectionString <- v
member this.Database
with get() = ""
member this.DataSource
with get() = ""
member this.ServerVersion
with get() = ""
member this.State
with get() = state
member this.Close () = closeCallback ()
member this.ChangeDatabase dbName = ()
member this.Open() = openCallback ()
member this.BeginDbTransaction isolationLevel = failwith "BeginDbTransaction not called"
member this.CreateDbCommand () = makeCommand this response
}
with get () = this.CheckDisposed(connectionString)
and set v = this.CheckDisposed(connectionString <- v)
member this.Database with get () = this.CheckDisposed("")
member this.DataSource with get () = this.CheckDisposed("")
member this.ServerVersion with get () = this.CheckDisposed("")
member this.State with get () = this.CheckDisposed(state)
member this.Close () =
closeCallback ()
|> this.CheckDisposed
member this.ChangeDatabase dbName = this.CheckDisposed()
member this.Open() =
openCallback ()
|> this.CheckDisposed
member this.BeginDbTransaction isolationLevel =
failwith "BeginDbTransaction not called"
|> this.CheckDisposed
member this.CreateDbCommand () =
makeCommand this response
|> this.CheckDisposed
} :> DbConnection
8 changes: 3 additions & 5 deletions Vp.FSharp.Sql.Tests/SqlCommand for querySeqSync should.fs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ open Vp.FSharp.Sql
open Vp.FSharp.Sql.Tests.Helpers


let toFieldName =
sprintf "id%i"
let toFieldName = sprintf "id%i"
let toFieldNames = List.map toFieldName

let toFieldNames =
List.map toFieldName

let readValueByFieldName (columns: string list) _ _ (reader: SqlRecordReader<DbDataReader>) =
columns
Expand Down Expand Up @@ -276,7 +274,7 @@ let ``log for just command events on globalLogger if connection initially not cl
let data = Mocks.fakeData
[[
[1;null;3]
[4;5;6]
[4;5 ;6]
]]
[[
{ Name = "id0"
Expand Down
56 changes: 29 additions & 27 deletions Vp.FSharp.Sql/SqlCommand.fs
Original file line number Diff line number Diff line change
Expand Up @@ -212,36 +212,38 @@ module Vp.FSharp.Sql.SqlCommand
/// This function runs synchronously.
let querySeqSync (connection: #DbConnection) deps conf
(read: Read<_, _>) commandDefinition =
let wasClosed = connection.State = ConnectionState.Closed
let log sqlLog = log4 conf commandDefinition sqlLog
let connectionStopwatch = Stopwatch()
let commandStopwatch = Stopwatch()
use command = setupCommandSync deps commandDefinition connection
seq {
let wasClosed = connection.State = ConnectionState.Closed
let log sqlLog = log4 conf commandDefinition sqlLog
let connectionStopwatch = Stopwatch()
let commandStopwatch = Stopwatch()
use command = setupCommandSync deps commandDefinition connection

try
if wasClosed then
setupConnectionSync connection
connectionStopwatch.Start()
ConnectionOpened connection |> log
try
if wasClosed then
setupConnectionSync connection
connectionStopwatch.Start()
ConnectionOpened connection |> log

CommandPrepared command |> log
commandStopwatch.Start ()
use dbDataReader = deps.ExecuteReader command
let items =
Seq.initInfinite(fun _ -> dbDataReader)
|> SkipFirstSeq.scan(readNextRecordSync) { Continue = true; SetIndex = 0; RecordIndex = -1 }
|> Seq.takeWhile(fun state -> state.Continue)
|> Seq.mapChange(fun state -> state.SetIndex) (fun _ -> SqlRecordReader(dbDataReader))
|> Seq.map(fun (state, rowReader) -> read state.SetIndex state.RecordIndex rowReader )
items
CommandPrepared command |> log
commandStopwatch.Start ()
use dbDataReader = deps.ExecuteReader command
let items =
Seq.initInfinite(fun _ -> dbDataReader)
|> SkipFirstSeq.scan(readNextRecordSync) { Continue = true; SetIndex = 0; RecordIndex = -1 }
|> Seq.takeWhile(fun state -> state.Continue)
|> Seq.mapChange(fun state -> state.SetIndex) (fun _ -> SqlRecordReader(dbDataReader))
|> Seq.map(fun (state, rowReader) -> read state.SetIndex state.RecordIndex rowReader )
yield! items

finally
commandStopwatch.Stop ()
CommandExecuted (command, commandStopwatch.Elapsed) |> log
if wasClosed then
connection.Close()
connectionStopwatch.Stop ()
ConnectionClosed (connection, connectionStopwatch.Elapsed) |> log
finally
commandStopwatch.Stop ()
CommandExecuted (command, commandStopwatch.Elapsed) |> log
if wasClosed then
connection.Close()
connectionStopwatch.Stop ()
ConnectionClosed (connection, connectionStopwatch.Elapsed) |> log
}

/// Execute the command and return the sets of rows as a list accordingly to the command definition.
/// This function runs asynchronously.
Expand Down

0 comments on commit f22a25d

Please sign in to comment.