File: OleDbWrapper.cs

package info (click to toggle)
mono 6.12.0.199%2Bdfsg-6
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 1,296,836 kB
  • sloc: cs: 11,181,803; xml: 2,850,076; ansic: 699,709; cpp: 123,344; perl: 59,361; javascript: 30,841; asm: 21,853; makefile: 20,405; sh: 15,009; python: 4,839; pascal: 925; sql: 859; sed: 16; php: 1
file content (465 lines) | stat: -rw-r--r-- 24,697 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
//------------------------------------------------------------------------------
// <copyright file="OleDbWrapper.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>
//------------------------------------------------------------------------------

namespace System.Data.OleDb {

    using System;
    using System.Data.Common;
    using System.Data.ProviderBase;
    using System.Diagnostics;
    using System.Runtime.CompilerServices;
    using System.Runtime.ConstrainedExecution;
    using System.Runtime.InteropServices;

    // SafeHandle wrapper around 'DataLinks' object which pools the native OLE DB providers.
    // expect 1 per app-domain
    sealed internal class OleDbServicesWrapper : WrappedIUnknown {

        // we expect to store IDataInitialize instance pointer in base.handle

        // since we only have one DataLinks object, caching the delegate here is valid as long we
        // maintain an AddRef which is also the lifetime of this class instance
        private UnsafeNativeMethods.IDataInitializeGetDataSource DangerousIDataInitializeGetDataSource;

        // DataLinks (the unknown parameter) is created via Activator.CreateInstance outside of the SafeHandle
        internal OleDbServicesWrapper(object unknown) : base() {
            if (null != unknown) {
                RuntimeHelpers.PrepareConstrainedRegions();
                try { } finally {
                    // store the QI result for IID_IDataInitialize
                    base.handle = Marshal.GetComInterfaceForObject(unknown, typeof(UnsafeNativeMethods.IDataInitialize)); // 
                }
                // native COM rules are the QI result is the 'this' pointer
                // the pointer stored at that location is the vtable
                // since IDataInitialize is a public,shipped COM interface, its layout will not change (ever)
                IntPtr vtable = Marshal.ReadIntPtr(base.handle, 0);
                IntPtr method = Marshal.ReadIntPtr(vtable, 3 * IntPtr.Size); // GetDataSource is the 4'th vtable entry
                DangerousIDataInitializeGetDataSource = (UnsafeNativeMethods.IDataInitializeGetDataSource)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IDataInitializeGetDataSource));
            }
        }

        internal void GetDataSource(OleDbConnectionString constr, ref DataSourceWrapper datasrcWrapper) {
            OleDbHResult hr;
            UnsafeNativeMethods.IDataInitializeGetDataSource GetDataSource = DangerousIDataInitializeGetDataSource;
            bool mustRelease = false;
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                DangerousAddRef(ref mustRelease);

                // this is the string that DataLinks / OLE DB Services will use to create the provider
                string connectionString = constr.ActualConnectionString;
                
                // base.handle is the 'this' pointer for making the COM call to GetDataSource
                // the datasrcWrapper will store the IID_IDBInitialize pointer
                // call IDataInitiailze::GetDataSource via the delegate
                hr = GetDataSource(base.handle, IntPtr.Zero, ODB.CLSCTX_ALL, connectionString, ref ODB.IID_IDBInitialize, ref datasrcWrapper);
            }
            finally {
                if (mustRelease) {
                    DangerousRelease();
                }
            }
            if (hr < 0) { // ignore infomsg
                if (OleDbHResult.REGDB_E_CLASSNOTREG == hr) {
                    throw ODB.ProviderUnavailable(constr.Provider, null);
                }
                Exception e = OleDbConnection.ProcessResults(hr, null, null);
                Debug.Assert(null != e, "CreateProviderError");
                throw e;
            }
            else if (datasrcWrapper.IsInvalid) {
                SafeNativeMethods.Wrapper.ClearErrorInfo();
                throw ODB.ProviderUnavailable(constr.Provider, null);
            }
            Debug.Assert(!datasrcWrapper.IsInvalid, "bad DataSource");
        }
    }

    // SafeHandle wrapper around 'Data Source' object which represents the connection
    // expect 1 per OleDbConnectionInternal
    sealed internal class DataSourceWrapper : WrappedIUnknown {

        // we expect to store IDBInitialize instance pointer in base.handle

        // construct a DataSourceWrapper and used as a ref parameter to GetDataSource
        internal DataSourceWrapper() : base() {
        }

        internal OleDbHResult InitializeAndCreateSession(OleDbConnectionString constr, ref SessionWrapper sessionWrapper) {
            OleDbHResult hr;
            bool mustRelease = false;
            IntPtr idbCreateSession = IntPtr.Zero;
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                DangerousAddRef(ref mustRelease);
                
                // native COM rules are the QI result is the 'this' pointer
                // the pointer stored at that location is the vtable
                // since IUnknown is a public,shipped COM interface, its layout will not change (ever)
                IntPtr vtable = Marshal.ReadIntPtr(base.handle, 0);
                IntPtr method = Marshal.ReadIntPtr(vtable, 0);
                
                // we cache the QueryInterface delegate to prevent recreating it on every call
                UnsafeNativeMethods.IUnknownQueryInterface QueryInterface = constr.DangerousDataSourceIUnknownQueryInterface;

                // since the delegate lifetime is longer than the original instance used to create it
                // we double check before each usage to verify the delegates function pointer
                if ((null == QueryInterface) || (method != Marshal.GetFunctionPointerForDelegate(QueryInterface))) {
                    QueryInterface = (UnsafeNativeMethods.IUnknownQueryInterface)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IUnknownQueryInterface));
                    constr.DangerousDataSourceIUnknownQueryInterface = QueryInterface;
                }
                
                // native COM rules are the QI result is the 'this' pointer
                // the pointer stored at that location is the vtable
                // since IDBInitialize is a public,shipped COM interface, its layout will not change (ever)
                vtable = Marshal.ReadIntPtr(base.handle, 0);
                method = Marshal.ReadIntPtr(vtable, 3 * IntPtr.Size);  // Initialize is the 4'th vtable entry

                // we cache the Initialize delegate to prevent recreating it on every call
                UnsafeNativeMethods.IDBInitializeInitialize Initialize = constr.DangerousIDBInitializeInitialize;
                
                // since the delegate lifetime is longer than the original instance used to create it
                // we double check before each usage to verify the delegates function pointer
                if ((null == Initialize) || (method != Marshal.GetFunctionPointerForDelegate(Initialize))) {
                    Initialize = (UnsafeNativeMethods.IDBInitializeInitialize)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IDBInitializeInitialize));
                    constr.DangerousIDBInitializeInitialize = Initialize;
                }

                // call IDBInitialize::Initialize via the delegate
                hr = Initialize(base.handle);

                // we don't ever expect DB_E_ALREADYINITIALIZED, but since we checked in V1.0 - its propagated along
                if ((0 <= hr) || (OleDbHResult.DB_E_ALREADYINITIALIZED == hr)) {
                    
                    // call IUnknown::QueryInterface via the delegate
                    hr = (OleDbHResult)QueryInterface(base.handle, ref ODB.IID_IDBCreateSession, ref idbCreateSession);
                    if ((0 <= hr) && (IntPtr.Zero != idbCreateSession)) {
                        
                        // native COM rules are the QI result is the 'this' pointer
                        // the pointer stored at that location is the vtable
                        // since IDBCreateSession is a public,shipped COM interface, its layout will not change (ever)
                        vtable = Marshal.ReadIntPtr(idbCreateSession, 0);
                        method = Marshal.ReadIntPtr(vtable, 3 * IntPtr.Size);  // CreateSession is the 4'th vtable entry
                        
                        UnsafeNativeMethods.IDBCreateSessionCreateSession CreateSession = constr.DangerousIDBCreateSessionCreateSession;                                

                        // since the delegate lifetime is longer than the original instance used to create it
                        // we double check before each usage to verify the delegates function pointer
                        if ((null == CreateSession) || (method != Marshal.GetFunctionPointerForDelegate(CreateSession))) {
                            CreateSession = (UnsafeNativeMethods.IDBCreateSessionCreateSession)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IDBCreateSessionCreateSession));
                            constr.DangerousIDBCreateSessionCreateSession = CreateSession;
                        }

                        // if I have a delegate for CreateCommand directly ask for IDBCreateCommand
                        if (null != constr.DangerousIDBCreateCommandCreateCommand) {
                            // call IDBCreateSession::CreateSession via the delegate directly for IDBCreateCommand
                            hr = CreateSession(idbCreateSession, IntPtr.Zero, ref ODB.IID_IDBCreateCommand, ref sessionWrapper);
                            if ((0 <= hr) && !sessionWrapper.IsInvalid) {                                        
                                // double check the cached delegate is correct
                                sessionWrapper.VerifyIDBCreateCommand(constr);
                            }
                        }
                        else {
                            // otherwise ask for IUnknown (it may be first time usage or IDBCreateCommand not supported)
                            hr = CreateSession(idbCreateSession, IntPtr.Zero, ref ODB.IID_IUnknown, ref sessionWrapper);
                            if ((0 <= hr) && !sessionWrapper.IsInvalid) {
                                // and check support for IDBCreateCommand and create delegate for CreateCommand
                                sessionWrapper.QueryInterfaceIDBCreateCommand(constr);
                            }
                        }
                    }
                }
            }
            finally {
                if (IntPtr.Zero != idbCreateSession) {
                    // release the QI for IDBCreateSession
                    Marshal.Release(idbCreateSession);
                }
                if (mustRelease) {
                    // release the AddRef on DataLinks
                    DangerousRelease();
                }
            }
            return hr;
        }

        internal IDBInfoWrapper IDBInfo(OleDbConnectionInternal connection) {
            Bid.Trace("<oledb.IUnknown.QueryInterface|API|OLEDB|datasource> %d#, IDBInfo\n", connection.ObjectID);
            return new IDBInfoWrapper(ComWrapper());
        }
        internal IDBPropertiesWrapper IDBProperties(OleDbConnectionInternal connection) {
            Bid.Trace("<oledb.IUnknown.QueryInterface|API|OLEDB|datasource> %d#, IDBProperties\n", connection.ObjectID);
            return new IDBPropertiesWrapper(ComWrapper());
        }
    }


    // SafeHandle wrapper around 'Session' object which represents the session on the connection
    // expect 1 per OleDbConnectionInternal
    sealed internal class SessionWrapper : WrappedIUnknown {
    
        // base.handle will either reference the IUnknown interface or IDBCreateCommand interface
        // if OleDbConnectionString.DangerousIDBCreateCommandCreateCommand exists
        // the CreateSession call will ask directly for the optional IDBCreateCommand
        // otherwise it is the first call or known that IDBCreateCommand isn't supported

        // we cache the DangerousIDBCreateCommandCreateCommand (expecting base.handle to be IDBCreateCommand)
        // since we maintain an AddRef on IDBCreateCommand it is safe to use the delegate without rechecking its function pointer
        private UnsafeNativeMethods.IDBCreateCommandCreateCommand DangerousIDBCreateCommandCreateCommand;

        internal SessionWrapper() : base() {
        }

        // if OleDbConnectionString.DangerousIDBCreateCommandCreateCommand does not exist
        // this method will be called to query for IDBCreateCommand (and cache that interface pointer)
        // or it will be known that IDBCreateCommand is not supported
        internal void QueryInterfaceIDBCreateCommand(OleDbConnectionString constr) {
            // DangerousAddRef/DangerousRelease are not neccessary here in the current implementation
            // only used from within OleDbConnectionInternal.ctor->DataSourceWrapper.InitializeAndCreateSession

            // caching the fact if we have queried for IDBCreateCommand or not
            // the command object is not supported by all providers, they would use IOpenRowset
            // SQLBU:446413, SQLHotfix:1138 -- added extra if condition
            // If constr.HaveQueriedForCreateCommand is false, this is the first time through this method and we need to set up the cache for sure.
            // 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.
            // 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,
            //     we don't know if another thread set the flag, or if the provider really doesn't support commands.
            // If constr.HaveQueriedForCreateCommand is true and constr.DangerousIDBCreateCommandCreateCommand is not null, that means that another thread has set it after we
            //     determined we needed to call QueryInterfaceIDBCreateCommand -- otherwise we would have called VerifyIDBCreateCommand instead 
            // 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            
            if (!constr.HaveQueriedForCreateCommand || (null != constr.DangerousIDBCreateCommandCreateCommand)) {
                IntPtr idbCreateCommand = IntPtr.Zero;
                RuntimeHelpers.PrepareConstrainedRegions();
                try {
                    // native COM rules are the QI result is the 'this' pointer
                    // the pointer stored at that location is the vtable
                    // since IUnknown is a public,shipped COM interface, its layout will not change (ever)
                    IntPtr vtable = Marshal.ReadIntPtr(base.handle, 0);
                    IntPtr method = Marshal.ReadIntPtr(vtable, 0);
                    UnsafeNativeMethods.IUnknownQueryInterface QueryInterface = (UnsafeNativeMethods.IUnknownQueryInterface)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IUnknownQueryInterface));

                    int hresult = QueryInterface(base.handle, ref ODB.IID_IDBCreateCommand, ref idbCreateCommand);  // 
                    if ((0 <= hresult) && (IntPtr.Zero != idbCreateCommand)) {
                        vtable = Marshal.ReadIntPtr(idbCreateCommand, 0);
                        method = Marshal.ReadIntPtr(vtable, 3 * IntPtr.Size);

                        DangerousIDBCreateCommandCreateCommand = (UnsafeNativeMethods.IDBCreateCommandCreateCommand)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IDBCreateCommandCreateCommand));
                        constr.DangerousIDBCreateCommandCreateCommand = DangerousIDBCreateCommandCreateCommand;                        
                    }
                    
                    // caching the fact that we have queried for IDBCreateCommand
                    constr.HaveQueriedForCreateCommand = true;
                }
                finally {
                    if (IntPtr.Zero != idbCreateCommand) {
                        IntPtr ptr = base.handle;
                        base.handle = idbCreateCommand;
                        Marshal.Release(ptr);
                    }
                }
            }
            //else if constr.HaveQueriedForCreateCommand is true and constr.DangerousIDBCreateCommandCreateCommand is still null, it means that this provider doesn't support commands
        }

        internal void VerifyIDBCreateCommand(OleDbConnectionString constr) {
            // DangerousAddRef/DangerousRelease are not neccessary here in the current implementation
            // only used from within OleDbConnectionInternal.ctor->DataSourceWrapper.InitializeAndCreateSession

            Debug.Assert(constr.HaveQueriedForCreateCommand, "expected HaveQueriedForCreateCommand");
            Debug.Assert(null != constr.DangerousIDBCreateCommandCreateCommand, "expected DangerousIDBCreateCommandCreateCommand");
            
            // native COM rules are the QI result is the 'this' pointer
            // the pointer stored at that location is the vtable
            // since IDBCreateCommand is a public,shipped COM interface, its layout will not change (ever)
            IntPtr vtable = Marshal.ReadIntPtr(base.handle, 0);
            IntPtr method = Marshal.ReadIntPtr(vtable, 3 * IntPtr.Size);

            // obtain the cached delegate to be cached on this instance
            UnsafeNativeMethods.IDBCreateCommandCreateCommand CreateCommand = constr.DangerousIDBCreateCommandCreateCommand;

            // since the delegate lifetime is longer than the original instance used to create it
            // we double check before each usage to verify the delegates function pointer
            if ((null == CreateCommand) || (method != Marshal.GetFunctionPointerForDelegate(CreateCommand))) {
                CreateCommand = (UnsafeNativeMethods.IDBCreateCommandCreateCommand)Marshal.GetDelegateForFunctionPointer(method, typeof(UnsafeNativeMethods.IDBCreateCommandCreateCommand));
                constr.DangerousIDBCreateCommandCreateCommand = CreateCommand;
            }
            // since this instance can be used to create multiple commands
            // cache it on the class so that the function pointer doesn't have to be validated every time
            DangerousIDBCreateCommandCreateCommand = CreateCommand;
        }

        internal OleDbHResult CreateCommand(ref object icommandText) {
            // if (null == CreateCommand), the IDBCreateCommand isn't supported - aka E_NOINTERFACE
            OleDbHResult hr = OleDbHResult.E_NOINTERFACE;
            UnsafeNativeMethods.IDBCreateCommandCreateCommand CreateCommand = DangerousIDBCreateCommandCreateCommand;
            if (null != CreateCommand) {
                bool mustRelease = false;
                RuntimeHelpers.PrepareConstrainedRegions();
                try {
                    DangerousAddRef(ref mustRelease);

                    // call IDBCreateCommand::CreateCommand via the delegate directly for IDBCreateCommand
                    hr = CreateCommand(base.handle, IntPtr.Zero, ref ODB.IID_ICommandText, ref icommandText);
                }
                finally {
                    if (mustRelease) {
                        DangerousRelease();
                    }
                }
            }
            return hr;
        }

        internal IDBSchemaRowsetWrapper IDBSchemaRowset(OleDbConnectionInternal connection) {
            Bid.Trace("<oledb.IUnknown.QueryInterface|API|OLEDB|session> %d#, IDBSchemaRowset\n", connection.ObjectID);
            return new IDBSchemaRowsetWrapper(ComWrapper());
        }

        internal IOpenRowsetWrapper IOpenRowset(OleDbConnectionInternal connection) {
            Bid.Trace("<oledb.IUnknown.QueryInterface|API|OLEDB|session> %d#, IOpenRowset\n", connection.ObjectID);
            return new IOpenRowsetWrapper(ComWrapper());
        }

        internal ITransactionJoinWrapper ITransactionJoin(OleDbConnectionInternal connection) {
            Bid.Trace("<oledb.IUnknown.QueryInterface|API|OLEDB|session> %d#, ITransactionJoin\n", connection.ObjectID);
            return new ITransactionJoinWrapper(ComWrapper());
        }
    }

    // unable to use generics bacause (unknown as T) doesn't compile
    internal struct IDBInfoWrapper : IDisposable {
        // _unknown must be tracked because the _value may not exist,
        // yet _unknown must still be released
        private object _unknown;
        private UnsafeNativeMethods.IDBInfo _value;

        internal IDBInfoWrapper(object unknown) {
            _unknown = unknown;
            _value = (unknown as UnsafeNativeMethods.IDBInfo);
        }

        internal UnsafeNativeMethods.IDBInfo Value {
            get {
                return _value;
            }
        }

        public void Dispose() {
            object unknown = _unknown;
            _unknown = null;
            _value = null;
            if (null != unknown) {
                Marshal.ReleaseComObject(unknown);
            }
        }
    }

    internal struct IDBPropertiesWrapper : IDisposable {
        private object _unknown;
        private UnsafeNativeMethods.IDBProperties _value;

        internal IDBPropertiesWrapper(object unknown) {
            _unknown = unknown;
            _value = (unknown as UnsafeNativeMethods.IDBProperties);
            Debug.Assert(null != _value, "null IDBProperties");
        }

        internal UnsafeNativeMethods.IDBProperties Value {
            get {
                Debug.Assert(null != _value, "null IDBProperties");
                return _value;
            }
        }

        public void Dispose() {
            object unknown = _unknown;
            _unknown = null;
            _value = null;
            if (null != unknown) {
                Marshal.ReleaseComObject(unknown);
            }
        }
    }

    internal struct IDBSchemaRowsetWrapper : IDisposable {
        private object _unknown;
        private UnsafeNativeMethods.IDBSchemaRowset _value;

        internal IDBSchemaRowsetWrapper(object unknown) {
            _unknown = unknown;
            _value = (unknown as UnsafeNativeMethods.IDBSchemaRowset);
        }

        internal UnsafeNativeMethods.IDBSchemaRowset Value {
            get {
                return _value;
            }
        }

        public void Dispose() {
            object unknown = _unknown;
            _unknown = null;
            _value = null;
            if (null != unknown) {
                Marshal.ReleaseComObject(unknown);
            }
        }
    }

    internal struct IOpenRowsetWrapper : IDisposable {
        private object _unknown;
        private UnsafeNativeMethods.IOpenRowset _value;

        internal IOpenRowsetWrapper(object unknown) {
            _unknown = unknown;
            _value = (unknown as UnsafeNativeMethods.IOpenRowset);
            Debug.Assert(null != _value, "null IOpenRowsetWrapper");
        }

        internal UnsafeNativeMethods.IOpenRowset Value {
            get {
                Debug.Assert(null != _value, "null IDBProperties");
                return _value;
            }
        }

        public void Dispose() {
            object unknown = _unknown;
            _unknown = null;
            _value = null;
            if (null != unknown) {
                Marshal.ReleaseComObject(unknown);
            }
        }
    }

    internal struct ITransactionJoinWrapper : IDisposable {
        private object _unknown;
        private NativeMethods.ITransactionJoin _value;

        internal ITransactionJoinWrapper(object unknown) {
            _unknown = unknown;
            _value = (unknown as NativeMethods.ITransactionJoin);
        }

        internal NativeMethods.ITransactionJoin Value {
            get {
                return _value;
            }
        }

        public void Dispose() {
            object unknown = _unknown;
            _unknown = null;
            _value = null;
            if (null != unknown) {
                Marshal.ReleaseComObject(unknown);
            }
        }
    }
}