| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- //------------------------------------------------------------------------------
- // <copyright file="SqlPipe.cs" company="Microsoft">
- // Copyright (c) Microsoft Corporation. All rights reserved.
- // </copyright>
- // <owner current="true" primary="true">Microsoft</owner>
- // <owner current="true" primary="false">Microsoft</owner>
- // <owner current="true" primary="false">daltodov</owner>
- //------------------------------------------------------------------------------
- namespace Microsoft.SqlServer.Server {
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Data;
- using System.Data.Sql;
- using System.Data.Common;
- using System.Data.SqlClient;
- using System.Data.SqlTypes;
- using System.Diagnostics;
- // SqlPipe
- // Abstraction of TDS data/message channel exposed to user.
- public sealed class SqlPipe {
- SmiContext _smiContext;
- SmiRecordBuffer _recordBufferSent; // Last recordBuffer sent to pipe (for push model SendEnd).
- SqlMetaData[] _metaDataSent; // Metadata of last resultset started (for push model). Overloaded to indicate if push started or not (non-null/null)
- SmiEventSink_Default _eventSink; // Eventsink to use when calling SmiContext entrypoints
- bool _isBusy; // Is this pipe currently handling an operation?
- bool _hadErrorInResultSet; // true if an exception was thrown from within various bodies; used to control cleanup during SendResultsEnd
- internal SqlPipe( SmiContext smiContext ) {
- _smiContext = smiContext;
- _eventSink = new SmiEventSink_Default();
- }
- //
- // Public methods
- //
- public void ExecuteAndSend( SqlCommand command ) {
- SetPipeBusy( );
- try {
- EnsureNormalSendValid( "ExecuteAndSend" );
- if ( null == command ) {
- throw ADP.ArgumentNull( "command" );
- }
- SqlConnection connection = command.Connection;
- // if the command doesn't have a connection set up, try to set one up on it's behalf
- if ( null == connection ) {
- using ( SqlConnection newConnection = new SqlConnection( "Context Connection=true" ) ) {
- newConnection.Open( );
- // use try-finally to restore command's connection property to it's original state
- try {
- command.Connection = newConnection;
- command.ExecuteToPipe( _smiContext );
- }
- finally {
- command.Connection = null;
- }
- }
- }
- else {
- // validate connection state
- if ( ConnectionState.Open != connection.State ) {
- throw ADP.ClosedConnectionError();
- }
- // validate connection is current scope's connection
- SqlInternalConnectionSmi internalConnection = connection.InnerConnection as SqlInternalConnectionSmi;
- if ( null == internalConnection ) {
- throw SQL.SqlPipeCommandHookedUpToNonContextConnection( );
- }
- command.ExecuteToPipe( _smiContext );
- }
- }
- finally {
- ClearPipeBusy( );
- }
- }
-
- // Equivalent to TSQL PRINT statement -- sends an info-only message.
- public void Send( string message ) {
- ADP.CheckArgumentNull(message, "message");
- if ( SmiMetaData.MaxUnicodeCharacters < message.Length ) {
- throw SQL.SqlPipeMessageTooLong( message.Length );
- }
- SetPipeBusy( );
- try {
- EnsureNormalSendValid( "Send" );
- _smiContext.SendMessageToPipe( message, _eventSink );
- // Handle any errors that are reported.
- _eventSink.ProcessMessagesAndThrow();
- }
- catch {
- _eventSink.CleanMessages();
- throw;
- }
- finally {
- ClearPipeBusy( );
- Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the Send message!");
- }
- }
- // Send results from SqlDataReader
- public void Send( SqlDataReader reader ) {
- ADP.CheckArgumentNull(reader, "reader");
- SetPipeBusy( );
- try {
- EnsureNormalSendValid( "Send" );
- do {
- SmiExtendedMetaData[] columnMetaData = reader.GetInternalSmiMetaData();
- if (null != columnMetaData && 0 != columnMetaData.Length) { // SQLBUDT #340528 -- don't send empty results.
- using ( SmiRecordBuffer recordBuffer = _smiContext.CreateRecordBuffer(columnMetaData, _eventSink) ) {
- _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
- _smiContext.SendResultsStartToPipe( recordBuffer, _eventSink );
- _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
- try {
- while( reader.Read( ) ) {
- if (SmiContextFactory.Instance.NegotiatedSmiVersion >= SmiContextFactory.KatmaiVersion) {
- ValueUtilsSmi.FillCompatibleSettersFromReader(_eventSink, recordBuffer, new List<SmiExtendedMetaData>(columnMetaData), reader);
- }
- else {
- ValueUtilsSmi.FillCompatibleITypedSettersFromReader(_eventSink, recordBuffer, columnMetaData, reader);
- }
-
- _smiContext.SendResultsRowToPipe( recordBuffer, _eventSink );
- _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
- }
- }
- finally {
- _smiContext.SendResultsEndToPipe( recordBuffer, _eventSink );
- _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
- }
- }
- }
- }
- while ( reader.NextResult( ) );
- }
- catch {
- _eventSink.CleanMessages();
- throw;
- }
- finally {
- ClearPipeBusy( );
- Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the Send reader!");
- }
- }
- public void Send( SqlDataRecord record ) {
- ADP.CheckArgumentNull(record, "record");
- SetPipeBusy( );
- try {
- EnsureNormalSendValid( "Send" );
- if (0 != record.FieldCount) { // SQLBUDT #340564 -- don't send empty records.
-
- SmiRecordBuffer recordBuffer;
- if (record.RecordContext == _smiContext) {
- recordBuffer = record.RecordBuffer;
- } else { // SendResultsRowToPipe() only takes a RecordBuffer created by an SmiContext
- SmiExtendedMetaData[] columnMetaData = record.InternalGetSmiMetaData();
- recordBuffer = _smiContext.CreateRecordBuffer(columnMetaData, _eventSink);
- if (SmiContextFactory.Instance.NegotiatedSmiVersion >= SmiContextFactory.KatmaiVersion) {
- ValueUtilsSmi.FillCompatibleSettersFromRecord(_eventSink, recordBuffer, columnMetaData, record, null /* no default values */);
- }
- else {
- ValueUtilsSmi.FillCompatibleITypedSettersFromRecord(_eventSink, recordBuffer, columnMetaData, record);
- }
- }
- _smiContext.SendResultsStartToPipe( recordBuffer, _eventSink );
- _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
- // If SendResultsStartToPipe succeeded, then SendResultsEndToPipe must be called.
- try {
- _smiContext.SendResultsRowToPipe( recordBuffer, _eventSink );
- _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
- }
- finally {
- _smiContext.SendResultsEndToPipe( recordBuffer, _eventSink );
- _eventSink.ProcessMessagesAndThrow(); // Handle any errors that are reported.
- }
- }
- }
- catch {
- // VSDD 479525: if exception happens (e.g. SendResultsStartToPipe throw OutOfMemory), _eventSink may not be empty,
- // which will affect server's behavior if the next call successes (previous exception is still in the eventSink,
- // will be throwed). So we need to clean _eventSink.
- _eventSink.CleanMessages();
- throw;
- }
- finally {
- ClearPipeBusy( );
- Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the Send record!");
- }
- }
- public void SendResultsStart( SqlDataRecord record ) {
- ADP.CheckArgumentNull(record, "record");
- SetPipeBusy( );
- try {
- EnsureNormalSendValid( "SendResultsStart" );
- SmiRecordBuffer recordBuffer = record.RecordBuffer;
- if (record.RecordContext == _smiContext) {
- recordBuffer = record.RecordBuffer;
- } else {
- recordBuffer = _smiContext.CreateRecordBuffer(record.InternalGetSmiMetaData(), _eventSink); // Only MetaData needed for sending start
- }
- _smiContext.SendResultsStartToPipe( recordBuffer, _eventSink );
- // Handle any errors that are reported.
- _eventSink.ProcessMessagesAndThrow();
- // remember sent buffer info so it can be used in send row/end.
- _recordBufferSent = recordBuffer;
- _metaDataSent = record.InternalGetMetaData();
- }
- catch {
- _eventSink.CleanMessages();
- throw;
- }
- finally {
- ClearPipeBusy( );
- Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the SendResultsStart!");
- }
- }
- public void SendResultsRow( SqlDataRecord record ) {
- ADP.CheckArgumentNull(record, "record");
- SetPipeBusy( );
- try {
- EnsureResultStarted( "SendResultsRow" );
- if ( _hadErrorInResultSet ) {
- throw SQL.SqlPipeErrorRequiresSendEnd();
- }
- // Assume error state unless cleared below
- _hadErrorInResultSet = true;
- SmiRecordBuffer recordBuffer;
- if (record.RecordContext == _smiContext) {
- recordBuffer = record.RecordBuffer;
- } else {
- SmiExtendedMetaData[] columnMetaData = record.InternalGetSmiMetaData();
- recordBuffer = _smiContext.CreateRecordBuffer(columnMetaData, _eventSink);
- if (SmiContextFactory.Instance.NegotiatedSmiVersion >= SmiContextFactory.KatmaiVersion) {
- ValueUtilsSmi.FillCompatibleSettersFromRecord(_eventSink, recordBuffer, columnMetaData, record, null /* no default values */);
- }
- else {
- ValueUtilsSmi.FillCompatibleITypedSettersFromRecord(_eventSink, recordBuffer, columnMetaData, record);
- }
- }
- _smiContext.SendResultsRowToPipe( recordBuffer, _eventSink );
- // Handle any errors that are reported.
- _eventSink.ProcessMessagesAndThrow();
- // We successfully traversed the send, clear error state
- _hadErrorInResultSet = false;
- }
- catch {
- _eventSink.CleanMessages();
- throw;
- }
- finally {
- ClearPipeBusy( );
- Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the SendResultsRow!");
- }
- }
- public void SendResultsEnd( ) {
- SetPipeBusy( );
- try {
- EnsureResultStarted( "SendResultsEnd" );
- _smiContext.SendResultsEndToPipe( _recordBufferSent, _eventSink );
- // Once end called down to native code, assume end of resultset
- _metaDataSent = null;
- _recordBufferSent = null;
- _hadErrorInResultSet = false;
- // Handle any errors that are reported.
- _eventSink.ProcessMessagesAndThrow();
- }
- catch {
- _eventSink.CleanMessages();
- throw;
- }
- finally {
- ClearPipeBusy( );
- Debug.Assert(_eventSink.HasMessages == false, "There should be no messages left in _eventsink at the end of the SendResultsEnd!");
- }
- }
- // This isn't speced, but it may not be a bad idea to implement...
- public bool IsSendingResults {
- get {
- return null != _metaDataSent;
- }
- }
- internal void OnOutOfScope( ) {
- _metaDataSent = null;
- _recordBufferSent = null;
- _hadErrorInResultSet = false;
- _isBusy = false;
- }
- // Pipe busy status.
- // Ensures user code cannot call any APIs while a send is in progress.
- //
- // Public methods must call this method before sending anything to the unmanaged pipe.
- // Once busy status is set, it must clear before returning from the calling method
- // ( i.e. clear should be in a finally block).
- private void SetPipeBusy( ) {
- if ( _isBusy ) {
- throw SQL.SqlPipeIsBusy( );
- }
- _isBusy = true;
- }
- // Clear the pipe's busy status.
- private void ClearPipeBusy( ) {
- _isBusy = false;
- }
- //
- // State validation
- // One of the Ensure* validation methods should appear at the top of every public method
- //
- // Default validation method
- // Ensures Pipe is not currently transmitting a push-model resultset
- private void EnsureNormalSendValid( string methodName ) {
- if ( IsSendingResults ) {
- throw SQL.SqlPipeAlreadyHasAnOpenResultSet( methodName );
- }
- }
- private void EnsureResultStarted( string methodName ) {
- if ( !IsSendingResults ) {
- throw SQL.SqlPipeDoesNotHaveAnOpenResultSet( methodName );
- }
- }
- }
- }
|