Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data / System / Data / OleDb / OleDbWrapper.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="OleDbWrapper.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 //------------------------------------------------------------------------------
8
9 namespace System.Data.OleDb {
10
11     using System;
12     using System.Data.Common;
13     using System.Data.ProviderBase;
14     using System.Diagnostics;
15     using System.Runtime.CompilerServices;
16     using System.Runtime.ConstrainedExecution;
17     using System.Runtime.InteropServices;
18
19     // SafeHandle wrapper around 'DataLinks' object which pools the native OLE DB providers.
20     // expect 1 per app-domain
21     sealed internal class OleDbServicesWrapper : WrappedIUnknown {
22
23         // we expect to store IDataInitialize instance pointer in base.handle
24
25         // since we only have one DataLinks object, caching the delegate here is valid as long we
26         // maintain an AddRef which is also the lifetime of this class instance
27         private UnsafeNativeMethods.IDataInitializeGetDataSource DangerousIDataInitializeGetDataSource;
28
29         // DataLinks (the unknown parameter) is created via Activator.CreateInstance outside of the SafeHandle
30         internal OleDbServicesWrapper(object unknown) : base() {
31             if (null != unknown) {
32                 RuntimeHelpers.PrepareConstrainedRegions();
33                 try { } finally {
34                     // store the QI result for IID_IDataInitialize
35                     base.handle = Marshal.GetComInterfaceForObject(unknown, typeof(UnsafeNativeMethods.IDataInitialize)); // 
36                 }
37                 // native COM rules are the QI result is the 'this' pointer
38                 // the pointer stored at that location is the vtable
39                 // since IDataInitialize is a public,shipped COM interface, its layout will not change (ever)
40                 IntPtr vtable = Marshal.ReadIntPtr(base.handle, 0);
41                 IntPtr method = Marshal.ReadIntPtr(vtable, 3 * IntPtr.Size); // GetDataSource is the 4'th vtable entry
42                 DangerousIDataInitializeGetDataSource = (UnsafeNativeMethods.IDataInitializeGetDataSource)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IDataInitializeGetDataSource));
43             }
44         }
45
46         internal void GetDataSource(OleDbConnectionString constr, ref DataSourceWrapper datasrcWrapper) {
47             OleDbHResult hr;
48             UnsafeNativeMethods.IDataInitializeGetDataSource GetDataSource = DangerousIDataInitializeGetDataSource;
49             bool mustRelease = false;
50             RuntimeHelpers.PrepareConstrainedRegions();
51             try {
52                 DangerousAddRef(ref mustRelease);
53
54                 // this is the string that DataLinks / OLE DB Services will use to create the provider
55                 string connectionString = constr.ActualConnectionString;
56                 
57                 // base.handle is the 'this' pointer for making the COM call to GetDataSource
58                 // the datasrcWrapper will store the IID_IDBInitialize pointer
59                 // call IDataInitiailze::GetDataSource via the delegate
60                 hr = GetDataSource(base.handle, IntPtr.Zero, ODB.CLSCTX_ALL, connectionString, ref ODB.IID_IDBInitialize, ref datasrcWrapper);
61             }
62             finally {
63                 if (mustRelease) {
64                     DangerousRelease();
65                 }
66             }
67             if (hr < 0) { // ignore infomsg
68                 if (OleDbHResult.REGDB_E_CLASSNOTREG == hr) {
69                     throw ODB.ProviderUnavailable(constr.Provider, null);
70                 }
71                 Exception e = OleDbConnection.ProcessResults(hr, null, null);
72                 Debug.Assert(null != e, "CreateProviderError");
73                 throw e;
74             }
75             else if (datasrcWrapper.IsInvalid) {
76                 SafeNativeMethods.Wrapper.ClearErrorInfo();
77                 throw ODB.ProviderUnavailable(constr.Provider, null);
78             }
79             Debug.Assert(!datasrcWrapper.IsInvalid, "bad DataSource");
80         }
81     }
82
83     // SafeHandle wrapper around 'Data Source' object which represents the connection
84     // expect 1 per OleDbConnectionInternal
85     sealed internal class DataSourceWrapper : WrappedIUnknown {
86
87         // we expect to store IDBInitialize instance pointer in base.handle
88
89         // construct a DataSourceWrapper and used as a ref parameter to GetDataSource
90         internal DataSourceWrapper() : base() {
91         }
92
93         internal OleDbHResult InitializeAndCreateSession(OleDbConnectionString constr, ref SessionWrapper sessionWrapper) {
94             OleDbHResult hr;
95             bool mustRelease = false;
96             IntPtr idbCreateSession = IntPtr.Zero;
97             RuntimeHelpers.PrepareConstrainedRegions();
98             try {
99                 DangerousAddRef(ref mustRelease);
100                 
101                 // native COM rules are the QI result is the 'this' pointer
102                 // the pointer stored at that location is the vtable
103                 // since IUnknown is a public,shipped COM interface, its layout will not change (ever)
104                 IntPtr vtable = Marshal.ReadIntPtr(base.handle, 0);
105                 IntPtr method = Marshal.ReadIntPtr(vtable, 0);
106                 
107                 // we cache the QueryInterface delegate to prevent recreating it on every call
108                 UnsafeNativeMethods.IUnknownQueryInterface QueryInterface = constr.DangerousDataSourceIUnknownQueryInterface;
109
110                 // since the delegate lifetime is longer than the original instance used to create it
111                 // we double check before each usage to verify the delegates function pointer
112                 if ((null == QueryInterface) || (method != Marshal.GetFunctionPointerForDelegate(QueryInterface))) {
113                     QueryInterface = (UnsafeNativeMethods.IUnknownQueryInterface)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IUnknownQueryInterface));
114                     constr.DangerousDataSourceIUnknownQueryInterface = QueryInterface;
115                 }
116                 
117                 // native COM rules are the QI result is the 'this' pointer
118                 // the pointer stored at that location is the vtable
119                 // since IDBInitialize is a public,shipped COM interface, its layout will not change (ever)
120                 vtable = Marshal.ReadIntPtr(base.handle, 0);
121                 method = Marshal.ReadIntPtr(vtable, 3 * IntPtr.Size);  // Initialize is the 4'th vtable entry
122
123                 // we cache the Initialize delegate to prevent recreating it on every call
124                 UnsafeNativeMethods.IDBInitializeInitialize Initialize = constr.DangerousIDBInitializeInitialize;
125                 
126                 // since the delegate lifetime is longer than the original instance used to create it
127                 // we double check before each usage to verify the delegates function pointer
128                 if ((null == Initialize) || (method != Marshal.GetFunctionPointerForDelegate(Initialize))) {
129                     Initialize = (UnsafeNativeMethods.IDBInitializeInitialize)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IDBInitializeInitialize));
130                     constr.DangerousIDBInitializeInitialize = Initialize;
131                 }
132
133                 // call IDBInitialize::Initialize via the delegate
134                 hr = Initialize(base.handle);
135
136                 // we don't ever expect DB_E_ALREADYINITIALIZED, but since we checked in V1.0 - its propagated along
137                 if ((0 <= hr) || (OleDbHResult.DB_E_ALREADYINITIALIZED == hr)) {
138                     
139                     // call IUnknown::QueryInterface via the delegate
140                     hr = (OleDbHResult)QueryInterface(base.handle, ref ODB.IID_IDBCreateSession, ref idbCreateSession);
141                     if ((0 <= hr) && (IntPtr.Zero != idbCreateSession)) {
142                         
143                         // native COM rules are the QI result is the 'this' pointer
144                         // the pointer stored at that location is the vtable
145                         // since IDBCreateSession is a public,shipped COM interface, its layout will not change (ever)
146                         vtable = Marshal.ReadIntPtr(idbCreateSession, 0);
147                         method = Marshal.ReadIntPtr(vtable, 3 * IntPtr.Size);  // CreateSession is the 4'th vtable entry
148                         
149                         UnsafeNativeMethods.IDBCreateSessionCreateSession CreateSession = constr.DangerousIDBCreateSessionCreateSession;                                
150
151                         // since the delegate lifetime is longer than the original instance used to create it
152                         // we double check before each usage to verify the delegates function pointer
153                         if ((null == CreateSession) || (method != Marshal.GetFunctionPointerForDelegate(CreateSession))) {
154                             CreateSession = (UnsafeNativeMethods.IDBCreateSessionCreateSession)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IDBCreateSessionCreateSession));
155                             constr.DangerousIDBCreateSessionCreateSession = CreateSession;
156                         }
157
158                         // if I have a delegate for CreateCommand directly ask for IDBCreateCommand
159                         if (null != constr.DangerousIDBCreateCommandCreateCommand) {
160                             // call IDBCreateSession::CreateSession via the delegate directly for IDBCreateCommand
161                             hr = CreateSession(idbCreateSession, IntPtr.Zero, ref ODB.IID_IDBCreateCommand, ref sessionWrapper);
162                             if ((0 <= hr) && !sessionWrapper.IsInvalid) {                                        
163                                 // double check the cached delegate is correct
164                                 sessionWrapper.VerifyIDBCreateCommand(constr);
165                             }
166                         }
167                         else {
168                             // otherwise ask for IUnknown (it may be first time usage or IDBCreateCommand not supported)
169                             hr = CreateSession(idbCreateSession, IntPtr.Zero, ref ODB.IID_IUnknown, ref sessionWrapper);
170                             if ((0 <= hr) && !sessionWrapper.IsInvalid) {
171                                 // and check support for IDBCreateCommand and create delegate for CreateCommand
172                                 sessionWrapper.QueryInterfaceIDBCreateCommand(constr);
173                             }
174                         }
175                     }
176                 }
177             }
178             finally {
179                 if (IntPtr.Zero != idbCreateSession) {
180                     // release the QI for IDBCreateSession
181                     Marshal.Release(idbCreateSession);
182                 }
183                 if (mustRelease) {
184                     // release the AddRef on DataLinks
185                     DangerousRelease();
186                 }
187             }
188             return hr;
189         }
190
191         internal IDBInfoWrapper IDBInfo(OleDbConnectionInternal connection) {
192             Bid.Trace("<oledb.IUnknown.QueryInterface|API|OLEDB|datasource> %d#, IDBInfo\n", connection.ObjectID);
193             return new IDBInfoWrapper(ComWrapper());
194         }
195         internal IDBPropertiesWrapper IDBProperties(OleDbConnectionInternal connection) {
196             Bid.Trace("<oledb.IUnknown.QueryInterface|API|OLEDB|datasource> %d#, IDBProperties\n", connection.ObjectID);
197             return new IDBPropertiesWrapper(ComWrapper());
198         }
199     }
200
201
202     // SafeHandle wrapper around 'Session' object which represents the session on the connection
203     // expect 1 per OleDbConnectionInternal
204     sealed internal class SessionWrapper : WrappedIUnknown {
205     
206         // base.handle will either reference the IUnknown interface or IDBCreateCommand interface
207         // if OleDbConnectionString.DangerousIDBCreateCommandCreateCommand exists
208         // the CreateSession call will ask directly for the optional IDBCreateCommand
209         // otherwise it is the first call or known that IDBCreateCommand isn't supported
210
211         // we cache the DangerousIDBCreateCommandCreateCommand (expecting base.handle to be IDBCreateCommand)
212         // since we maintain an AddRef on IDBCreateCommand it is safe to use the delegate without rechecking its function pointer
213         private UnsafeNativeMethods.IDBCreateCommandCreateCommand DangerousIDBCreateCommandCreateCommand;
214
215         internal SessionWrapper() : base() {
216         }
217
218         // if OleDbConnectionString.DangerousIDBCreateCommandCreateCommand does not exist
219         // this method will be called to query for IDBCreateCommand (and cache that interface pointer)
220         // or it will be known that IDBCreateCommand is not supported
221         internal void QueryInterfaceIDBCreateCommand(OleDbConnectionString constr) {
222             // DangerousAddRef/DangerousRelease are not neccessary here in the current implementation
223             // only used from within OleDbConnectionInternal.ctor->DataSourceWrapper.InitializeAndCreateSession
224
225             // caching the fact if we have queried for IDBCreateCommand or not
226             // the command object is not supported by all providers, they would use IOpenRowset
227             // SQLBU:446413, SQLHotfix:1138 -- added extra if condition
228             // If constr.HaveQueriedForCreateCommand is false, this is the first time through this method and we need to set up the cache for sure.
229             // If two threads try to set the cache at the same time, everything should be okay. There can be multiple delegates that point to the same unmanaged function.
230             // If constr.HaveQueriedForCreateCommand is true, we have already tried to query for IDBCreateCommand on a previous call to this method, but based on that alone,
231             //     we don't know if another thread set the flag, or if the provider really doesn't support commands.
232             // If constr.HaveQueriedForCreateCommand is true and constr.DangerousIDBCreateCommandCreateCommand is not null, that means that another thread has set it after we
233             //     determined we needed to call QueryInterfaceIDBCreateCommand -- otherwise we would have called VerifyIDBCreateCommand instead 
234             // In that case, we still need to set our local DangerousIDBCreateCommandCreateCommand, so we want to go through the if block even though the cache has been set on constr already            
235             if (!constr.HaveQueriedForCreateCommand || (null != constr.DangerousIDBCreateCommandCreateCommand)) {
236                 IntPtr idbCreateCommand = IntPtr.Zero;
237                 RuntimeHelpers.PrepareConstrainedRegions();
238                 try {
239                     // native COM rules are the QI result is the 'this' pointer
240                     // the pointer stored at that location is the vtable
241                     // since IUnknown is a public,shipped COM interface, its layout will not change (ever)
242                     IntPtr vtable = Marshal.ReadIntPtr(base.handle, 0);
243                     IntPtr method = Marshal.ReadIntPtr(vtable, 0);
244                     UnsafeNativeMethods.IUnknownQueryInterface QueryInterface = (UnsafeNativeMethods.IUnknownQueryInterface)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IUnknownQueryInterface));
245
246                     int hresult = QueryInterface(base.handle, ref ODB.IID_IDBCreateCommand, ref idbCreateCommand);  // 
247                     if ((0 <= hresult) && (IntPtr.Zero != idbCreateCommand)) {
248                         vtable = Marshal.ReadIntPtr(idbCreateCommand, 0);
249                         method = Marshal.ReadIntPtr(vtable, 3 * IntPtr.Size);
250
251                         DangerousIDBCreateCommandCreateCommand = (UnsafeNativeMethods.IDBCreateCommandCreateCommand)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IDBCreateCommandCreateCommand));
252                         constr.DangerousIDBCreateCommandCreateCommand = DangerousIDBCreateCommandCreateCommand;                        
253                     }
254                     
255                     // caching the fact that we have queried for IDBCreateCommand
256                     constr.HaveQueriedForCreateCommand = true;
257                 }
258                 finally {
259                     if (IntPtr.Zero != idbCreateCommand) {
260                         IntPtr ptr = base.handle;
261                         base.handle = idbCreateCommand;
262                         Marshal.Release(ptr);
263                     }
264                 }
265             }
266             //else if constr.HaveQueriedForCreateCommand is true and constr.DangerousIDBCreateCommandCreateCommand is still null, it means that this provider doesn't support commands
267         }
268
269         internal void VerifyIDBCreateCommand(OleDbConnectionString constr) {
270             // DangerousAddRef/DangerousRelease are not neccessary here in the current implementation
271             // only used from within OleDbConnectionInternal.ctor->DataSourceWrapper.InitializeAndCreateSession
272
273             Debug.Assert(constr.HaveQueriedForCreateCommand, "expected HaveQueriedForCreateCommand");
274             Debug.Assert(null != constr.DangerousIDBCreateCommandCreateCommand, "expected DangerousIDBCreateCommandCreateCommand");
275             
276             // native COM rules are the QI result is the 'this' pointer
277             // the pointer stored at that location is the vtable
278             // since IDBCreateCommand is a public,shipped COM interface, its layout will not change (ever)
279             IntPtr vtable = Marshal.ReadIntPtr(base.handle, 0);
280             IntPtr method = Marshal.ReadIntPtr(vtable, 3 * IntPtr.Size);
281
282             // obtain the cached delegate to be cached on this instance
283             UnsafeNativeMethods.IDBCreateCommandCreateCommand CreateCommand = constr.DangerousIDBCreateCommandCreateCommand;
284
285             // since the delegate lifetime is longer than the original instance used to create it
286             // we double check before each usage to verify the delegates function pointer
287             if ((null == CreateCommand) || (method != Marshal.GetFunctionPointerForDelegate(CreateCommand))) {
288                 CreateCommand = (UnsafeNativeMethods.IDBCreateCommandCreateCommand)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IDBCreateCommandCreateCommand));
289                 constr.DangerousIDBCreateCommandCreateCommand = CreateCommand;
290             }
291             // since this instance can be used to create multiple commands
292             // cache it on the class so that the function pointer doesn't have to be validated every time
293             DangerousIDBCreateCommandCreateCommand = CreateCommand;
294         }
295
296         internal OleDbHResult CreateCommand(ref object icommandText) {
297             // if (null == CreateCommand), the IDBCreateCommand isn't supported - aka E_NOINTERFACE
298             OleDbHResult hr = OleDbHResult.E_NOINTERFACE;
299             UnsafeNativeMethods.IDBCreateCommandCreateCommand CreateCommand = DangerousIDBCreateCommandCreateCommand;
300             if (null != CreateCommand) {
301                 bool mustRelease = false;
302                 RuntimeHelpers.PrepareConstrainedRegions();
303                 try {
304                     DangerousAddRef(ref mustRelease);
305
306                     // call IDBCreateCommand::CreateCommand via the delegate directly for IDBCreateCommand
307                     hr = CreateCommand(base.handle, IntPtr.Zero, ref ODB.IID_ICommandText, ref icommandText);
308                 }
309                 finally {
310                     if (mustRelease) {
311                         DangerousRelease();
312                     }
313                 }
314             }
315             return hr;
316         }
317
318         internal IDBSchemaRowsetWrapper IDBSchemaRowset(OleDbConnectionInternal connection) {
319             Bid.Trace("<oledb.IUnknown.QueryInterface|API|OLEDB|session> %d#, IDBSchemaRowset\n", connection.ObjectID);
320             return new IDBSchemaRowsetWrapper(ComWrapper());
321         }
322
323         internal IOpenRowsetWrapper IOpenRowset(OleDbConnectionInternal connection) {
324             Bid.Trace("<oledb.IUnknown.QueryInterface|API|OLEDB|session> %d#, IOpenRowset\n", connection.ObjectID);
325             return new IOpenRowsetWrapper(ComWrapper());
326         }
327
328         internal ITransactionJoinWrapper ITransactionJoin(OleDbConnectionInternal connection) {
329             Bid.Trace("<oledb.IUnknown.QueryInterface|API|OLEDB|session> %d#, ITransactionJoin\n", connection.ObjectID);
330             return new ITransactionJoinWrapper(ComWrapper());
331         }
332     }
333
334     // unable to use generics bacause (unknown as T) doesn't compile
335     internal struct IDBInfoWrapper : IDisposable {
336         // _unknown must be tracked because the _value may not exist,
337         // yet _unknown must still be released
338         private object _unknown;
339         private UnsafeNativeMethods.IDBInfo _value;
340
341         internal IDBInfoWrapper(object unknown) {
342             _unknown = unknown;
343             _value = (unknown as UnsafeNativeMethods.IDBInfo);
344         }
345
346         internal UnsafeNativeMethods.IDBInfo Value {
347             get {
348                 return _value;
349             }
350         }
351
352         public void Dispose() {
353             object unknown = _unknown;
354             _unknown = null;
355             _value = null;
356             if (null != unknown) {
357                 Marshal.ReleaseComObject(unknown);
358             }
359         }
360     }
361
362     internal struct IDBPropertiesWrapper : IDisposable {
363         private object _unknown;
364         private UnsafeNativeMethods.IDBProperties _value;
365
366         internal IDBPropertiesWrapper(object unknown) {
367             _unknown = unknown;
368             _value = (unknown as UnsafeNativeMethods.IDBProperties);
369             Debug.Assert(null != _value, "null IDBProperties");
370         }
371
372         internal UnsafeNativeMethods.IDBProperties Value {
373             get {
374                 Debug.Assert(null != _value, "null IDBProperties");
375                 return _value;
376             }
377         }
378
379         public void Dispose() {
380             object unknown = _unknown;
381             _unknown = null;
382             _value = null;
383             if (null != unknown) {
384                 Marshal.ReleaseComObject(unknown);
385             }
386         }
387     }
388
389     internal struct IDBSchemaRowsetWrapper : IDisposable {
390         private object _unknown;
391         private UnsafeNativeMethods.IDBSchemaRowset _value;
392
393         internal IDBSchemaRowsetWrapper(object unknown) {
394             _unknown = unknown;
395             _value = (unknown as UnsafeNativeMethods.IDBSchemaRowset);
396         }
397
398         internal UnsafeNativeMethods.IDBSchemaRowset Value {
399             get {
400                 return _value;
401             }
402         }
403
404         public void Dispose() {
405             object unknown = _unknown;
406             _unknown = null;
407             _value = null;
408             if (null != unknown) {
409                 Marshal.ReleaseComObject(unknown);
410             }
411         }
412     }
413
414     internal struct IOpenRowsetWrapper : IDisposable {
415         private object _unknown;
416         private UnsafeNativeMethods.IOpenRowset _value;
417
418         internal IOpenRowsetWrapper(object unknown) {
419             _unknown = unknown;
420             _value = (unknown as UnsafeNativeMethods.IOpenRowset);
421             Debug.Assert(null != _value, "null IOpenRowsetWrapper");
422         }
423
424         internal UnsafeNativeMethods.IOpenRowset Value {
425             get {
426                 Debug.Assert(null != _value, "null IDBProperties");
427                 return _value;
428             }
429         }
430
431         public void Dispose() {
432             object unknown = _unknown;
433             _unknown = null;
434             _value = null;
435             if (null != unknown) {
436                 Marshal.ReleaseComObject(unknown);
437             }
438         }
439     }
440
441     internal struct ITransactionJoinWrapper : IDisposable {
442         private object _unknown;
443         private NativeMethods.ITransactionJoin _value;
444
445         internal ITransactionJoinWrapper(object unknown) {
446             _unknown = unknown;
447             _value = (unknown as NativeMethods.ITransactionJoin);
448         }
449
450         internal NativeMethods.ITransactionJoin Value {
451             get {
452                 return _value;
453             }
454         }
455
456         public void Dispose() {
457             object unknown = _unknown;
458             _unknown = null;
459             _value = null;
460             if (null != unknown) {
461                 Marshal.ReleaseComObject(unknown);
462             }
463         }
464     }
465 }