diff --git a/Vp.FSharp.Sql.Tests/Mocks.fs b/Vp.FSharp.Sql.Tests/Mocks.fs index d04467a..b5c7add 100644 --- a/Vp.FSharp.Sql.Tests/Mocks.fs +++ b/Vp.FSharp.Sql.Tests/Mocks.fs @@ -17,7 +17,7 @@ type DbField' = type Data = { Columns: DbField' list list - GetValues: int32 -> int32 -> Object list + GetValues: int32 -> int32 -> obj list CountRows: int32 -> int32 CountResultSets: int32 } @@ -25,6 +25,52 @@ type Response = | Reader of (CommandBehavior -> DbDataReader) | NonQuery of int32 +type IIsDisposed = + inherit IDisposable + abstract member IsDisposed : bool with get + +[] +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 + +[] +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 + +[] +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 = @@ -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 diff --git a/Vp.FSharp.Sql.Tests/SqlCommand for querySeqSync should.fs b/Vp.FSharp.Sql.Tests/SqlCommand for querySeqSync should.fs index ebaa210..280f2f8 100644 --- a/Vp.FSharp.Sql.Tests/SqlCommand for querySeqSync should.fs +++ b/Vp.FSharp.Sql.Tests/SqlCommand for querySeqSync should.fs @@ -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) = columns @@ -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" diff --git a/Vp.FSharp.Sql/SqlCommand.fs b/Vp.FSharp.Sql/SqlCommand.fs index 4de2e9a..334dcb7 100644 --- a/Vp.FSharp.Sql/SqlCommand.fs +++ b/Vp.FSharp.Sql/SqlCommand.fs @@ -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.