Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data / Microsoft / SqlServer / Server / sqlpipe.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="SqlPipe.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 // <owner current="true" primary="false">daltodov</owner>
8 //------------------------------------------------------------------------------
9
10 namespace Microsoft.SqlServer.Server {
11
12     using System;
13     using System.Collections;
14     using System.Collections.Generic;
15     using System.Data;
16     using System.Data.Sql;
17     using System.Data.Common;
18     using System.Data.SqlClient;
19     using System.Data.SqlTypes;
20     using System.Diagnostics;
21
22     // SqlPipe
23     //    Abstraction of TDS data/message channel exposed to user.
24     public sealed class SqlPipe {
25
26         SmiContext              _smiContext;
27         SmiRecordBuffer         _recordBufferSent;          // Last recordBuffer sent to pipe (for push model SendEnd).
28         SqlMetaData[]           _metaDataSent;              // Metadata of last resultset started (for push model). Overloaded to indicate if push started or not (non-null/null)
29         SmiEventSink_Default    _eventSink;                 // Eventsink to use when calling SmiContext entrypoints
30         bool                    _isBusy;                    // Is this pipe currently handling an operation?
31         bool                    _hadErrorInResultSet;       // true if an exception was thrown from within various bodies; used to control cleanup during SendResultsEnd
32
33
34         internal SqlPipe( SmiContext smiContext ) {
35             _smiContext = smiContext;
36             _eventSink = new SmiEventSink_Default();
37         }
38
39
40         //
41         // Public methods
42         //
43         public void ExecuteAndSend( SqlCommand command ) {
44             SetPipeBusy( );
45             try {
46                 EnsureNormalSendValid( "ExecuteAndSend" );
47
48                 if ( null == command ) {
49                     throw ADP.ArgumentNull( "command" );
50                 }
51
52                 SqlConnection connection = command.Connection;
53
54                 // if the command doesn't have a connection set up, try to set one up on it's behalf
55                 if ( null == connection ) {
56                     using ( SqlConnection newConnection = new SqlConnection( "Context Connection=true" ) ) {
57                         newConnection.Open( );
58
59                         // use try-finally to restore command's connection property to it's original state
60                         try {
61                             command.Connection = newConnection;
62                             command.ExecuteToPipe( _smiContext );
63                         }
64                         finally {
65                             command.Connection = null;
66                         }
67                     }
68                 }
69                 else {
70                     // validate connection state
71                     if ( ConnectionState.Open != connection.State ) {
72                         throw ADP.ClosedConnectionError();
73                     }
74
75                     // validate connection is current scope's connection
76                     SqlInternalConnectionSmi internalConnection = connection.InnerConnection as SqlInternalConnectionSmi;
77
78                     if ( null == internalConnection ) {
79                         throw SQL.SqlPipeCommandHookedUpToNonContextConnection( );
80                     }
81
82                     command.ExecuteToPipe( _smiContext );
83                 }
84             }
85             finally {
86                 ClearPipeBusy( );
87             }
88         }
89        
90         // Equivalent to TSQL PRINT statement -- sends an info-only message.
91         public void Send( string message ) {
92             ADP.CheckArgumentNull(message, "message");
93
94             if ( SmiMetaData.MaxUnicodeCharacters < message.Length ) {
95                 throw SQL.SqlPipeMessageTooLong( message.Length );
96             }
97
98             SetPipeBusy( );
99             try {
100                 EnsureNormalSendValid( "Send" );
101
102                 _smiContext.SendMessageToPipe( message, _eventSink );
103
104                 // Handle any errors that are reported.
105                 _eventSink.ProcessMessagesAndThrow();
106             }
107             catch {
108                 _eventSink.CleanMessages();
109                 throw;
110             }
111             finally {
112                 ClearPipeBusy( );
113                 Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the Send message!");
114             }
115         }
116
117         // Send results from SqlDataReader
118         public void Send( SqlDataReader reader ) {
119             ADP.CheckArgumentNull(reader, "reader");
120
121             SetPipeBusy( );
122             try {
123                 EnsureNormalSendValid( "Send" );
124                 do {
125                     SmiExtendedMetaData[] columnMetaData = reader.GetInternalSmiMetaData();
126
127                     if (null != columnMetaData && 0 != columnMetaData.Length) { // SQLBUDT #340528 -- don't send empty results.
128                         using ( SmiRecordBuffer recordBuffer = _smiContext.CreateRecordBuffer(columnMetaData, _eventSink) ) {
129                             _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
130
131
132                             _smiContext.SendResultsStartToPipe( recordBuffer, _eventSink );
133                             _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
134
135                             try {
136                                 while( reader.Read( ) ) {
137                                     if (SmiContextFactory.Instance.NegotiatedSmiVersion >= SmiContextFactory.KatmaiVersion) {
138                                         ValueUtilsSmi.FillCompatibleSettersFromReader(_eventSink, recordBuffer, new List<SmiExtendedMetaData>(columnMetaData), reader);
139                                     }
140                                     else {
141                                         ValueUtilsSmi.FillCompatibleITypedSettersFromReader(_eventSink, recordBuffer, columnMetaData, reader);
142                                     }
143                                     
144                                     _smiContext.SendResultsRowToPipe( recordBuffer, _eventSink );
145                                     _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
146                                 }
147                             }
148                             finally {
149                                 _smiContext.SendResultsEndToPipe( recordBuffer, _eventSink );
150                                 _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
151                             }
152                         }
153                     }
154                 }
155                 while ( reader.NextResult( ) );
156             }
157             catch { 
158                 _eventSink.CleanMessages();
159                 throw;
160             }
161             finally {
162                 ClearPipeBusy( );
163                 Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the Send reader!");
164             }
165         }
166
167         public void Send( SqlDataRecord record ) {
168             ADP.CheckArgumentNull(record, "record");
169
170             SetPipeBusy( );
171             try {
172                 EnsureNormalSendValid( "Send" );
173
174                 if (0 != record.FieldCount) { // SQLBUDT #340564 -- don't send empty records.
175                 
176                     SmiRecordBuffer recordBuffer;
177                     if (record.RecordContext == _smiContext) {
178                         recordBuffer = record.RecordBuffer;
179                     } else {    // SendResultsRowToPipe() only takes a RecordBuffer created by an SmiContext
180                         SmiExtendedMetaData[] columnMetaData = record.InternalGetSmiMetaData();
181                         recordBuffer = _smiContext.CreateRecordBuffer(columnMetaData, _eventSink);
182                         if (SmiContextFactory.Instance.NegotiatedSmiVersion >= SmiContextFactory.KatmaiVersion) {
183                             ValueUtilsSmi.FillCompatibleSettersFromRecord(_eventSink, recordBuffer, columnMetaData, record, null /* no default values */);
184                         }
185                         else {
186                             ValueUtilsSmi.FillCompatibleITypedSettersFromRecord(_eventSink, recordBuffer, columnMetaData, record);
187                         }
188                     }
189
190                     _smiContext.SendResultsStartToPipe( recordBuffer, _eventSink );
191                     _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
192
193                     //  If SendResultsStartToPipe succeeded, then SendResultsEndToPipe must be called.
194                     try {
195                         _smiContext.SendResultsRowToPipe( recordBuffer, _eventSink );
196                         _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
197                     }
198                     finally {
199                         _smiContext.SendResultsEndToPipe( recordBuffer, _eventSink );
200                         _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
201                     }
202                 }
203             }
204             catch {
205                 // VSDD 479525: if exception happens (e.g. SendResultsStartToPipe throw OutOfMemory), _eventSink may not be empty,
206                 // which will affect server's behavior if the next call successes (previous exception is still in the eventSink, 
207                 // will be throwed). So we need to clean _eventSink.
208                 _eventSink.CleanMessages();
209                 throw;
210             }
211             finally {
212                 ClearPipeBusy( );
213                 Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the Send record!");
214             }
215         }
216
217         public void SendResultsStart( SqlDataRecord record ) {
218             ADP.CheckArgumentNull(record, "record");
219
220             SetPipeBusy( );
221             try {
222                 EnsureNormalSendValid( "SendResultsStart" );
223
224                 SmiRecordBuffer recordBuffer = record.RecordBuffer;
225                 if (record.RecordContext == _smiContext) {
226                     recordBuffer = record.RecordBuffer;
227                 } else {
228                     recordBuffer = _smiContext.CreateRecordBuffer(record.InternalGetSmiMetaData(), _eventSink);    // Only MetaData needed for sending start
229                 }
230                 _smiContext.SendResultsStartToPipe( recordBuffer, _eventSink );
231
232                 // Handle any errors that are reported.
233                 _eventSink.ProcessMessagesAndThrow();
234
235                 // remember sent buffer info so it can be used in send row/end.
236                 _recordBufferSent = recordBuffer;
237                 _metaDataSent = record.InternalGetMetaData();
238             }
239             catch {
240                 _eventSink.CleanMessages();
241                 throw;
242             }
243             finally {
244                 ClearPipeBusy( );
245                 Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the SendResultsStart!");
246             }
247         }
248
249         public void SendResultsRow( SqlDataRecord record ) {
250             ADP.CheckArgumentNull(record, "record");
251
252             SetPipeBusy( );
253             try {
254                 EnsureResultStarted( "SendResultsRow" );
255
256                 if ( _hadErrorInResultSet ) {
257                     throw SQL.SqlPipeErrorRequiresSendEnd();
258                 }
259
260                 // Assume error state unless cleared below
261                 _hadErrorInResultSet = true;
262
263                 SmiRecordBuffer recordBuffer;
264                 if (record.RecordContext == _smiContext) {
265                     recordBuffer = record.RecordBuffer;
266                 } else {
267                     SmiExtendedMetaData[] columnMetaData = record.InternalGetSmiMetaData();
268                     recordBuffer = _smiContext.CreateRecordBuffer(columnMetaData, _eventSink);
269                     if (SmiContextFactory.Instance.NegotiatedSmiVersion >= SmiContextFactory.KatmaiVersion) {
270                         ValueUtilsSmi.FillCompatibleSettersFromRecord(_eventSink, recordBuffer, columnMetaData, record, null /* no default values */);
271                     }
272                     else {
273                         ValueUtilsSmi.FillCompatibleITypedSettersFromRecord(_eventSink, recordBuffer, columnMetaData, record);
274                     }
275                 }
276                 _smiContext.SendResultsRowToPipe( recordBuffer, _eventSink );
277
278                 // Handle any errors that are reported.
279                 _eventSink.ProcessMessagesAndThrow();
280
281                 // We successfully traversed the send, clear error state
282                 _hadErrorInResultSet = false;
283             }
284             catch {
285                 _eventSink.CleanMessages();
286                 throw;
287             }
288             finally {
289                 ClearPipeBusy( );
290                 Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the SendResultsRow!");
291             }
292         }
293
294         public void SendResultsEnd( ) {
295             SetPipeBusy( );
296             try {
297                 EnsureResultStarted( "SendResultsEnd" );
298
299                 _smiContext.SendResultsEndToPipe( _recordBufferSent, _eventSink );
300
301                 // Once end called down to native code, assume end of resultset
302                 _metaDataSent = null;
303                 _recordBufferSent = null;
304                 _hadErrorInResultSet = false;
305
306                 // Handle any errors that are reported.
307                 _eventSink.ProcessMessagesAndThrow();
308             }
309             catch {
310                 _eventSink.CleanMessages();
311                 throw;
312             }
313             finally {
314                 ClearPipeBusy( );
315                 Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the SendResultsEnd!");
316             }
317         }
318
319         // This isn't speced, but it may not be a bad idea to implement...
320         public bool IsSendingResults {
321             get {
322                 return null != _metaDataSent;
323             }
324         }
325
326         internal void OnOutOfScope( ) {
327             _metaDataSent = null;
328             _recordBufferSent = null;
329             _hadErrorInResultSet = false;
330             _isBusy = false;
331         }
332
333         // Pipe busy status.
334         //    Ensures user code cannot call any APIs while a send is in progress.
335         //
336         //    Public methods must call this method before sending anything to the unmanaged pipe.
337         //    Once busy status is set, it must clear before returning from the calling method
338         //        ( i.e. clear should be in a finally block).
339         private void SetPipeBusy( ) {
340             if ( _isBusy ) {
341                 throw SQL.SqlPipeIsBusy( );
342             }
343             _isBusy = true;
344         }
345
346         // Clear the pipe's busy status.
347         private void ClearPipeBusy( ) {
348             _isBusy = false;
349         }
350
351         //
352         // State validation
353         //    One of the Ensure* validation methods should appear at the top of every public method
354         //
355
356         // Default validation method
357         //    Ensures Pipe is not currently transmitting a push-model resultset
358         private void EnsureNormalSendValid( string methodName ) {
359             if ( IsSendingResults ) {
360                 throw SQL.SqlPipeAlreadyHasAnOpenResultSet( methodName );
361             }
362         }
363
364         private void EnsureResultStarted( string methodName ) {
365             if ( !IsSendingResults ) {
366                 throw SQL.SqlPipeDoesNotHaveAnOpenResultSet( methodName );
367             }
368         }
369     }
370 }
371
372