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
|
//------------------------------------------------------------------------------
// <copyright file="OdbcConnectionHandle.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <owner current="true" primary="true">[....]</owner>
// <owner current="true" primary="false">[....]</owner>
//------------------------------------------------------------------------------
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Runtime.ConstrainedExecution;
namespace System.Data.Odbc {
sealed internal class OdbcConnectionHandle : OdbcHandle {
private HandleState _handleState;
private enum HandleState {
Allocated = 0,
Connected = 1,
Transacted = 2,
TransactionInProgress = 3,
}
internal OdbcConnectionHandle(OdbcConnection connection, OdbcConnectionString constr, OdbcEnvironmentHandle environmentHandle) : base(ODBC32.SQL_HANDLE.DBC, environmentHandle) {
if(null == connection) {
throw ADP.ArgumentNull("connection");
}
if(null == constr) {
throw ADP.ArgumentNull("constr");
}
ODBC32.RetCode retcode;
//Set connection timeout (only before open).
//Note: We use login timeout since its odbc 1.0 option, instead of using
//connectiontimeout (which affects other things besides just login) and its
//a odbc 3.0 feature. The ConnectionTimeout on the managed providers represents
//the login timeout, nothing more.
int connectionTimeout = connection.ConnectionTimeout;
retcode = SetConnectionAttribute2(ODBC32.SQL_ATTR.LOGIN_TIMEOUT, (IntPtr)connectionTimeout, (Int32)ODBC32.SQL_IS.UINTEGER);
string connectionString = constr.UsersConnectionString(false);
// Connect to the driver. (Using the connection string supplied)
//Note: The driver doesn't filter out the password in the returned connection string
//so their is no need for us to obtain the returned connection string
// Prepare to handle a ThreadAbort Exception between SQLDriverConnectW and update of the state variables
retcode = Connect(connectionString);
connection.HandleError(this, retcode);
}
private ODBC32.RetCode AutoCommitOff() {
ODBC32.RetCode retcode;
Debug.Assert(HandleState.Connected <= _handleState, "AutoCommitOff while in wrong state?");
// Avoid runtime injected errors in the following block.
// must call SQLSetConnectAttrW and set _handleState
RuntimeHelpers.PrepareConstrainedRegions();
try {} finally {
retcode = UnsafeNativeMethods.SQLSetConnectAttrW(this, ODBC32.SQL_ATTR.AUTOCOMMIT, ODBC32.SQL_AUTOCOMMIT_OFF, (Int32)ODBC32.SQL_IS.UINTEGER);
switch(retcode) {
case ODBC32.RetCode.SUCCESS:
case ODBC32.RetCode.SUCCESS_WITH_INFO:
_handleState = HandleState.Transacted;
break;
}
}
ODBC.TraceODBC(3, "SQLSetConnectAttrW", retcode);
return retcode;
}
internal ODBC32.RetCode BeginTransaction(ref IsolationLevel isolevel) {
ODBC32.RetCode retcode = ODBC32.RetCode.SUCCESS;
ODBC32.SQL_ATTR isolationAttribute;
if(IsolationLevel.Unspecified != isolevel) {
ODBC32.SQL_TRANSACTION sql_iso;
switch(isolevel) {
case IsolationLevel.ReadUncommitted:
sql_iso = ODBC32.SQL_TRANSACTION.READ_UNCOMMITTED;
isolationAttribute = ODBC32.SQL_ATTR.TXN_ISOLATION;
break;
case IsolationLevel.ReadCommitted:
sql_iso = ODBC32.SQL_TRANSACTION.READ_COMMITTED;
isolationAttribute = ODBC32.SQL_ATTR.TXN_ISOLATION;
break;
case IsolationLevel.RepeatableRead:
sql_iso = ODBC32.SQL_TRANSACTION.REPEATABLE_READ;
isolationAttribute = ODBC32.SQL_ATTR.TXN_ISOLATION;
break;
case IsolationLevel.Serializable:
sql_iso = ODBC32.SQL_TRANSACTION.SERIALIZABLE;
isolationAttribute = ODBC32.SQL_ATTR.TXN_ISOLATION;
break;
case IsolationLevel.Snapshot:
sql_iso = ODBC32.SQL_TRANSACTION.SNAPSHOT;
// VSDD 414121: Snapshot isolation level must be set through SQL_COPT_SS_TXN_ISOLATION (http://msdn.microsoft.com/en-us/library/ms131709.aspx)
isolationAttribute = ODBC32.SQL_ATTR.SQL_COPT_SS_TXN_ISOLATION;
break;
case IsolationLevel.Chaos:
throw ODBC.NotSupportedIsolationLevel(isolevel);
default:
throw ADP.InvalidIsolationLevel(isolevel);
}
//Set the isolation level (unless its unspecified)
retcode = SetConnectionAttribute2(isolationAttribute, (IntPtr)sql_iso, (Int32)ODBC32.SQL_IS.INTEGER);
//Note: The Driver can return success_with_info to indicate it "rolled" the
//isolevel to the next higher value. If this is the case, we need to requery
//the value if th euser asks for it...
//We also still propagate the info, since it could be other info as well...
if(ODBC32.RetCode.SUCCESS_WITH_INFO == retcode) {
isolevel = IsolationLevel.Unspecified;
}
}
switch(retcode) {
case ODBC32.RetCode.SUCCESS:
case ODBC32.RetCode.SUCCESS_WITH_INFO:
//Turn off auto-commit (which basically starts the transaction)
retcode = AutoCommitOff();
_handleState = HandleState.TransactionInProgress;
break;
}
return retcode;
}
internal ODBC32.RetCode CompleteTransaction(short transactionOperation) {
bool mustRelease = false;
RuntimeHelpers.PrepareConstrainedRegions();
try {
DangerousAddRef(ref mustRelease);
ODBC32.RetCode retcode = CompleteTransaction(transactionOperation, base.handle);
return retcode;
}
finally {
if (mustRelease) {
DangerousRelease();
}
}
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
private ODBC32.RetCode CompleteTransaction(short transactionOperation, IntPtr handle) {
// must only call this code from ReleaseHandle or DangerousAddRef region
ODBC32.RetCode retcode = ODBC32.RetCode.SUCCESS;
// using ConstrainedRegions to make the native ODBC call and change the _handleState
RuntimeHelpers.PrepareConstrainedRegions();
try { } finally {
if (HandleState.TransactionInProgress == _handleState) {
retcode = UnsafeNativeMethods.SQLEndTran(HandleType, handle, transactionOperation);
if((ODBC32.RetCode.SUCCESS == retcode) || (ODBC32.RetCode.SUCCESS_WITH_INFO == retcode)) {
_handleState = HandleState.Transacted;
}
Bid.TraceSqlReturn("<odbc.SQLEndTran|API|ODBC|RET> %08X{SQLRETURN}\n", retcode);
}
if (HandleState.Transacted == _handleState) { // AutoCommitOn
retcode = UnsafeNativeMethods.SQLSetConnectAttrW(handle, ODBC32.SQL_ATTR.AUTOCOMMIT, ODBC32.SQL_AUTOCOMMIT_ON, (Int32)ODBC32.SQL_IS.UINTEGER);
_handleState = HandleState.Connected;
Bid.TraceSqlReturn("<odbc.SQLSetConnectAttr|API|ODBC|RET> %08X{SQLRETURN}\n", retcode);
}
}
//Overactive assert which fires if handle was allocated - but failed to connect to the server
//it can more legitmately fire if transaction failed to rollback - but there isn't much we can do in that situation
//Debug.Assert((HandleState.Connected == _handleState) || (HandleState.TransactionInProgress == _handleState), "not expected HandleState.Connected");
return retcode;
}
private ODBC32.RetCode Connect(string connectionString) {
Debug.Assert(HandleState.Allocated == _handleState, "SQLDriverConnect while in wrong state?");
ODBC32.RetCode retcode;
// Avoid runtime injected errors in the following block.
RuntimeHelpers.PrepareConstrainedRegions();
try {} finally {
short cbActualSize;
retcode = UnsafeNativeMethods.SQLDriverConnectW(this, ADP.PtrZero, connectionString, ODBC32.SQL_NTS, ADP.PtrZero, 0, out cbActualSize, (short)ODBC32.SQL_DRIVER.NOPROMPT);
switch(retcode) {
case ODBC32.RetCode.SUCCESS:
case ODBC32.RetCode.SUCCESS_WITH_INFO:
_handleState = HandleState.Connected;
break;
}
}
ODBC.TraceODBC(3, "SQLDriverConnectW", retcode);
return retcode;
}
override protected bool ReleaseHandle() {
// NOTE: The SafeHandle class guarantees this will be called exactly once and is non-interrutible.
ODBC32.RetCode retcode;
// must call complete the transaction rollback, change handle state, and disconnect the connection
retcode = CompleteTransaction(ODBC32.SQL_ROLLBACK, handle);
if ((HandleState.Connected == _handleState) || (HandleState.TransactionInProgress == _handleState)) {
retcode = UnsafeNativeMethods.SQLDisconnect(handle);
_handleState = HandleState.Allocated;
Bid.TraceSqlReturn("<odbc.SQLDisconnect|API|ODBC|RET> %08X{SQLRETURN}\n", retcode);
}
Debug.Assert(HandleState.Allocated == _handleState, "not expected HandleState.Allocated");
return base.ReleaseHandle();
}
internal ODBC32.RetCode GetConnectionAttribute(ODBC32.SQL_ATTR attribute, byte[] buffer, out int cbActual) {
ODBC32.RetCode retcode = UnsafeNativeMethods.SQLGetConnectAttrW(this, attribute, buffer, buffer.Length, out cbActual);
Bid.Trace("<odbc.SQLGetConnectAttr|ODBC> SQLRETURN=%d, Attribute=%d, BufferLength=%d, StringLength=%d\n", (int)retcode, (int)attribute, buffer.Length, (int)cbActual);
return retcode;
}
internal ODBC32.RetCode GetFunctions(ODBC32.SQL_API fFunction, out Int16 fExists) {
ODBC32.RetCode retcode = UnsafeNativeMethods.SQLGetFunctions(this, fFunction, out fExists);
ODBC.TraceODBC(3, "SQLGetFunctions", retcode);
return retcode;
}
internal ODBC32.RetCode GetInfo2(ODBC32.SQL_INFO info, byte[] buffer, out short cbActual) {
ODBC32.RetCode retcode = UnsafeNativeMethods.SQLGetInfoW(this, info, buffer, checked((short)buffer.Length), out cbActual);
Bid.Trace("<odbc.SQLGetInfo|ODBC> SQLRETURN=%d, InfoType=%d, BufferLength=%d, StringLength=%d\n", (int)retcode, (int)info, buffer.Length, (int)cbActual);
return retcode;
}
internal ODBC32.RetCode GetInfo1(ODBC32.SQL_INFO info, byte[] buffer) {
ODBC32.RetCode retcode = UnsafeNativeMethods.SQLGetInfoW(this, info, buffer, checked((short)buffer.Length), ADP.PtrZero);
Bid.Trace("<odbc.SQLGetInfo|ODBC> SQLRETURN=%d, InfoType=%d, BufferLength=%d\n", (int)retcode, (int)info, buffer.Length);
return retcode;
}
internal ODBC32.RetCode SetConnectionAttribute2(ODBC32.SQL_ATTR attribute, IntPtr value, Int32 length) {
ODBC32.RetCode retcode = UnsafeNativeMethods.SQLSetConnectAttrW(this, attribute, value, length);
ODBC.TraceODBC(3, "SQLSetConnectAttrW", retcode);
return retcode;
}
internal ODBC32.RetCode SetConnectionAttribute3(ODBC32.SQL_ATTR attribute, string buffer, Int32 length) {
ODBC32.RetCode retcode = UnsafeNativeMethods.SQLSetConnectAttrW(this, attribute, buffer, length);
Bid.Trace("<odbc.SQLSetConnectAttr|ODBC> SQLRETURN=%d, Attribute=%d, BufferLength=%d\n", (int)retcode, (int)attribute, buffer.Length);
return retcode;
}
internal ODBC32.RetCode SetConnectionAttribute4(ODBC32.SQL_ATTR attribute, System.Transactions.IDtcTransaction transaction, Int32 length) {
ODBC32.RetCode retcode = UnsafeNativeMethods.SQLSetConnectAttrW(this, attribute, transaction, length);
ODBC.TraceODBC(3, "SQLSetConnectAttrW", retcode);
return retcode;
}
}
}
|