1 //------------------------------------------------------------------------------
2 // <copyright file="SqlPipe.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
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 //------------------------------------------------------------------------------
10 namespace Microsoft.SqlServer.Server {
13 using System.Collections;
14 using System.Collections.Generic;
16 using System.Data.Sql;
17 using System.Data.Common;
18 using System.Data.SqlClient;
19 using System.Data.SqlTypes;
20 using System.Diagnostics;
23 // Abstraction of TDS data/message channel exposed to user.
24 public sealed class SqlPipe {
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
34 internal SqlPipe( SmiContext smiContext ) {
35 _smiContext = smiContext;
36 _eventSink = new SmiEventSink_Default();
43 public void ExecuteAndSend( SqlCommand command ) {
46 EnsureNormalSendValid( "ExecuteAndSend" );
48 if ( null == command ) {
49 throw ADP.ArgumentNull( "command" );
52 SqlConnection connection = command.Connection;
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( );
59 // use try-finally to restore command's connection property to it's original state
61 command.Connection = newConnection;
62 command.ExecuteToPipe( _smiContext );
65 command.Connection = null;
70 // validate connection state
71 if ( ConnectionState.Open != connection.State ) {
72 throw ADP.ClosedConnectionError();
75 // validate connection is current scope's connection
76 SqlInternalConnectionSmi internalConnection = connection.InnerConnection as SqlInternalConnectionSmi;
78 if ( null == internalConnection ) {
79 throw SQL.SqlPipeCommandHookedUpToNonContextConnection( );
82 command.ExecuteToPipe( _smiContext );
90 // Equivalent to TSQL PRINT statement -- sends an info-only message.
91 public void Send( string message ) {
92 ADP.CheckArgumentNull(message, "message");
94 if ( SmiMetaData.MaxUnicodeCharacters < message.Length ) {
95 throw SQL.SqlPipeMessageTooLong( message.Length );
100 EnsureNormalSendValid( "Send" );
102 _smiContext.SendMessageToPipe( message, _eventSink );
104 // Handle any errors that are reported.
105 _eventSink.ProcessMessagesAndThrow();
108 _eventSink.CleanMessages();
113 Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the Send message!");
117 // Send results from SqlDataReader
118 public void Send( SqlDataReader reader ) {
119 ADP.CheckArgumentNull(reader, "reader");
123 EnsureNormalSendValid( "Send" );
125 SmiExtendedMetaData[] columnMetaData = reader.GetInternalSmiMetaData();
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.
132 _smiContext.SendResultsStartToPipe( recordBuffer, _eventSink );
133 _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
136 while( reader.Read( ) ) {
137 if (SmiContextFactory.Instance.NegotiatedSmiVersion >= SmiContextFactory.KatmaiVersion) {
138 ValueUtilsSmi.FillCompatibleSettersFromReader(_eventSink, recordBuffer, new List<SmiExtendedMetaData>(columnMetaData), reader);
141 ValueUtilsSmi.FillCompatibleITypedSettersFromReader(_eventSink, recordBuffer, columnMetaData, reader);
144 _smiContext.SendResultsRowToPipe( recordBuffer, _eventSink );
145 _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
149 _smiContext.SendResultsEndToPipe( recordBuffer, _eventSink );
150 _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
155 while ( reader.NextResult( ) );
158 _eventSink.CleanMessages();
163 Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the Send reader!");
167 public void Send( SqlDataRecord record ) {
168 ADP.CheckArgumentNull(record, "record");
172 EnsureNormalSendValid( "Send" );
174 if (0 != record.FieldCount) { // SQLBUDT #340564 -- don't send empty records.
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 */);
186 ValueUtilsSmi.FillCompatibleITypedSettersFromRecord(_eventSink, recordBuffer, columnMetaData, record);
190 _smiContext.SendResultsStartToPipe( recordBuffer, _eventSink );
191 _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
193 // If SendResultsStartToPipe succeeded, then SendResultsEndToPipe must be called.
195 _smiContext.SendResultsRowToPipe( recordBuffer, _eventSink );
196 _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
199 _smiContext.SendResultsEndToPipe( recordBuffer, _eventSink );
200 _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
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();
213 Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the Send record!");
217 public void SendResultsStart( SqlDataRecord record ) {
218 ADP.CheckArgumentNull(record, "record");
222 EnsureNormalSendValid( "SendResultsStart" );
224 SmiRecordBuffer recordBuffer = record.RecordBuffer;
225 if (record.RecordContext == _smiContext) {
226 recordBuffer = record.RecordBuffer;
228 recordBuffer = _smiContext.CreateRecordBuffer(record.InternalGetSmiMetaData(), _eventSink); // Only MetaData needed for sending start
230 _smiContext.SendResultsStartToPipe( recordBuffer, _eventSink );
232 // Handle any errors that are reported.
233 _eventSink.ProcessMessagesAndThrow();
235 // remember sent buffer info so it can be used in send row/end.
236 _recordBufferSent = recordBuffer;
237 _metaDataSent = record.InternalGetMetaData();
240 _eventSink.CleanMessages();
245 Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the SendResultsStart!");
249 public void SendResultsRow( SqlDataRecord record ) {
250 ADP.CheckArgumentNull(record, "record");
254 EnsureResultStarted( "SendResultsRow" );
256 if ( _hadErrorInResultSet ) {
257 throw SQL.SqlPipeErrorRequiresSendEnd();
260 // Assume error state unless cleared below
261 _hadErrorInResultSet = true;
263 SmiRecordBuffer recordBuffer;
264 if (record.RecordContext == _smiContext) {
265 recordBuffer = record.RecordBuffer;
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 */);
273 ValueUtilsSmi.FillCompatibleITypedSettersFromRecord(_eventSink, recordBuffer, columnMetaData, record);
276 _smiContext.SendResultsRowToPipe( recordBuffer, _eventSink );
278 // Handle any errors that are reported.
279 _eventSink.ProcessMessagesAndThrow();
281 // We successfully traversed the send, clear error state
282 _hadErrorInResultSet = false;
285 _eventSink.CleanMessages();
290 Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the SendResultsRow!");
294 public void SendResultsEnd( ) {
297 EnsureResultStarted( "SendResultsEnd" );
299 _smiContext.SendResultsEndToPipe( _recordBufferSent, _eventSink );
301 // Once end called down to native code, assume end of resultset
302 _metaDataSent = null;
303 _recordBufferSent = null;
304 _hadErrorInResultSet = false;
306 // Handle any errors that are reported.
307 _eventSink.ProcessMessagesAndThrow();
310 _eventSink.CleanMessages();
315 Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the SendResultsEnd!");
319 // This isn't speced, but it may not be a bad idea to implement...
320 public bool IsSendingResults {
322 return null != _metaDataSent;
326 internal void OnOutOfScope( ) {
327 _metaDataSent = null;
328 _recordBufferSent = null;
329 _hadErrorInResultSet = false;
334 // Ensures user code cannot call any APIs while a send is in progress.
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( ) {
341 throw SQL.SqlPipeIsBusy( );
346 // Clear the pipe's busy status.
347 private void ClearPipeBusy( ) {
353 // One of the Ensure* validation methods should appear at the top of every public method
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 );
364 private void EnsureResultStarted( string methodName ) {
365 if ( !IsSendingResults ) {
366 throw SQL.SqlPipeDoesNotHaveAnOpenResultSet( methodName );