sqlpipe.cs 16 KB

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