e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
883 lines
35 KiB
C#
883 lines
35 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="OdbcConnection.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.Data.ProviderBase;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security;
|
|
using System.Security.Permissions;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using SysTx = System.Transactions;
|
|
|
|
namespace System.Data.Odbc {
|
|
|
|
[DefaultEvent("InfoMessage")]
|
|
public sealed partial class OdbcConnection : DbConnection, ICloneable {
|
|
private int connectionTimeout = ADP.DefaultConnectionTimeout;
|
|
|
|
private OdbcInfoMessageEventHandler infoMessageEventHandler;
|
|
private WeakReference weakTransaction;
|
|
|
|
private OdbcConnectionHandle _connectionHandle;
|
|
private ConnectionState _extraState; // extras, like Executing and Fetching, that we add to the State.
|
|
|
|
public OdbcConnection(string connectionString) : this() {
|
|
ConnectionString = connectionString;
|
|
}
|
|
|
|
private OdbcConnection(OdbcConnection connection) : this() { // Clone
|
|
CopyFrom(connection);
|
|
connectionTimeout = connection.connectionTimeout;
|
|
}
|
|
|
|
internal OdbcConnectionHandle ConnectionHandle {
|
|
get {
|
|
return _connectionHandle;
|
|
}
|
|
set {
|
|
Debug.Assert(null == _connectionHandle, "reopening a connection?");
|
|
_connectionHandle = value;
|
|
}
|
|
}
|
|
|
|
[
|
|
DefaultValue(""),
|
|
Editor("Microsoft.VSDesigner.Data.Odbc.Design.OdbcConnectionStringEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing),
|
|
#pragma warning disable 618 // ignore obsolete warning about RecommendedAsConfigurable to use SettingsBindableAttribute
|
|
RecommendedAsConfigurable(true),
|
|
#pragma warning restore 618
|
|
SettingsBindableAttribute(true),
|
|
RefreshProperties(RefreshProperties.All),
|
|
ResCategoryAttribute(Res.DataCategory_Data),
|
|
ResDescriptionAttribute(Res.OdbcConnection_ConnectionString),
|
|
]
|
|
override public string ConnectionString {
|
|
get {
|
|
return ConnectionString_Get();
|
|
}
|
|
set {
|
|
ConnectionString_Set(value);
|
|
}
|
|
}
|
|
|
|
[
|
|
DefaultValue(ADP.DefaultConnectionTimeout),
|
|
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
|
|
ResCategoryAttribute(Res.DataCategory_Data),
|
|
ResDescriptionAttribute(Res.OdbcConnection_ConnectionTimeout),
|
|
]
|
|
new public int ConnectionTimeout {
|
|
get {
|
|
return connectionTimeout;
|
|
}
|
|
set {
|
|
if (value < 0)
|
|
throw ODBC.NegativeArgument();
|
|
if (IsOpen)
|
|
throw ODBC.CantSetPropertyOnOpenConnection();
|
|
connectionTimeout = value;
|
|
}
|
|
}
|
|
|
|
[
|
|
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
|
|
ResDescriptionAttribute(Res.OdbcConnection_Database),
|
|
]
|
|
override public string Database {
|
|
get {
|
|
if (IsOpen && !ProviderInfo.NoCurrentCatalog) {
|
|
//Note: CURRENT_CATALOG may not be supported by the current driver. In which
|
|
//case we ignore any error (without throwing), and just return string.empty.
|
|
//As we really don't want people to have to have try/catch around simple properties
|
|
return GetConnectAttrString(ODBC32.SQL_ATTR.CURRENT_CATALOG);
|
|
}
|
|
//Database is not available before open, and its not worth parsing the
|
|
//connection string over.
|
|
return String.Empty;
|
|
}
|
|
}
|
|
|
|
[
|
|
Browsable(false),
|
|
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
|
|
ResDescriptionAttribute(Res.OdbcConnection_DataSource),
|
|
]
|
|
override public string DataSource {
|
|
get {
|
|
if (IsOpen) {
|
|
// note: This will return an empty string if the driver keyword was used to connect
|
|
// see ODBC3.0 Programmers Reference, SQLGetInfo
|
|
//
|
|
return GetInfoStringUnhandled(ODBC32.SQL_INFO.SERVER_NAME, true);
|
|
}
|
|
return String.Empty;
|
|
}
|
|
}
|
|
|
|
[
|
|
Browsable(false),
|
|
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
|
|
ResDescriptionAttribute(Res.OdbcConnection_ServerVersion),
|
|
]
|
|
override public string ServerVersion {
|
|
get {
|
|
return InnerConnection.ServerVersion;
|
|
}
|
|
}
|
|
|
|
[
|
|
Browsable(false),
|
|
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
|
|
ResDescriptionAttribute(Res.DbConnection_State),
|
|
]
|
|
override public ConnectionState State {
|
|
get {
|
|
return InnerConnection.State;
|
|
}
|
|
}
|
|
|
|
internal OdbcConnectionPoolGroupProviderInfo ProviderInfo {
|
|
get {
|
|
Debug.Assert(null != this.PoolGroup, "PoolGroup must never be null when accessing ProviderInfo");
|
|
return (OdbcConnectionPoolGroupProviderInfo)this.PoolGroup.ProviderInfo;
|
|
}
|
|
}
|
|
|
|
internal ConnectionState InternalState {
|
|
get {
|
|
return (this.State | _extraState);
|
|
}
|
|
}
|
|
|
|
internal bool IsOpen {
|
|
get {
|
|
return (InnerConnection is OdbcConnectionOpen);
|
|
}
|
|
}
|
|
|
|
internal OdbcTransaction LocalTransaction {
|
|
|
|
get {
|
|
OdbcTransaction result = null;
|
|
if (null != weakTransaction) {
|
|
result = ((OdbcTransaction)weakTransaction.Target);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
set {
|
|
weakTransaction = null;
|
|
|
|
if (null != value) {
|
|
weakTransaction = new WeakReference((OdbcTransaction)value);
|
|
}
|
|
}
|
|
}
|
|
|
|
[
|
|
Browsable(false),
|
|
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
|
|
ResDescriptionAttribute(Res.OdbcConnection_Driver),
|
|
]
|
|
public string Driver {
|
|
get {
|
|
if(IsOpen) {
|
|
if (ProviderInfo.DriverName == null) {
|
|
ProviderInfo.DriverName = GetInfoStringUnhandled(ODBC32.SQL_INFO.DRIVER_NAME);
|
|
}
|
|
return ProviderInfo.DriverName;
|
|
}
|
|
return ADP.StrEmpty;
|
|
}
|
|
}
|
|
|
|
internal bool IsV3Driver {
|
|
get {
|
|
if (ProviderInfo.DriverVersion == null) {
|
|
ProviderInfo.DriverVersion = GetInfoStringUnhandled(ODBC32.SQL_INFO.DRIVER_ODBC_VER);
|
|
// protected against null and index out of range. Number cannot be bigger than 99
|
|
if (ProviderInfo.DriverVersion != null && ProviderInfo.DriverVersion.Length>=2) {
|
|
try { // mdac 89269: driver may return malformatted string
|
|
ProviderInfo.IsV3Driver = (int.Parse(ProviderInfo.DriverVersion.Substring(0,2), CultureInfo.InvariantCulture) >= 3);
|
|
}
|
|
catch (System.FormatException e) {
|
|
ProviderInfo.IsV3Driver = false;
|
|
ADP.TraceExceptionWithoutRethrow(e);
|
|
}
|
|
}
|
|
else {
|
|
ProviderInfo.DriverVersion = "";
|
|
}
|
|
}
|
|
return ProviderInfo.IsV3Driver;
|
|
}
|
|
}
|
|
|
|
[
|
|
ResCategoryAttribute(Res.DataCategory_InfoMessage),
|
|
ResDescriptionAttribute(Res.DbConnection_InfoMessage),
|
|
]
|
|
public event OdbcInfoMessageEventHandler InfoMessage {
|
|
add {
|
|
infoMessageEventHandler += value;
|
|
}
|
|
remove {
|
|
infoMessageEventHandler -= value;
|
|
}
|
|
}
|
|
|
|
internal char EscapeChar(string method) {
|
|
CheckState(method);
|
|
if (!ProviderInfo.HasEscapeChar) {
|
|
string escapeCharString;
|
|
escapeCharString = GetInfoStringUnhandled(ODBC32.SQL_INFO.SEARCH_PATTERN_ESCAPE);
|
|
Debug.Assert((escapeCharString.Length <= 1), "Can't handle multichar quotes");
|
|
ProviderInfo.EscapeChar = (escapeCharString.Length==1)?escapeCharString[0]:QuoteChar(method)[0];
|
|
}
|
|
return ProviderInfo.EscapeChar;
|
|
}
|
|
|
|
internal string QuoteChar(string method) {
|
|
CheckState(method);
|
|
if (!ProviderInfo.HasQuoteChar) {
|
|
string quoteCharString;
|
|
quoteCharString = GetInfoStringUnhandled(ODBC32.SQL_INFO.IDENTIFIER_QUOTE_CHAR);
|
|
Debug.Assert((quoteCharString.Length <= 1), "Can't handle multichar quotes");
|
|
ProviderInfo.QuoteChar = (1==quoteCharString.Length)?quoteCharString:"\0";
|
|
}
|
|
return ProviderInfo.QuoteChar;
|
|
}
|
|
|
|
new public OdbcTransaction BeginTransaction() {
|
|
return BeginTransaction(IsolationLevel.Unspecified);
|
|
}
|
|
|
|
new public OdbcTransaction BeginTransaction(IsolationLevel isolevel) {
|
|
return (OdbcTransaction)InnerConnection.BeginTransaction(isolevel);
|
|
}
|
|
|
|
private void RollbackDeadTransaction() {
|
|
WeakReference weak = weakTransaction;
|
|
if ((null != weak) && !weak.IsAlive) {
|
|
weakTransaction = null;
|
|
ConnectionHandle.CompleteTransaction(ODBC32.SQL_ROLLBACK);
|
|
}
|
|
}
|
|
|
|
override public void ChangeDatabase(string value) {
|
|
InnerConnection.ChangeDatabase(value);
|
|
}
|
|
|
|
internal void CheckState(string method) {
|
|
ConnectionState state = InternalState;
|
|
if (ConnectionState.Open != state) {
|
|
throw ADP.OpenConnectionRequired(method, state); // MDAC 68323
|
|
}
|
|
}
|
|
|
|
object ICloneable.Clone() {
|
|
OdbcConnection clone = new OdbcConnection(this);
|
|
Bid.Trace("<odbc.OdbcConnection.Clone|API> %d#, clone=%d#\n", ObjectID, clone.ObjectID);
|
|
return clone;
|
|
}
|
|
|
|
internal bool ConnectionIsAlive(Exception innerException) {
|
|
if (IsOpen) {
|
|
if (!ProviderInfo.NoConnectionDead) {
|
|
int isDead = GetConnectAttr(ODBC32.SQL_ATTR.CONNECTION_DEAD, ODBC32.HANDLER.IGNORE);
|
|
if (ODBC32.SQL_CD_TRUE == isDead) {
|
|
Close();
|
|
throw ADP.ConnectionIsDisabled(innerException);
|
|
}
|
|
}
|
|
// else connection is still alive or attribute not supported
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
new public OdbcCommand CreateCommand() {
|
|
return new OdbcCommand(String.Empty, this);
|
|
}
|
|
|
|
internal OdbcStatementHandle CreateStatementHandle() {
|
|
return new OdbcStatementHandle(ConnectionHandle);
|
|
}
|
|
|
|
override public void Close() {
|
|
InnerConnection.CloseConnection(this, ConnectionFactory);
|
|
|
|
OdbcConnectionHandle connectionHandle = _connectionHandle;
|
|
|
|
if (null != connectionHandle) {
|
|
_connectionHandle = null;
|
|
|
|
// If there is a pending transaction, automatically rollback.
|
|
WeakReference weak = this.weakTransaction;
|
|
if (null != weak) {
|
|
this.weakTransaction = null;
|
|
IDisposable transaction = weak.Target as OdbcTransaction;
|
|
if ((null != transaction) && weak.IsAlive) {
|
|
transaction.Dispose();
|
|
}
|
|
// else transaction will be rolled back when handle is disposed
|
|
}
|
|
connectionHandle.Dispose();
|
|
}
|
|
}
|
|
|
|
private void DisposeMe(bool disposing) { // MDAC 65459
|
|
}
|
|
|
|
public void EnlistDistributedTransaction(System.EnterpriseServices.ITransaction transaction) {
|
|
EnlistDistributedTransactionHelper(transaction);
|
|
}
|
|
|
|
internal string GetConnectAttrString(ODBC32.SQL_ATTR attribute) {
|
|
string value = "";
|
|
Int32 cbActual = 0;
|
|
byte[] buffer = new byte[100];
|
|
OdbcConnectionHandle connectionHandle = ConnectionHandle;
|
|
if (null != connectionHandle) {
|
|
ODBC32.RetCode retcode = connectionHandle.GetConnectionAttribute(attribute, buffer, out cbActual);
|
|
if (buffer.Length+2 <= cbActual) {
|
|
// 2 bytes for unicode null-termination character
|
|
// retry with cbActual because original buffer was too small
|
|
buffer = new byte[cbActual + 2];
|
|
retcode = connectionHandle.GetConnectionAttribute(attribute, buffer, out cbActual);
|
|
}
|
|
if ((ODBC32.RetCode.SUCCESS == retcode) || (ODBC32.RetCode.SUCCESS_WITH_INFO == retcode)) {
|
|
value = Encoding.Unicode.GetString(buffer, 0, Math.Min(cbActual, buffer.Length));
|
|
}
|
|
else if (retcode == ODBC32.RetCode.ERROR) {
|
|
string sqlstate = GetDiagSqlState();
|
|
if (("HYC00" == sqlstate) || ("HY092" == sqlstate) || ("IM001" == sqlstate)) {
|
|
FlagUnsupportedConnectAttr(attribute);
|
|
}
|
|
// not throwing errors if not supported or other failure
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
internal int GetConnectAttr(ODBC32.SQL_ATTR attribute, ODBC32.HANDLER handler) {
|
|
Int32 retval = -1;
|
|
Int32 cbActual = 0;
|
|
byte[] buffer = new byte[4];
|
|
OdbcConnectionHandle connectionHandle = ConnectionHandle;
|
|
if (null != connectionHandle) {
|
|
ODBC32.RetCode retcode = connectionHandle.GetConnectionAttribute(attribute, buffer, out cbActual);
|
|
|
|
if ((ODBC32.RetCode.SUCCESS == retcode) || (ODBC32.RetCode.SUCCESS_WITH_INFO == retcode)) {
|
|
retval = BitConverter.ToInt32(buffer, 0);
|
|
}
|
|
else {
|
|
if (retcode == ODBC32.RetCode.ERROR) {
|
|
string sqlstate = GetDiagSqlState();
|
|
if (("HYC00" == sqlstate) || ("HY092" == sqlstate) || ("IM001" == sqlstate)) {
|
|
FlagUnsupportedConnectAttr(attribute);
|
|
}
|
|
}
|
|
if (handler == ODBC32.HANDLER.THROW) {
|
|
this.HandleError(connectionHandle, retcode);
|
|
}
|
|
}
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
private string GetDiagSqlState () {
|
|
OdbcConnectionHandle connectionHandle = ConnectionHandle;
|
|
string sqlstate;
|
|
connectionHandle.GetDiagnosticField(out sqlstate);
|
|
return sqlstate;
|
|
}
|
|
|
|
internal ODBC32.RetCode GetInfoInt16Unhandled(ODBC32.SQL_INFO info, out Int16 resultValue) {
|
|
byte[] buffer = new byte[2];
|
|
ODBC32.RetCode retcode = ConnectionHandle.GetInfo1(info, buffer);
|
|
resultValue = BitConverter.ToInt16(buffer, 0);
|
|
return retcode;
|
|
}
|
|
|
|
internal ODBC32.RetCode GetInfoInt32Unhandled(ODBC32.SQL_INFO info, out Int32 resultValue) {
|
|
byte[] buffer = new byte[4];
|
|
ODBC32.RetCode retcode = ConnectionHandle.GetInfo1(info, buffer);
|
|
resultValue = BitConverter.ToInt32(buffer, 0);
|
|
return retcode;
|
|
}
|
|
|
|
private Int32 GetInfoInt32Unhandled(ODBC32.SQL_INFO infotype) {
|
|
byte[] buffer = new byte[4];
|
|
ConnectionHandle.GetInfo1(infotype, buffer);
|
|
return BitConverter.ToInt32(buffer, 0);
|
|
}
|
|
|
|
internal string GetInfoStringUnhandled(ODBC32.SQL_INFO info) {
|
|
return GetInfoStringUnhandled(info, false);
|
|
}
|
|
|
|
private string GetInfoStringUnhandled(ODBC32.SQL_INFO info, bool handleError) {
|
|
//SQLGetInfo
|
|
string value = null;
|
|
Int16 cbActual = 0;
|
|
byte[] buffer = new byte[100];
|
|
OdbcConnectionHandle connectionHandle = ConnectionHandle;
|
|
if (null != connectionHandle) {
|
|
ODBC32.RetCode retcode = connectionHandle.GetInfo2(info, buffer, out cbActual);
|
|
if (buffer.Length < cbActual-2) {
|
|
// 2 bytes for unicode null-termination character
|
|
// retry with cbActual because original buffer was too small
|
|
buffer = new byte[cbActual + 2];
|
|
retcode = connectionHandle.GetInfo2(info, buffer, out cbActual);
|
|
}
|
|
if (retcode == ODBC32.RetCode.SUCCESS || retcode == ODBC32.RetCode.SUCCESS_WITH_INFO) {
|
|
value = Encoding.Unicode.GetString(buffer, 0, Math.Min(cbActual, buffer.Length));
|
|
}
|
|
else if (handleError) {
|
|
this.HandleError(ConnectionHandle, retcode);
|
|
}
|
|
}
|
|
else if (handleError) {
|
|
value = "";
|
|
}
|
|
return value;
|
|
}
|
|
|
|
// non-throwing HandleError
|
|
internal Exception HandleErrorNoThrow(OdbcHandle hrHandle, ODBC32.RetCode retcode) {
|
|
|
|
Debug.Assert(retcode!=ODBC32.RetCode.INVALID_HANDLE, "retcode must never be ODBC32.RetCode.INVALID_HANDLE");
|
|
|
|
switch(retcode) {
|
|
case ODBC32.RetCode.SUCCESS:
|
|
break;
|
|
case ODBC32.RetCode.SUCCESS_WITH_INFO: {
|
|
//Optimize to only create the event objects and obtain error info if
|
|
//the user is really interested in retriveing the events...
|
|
if (infoMessageEventHandler != null) {
|
|
OdbcErrorCollection errors = ODBC32.GetDiagErrors(null, hrHandle, retcode);
|
|
errors.SetSource(this.Driver);
|
|
OnInfoMessage(new OdbcInfoMessageEventArgs(errors));
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
OdbcException e = OdbcException.CreateException(ODBC32.GetDiagErrors(null, hrHandle, retcode), retcode);
|
|
if (e != null) {
|
|
e.Errors.SetSource(this.Driver);
|
|
}
|
|
ConnectionIsAlive(e); // this will close and throw if the connection is dead
|
|
return (Exception)e;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
internal void HandleError(OdbcHandle hrHandle, ODBC32.RetCode retcode) {
|
|
Exception e = HandleErrorNoThrow(hrHandle, retcode);
|
|
switch(retcode) {
|
|
case ODBC32.RetCode.SUCCESS:
|
|
case ODBC32.RetCode.SUCCESS_WITH_INFO:
|
|
Debug.Assert(null == e, "success exception");
|
|
break;
|
|
default:
|
|
Debug.Assert(null != e, "failure without exception");
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
override public void Open() {
|
|
InnerConnection.OpenConnection(this, ConnectionFactory);
|
|
|
|
// SQLBUDT #276132 - need to manually enlist in some cases, because
|
|
// native ODBC doesn't know about SysTx transactions.
|
|
if (ADP.NeedManualEnlistment()) {
|
|
EnlistTransaction(SysTx.Transaction.Current);
|
|
}
|
|
}
|
|
|
|
private void OnInfoMessage(OdbcInfoMessageEventArgs args) {
|
|
if (null != infoMessageEventHandler) {
|
|
try {
|
|
infoMessageEventHandler(this, args);
|
|
}
|
|
catch (Exception e) {
|
|
//
|
|
if (!ADP.IsCatchableOrSecurityExceptionType(e)) {
|
|
throw;
|
|
}
|
|
ADP.TraceExceptionWithoutRethrow(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
static public void ReleaseObjectPool() {
|
|
(new OdbcPermission(PermissionState.Unrestricted)).Demand();
|
|
OdbcEnvironment.ReleaseObjectPool();
|
|
}
|
|
|
|
internal OdbcTransaction SetStateExecuting(string method, OdbcTransaction transaction) { // MDAC 69003
|
|
if (null != weakTransaction) { // transaction may exist
|
|
OdbcTransaction weak = (weakTransaction.Target as OdbcTransaction);
|
|
if (transaction != weak) { // transaction doesn't exist
|
|
if (null == transaction) { // transaction exists
|
|
throw ADP.TransactionRequired(method);
|
|
}
|
|
if (this!= transaction.Connection) {
|
|
// transaction can't have come from this connection
|
|
throw ADP.TransactionConnectionMismatch();
|
|
}
|
|
// if transaction is zombied, we don't know the original connection
|
|
transaction = null; // MDAC 69264
|
|
}
|
|
}
|
|
else if (null != transaction) { // no transaction started
|
|
if (null != transaction.Connection) {
|
|
// transaction can't have come from this connection
|
|
throw ADP.TransactionConnectionMismatch();
|
|
}
|
|
// if transaction is zombied, we don't know the original connection
|
|
transaction = null; // MDAC 69264
|
|
}
|
|
ConnectionState state = InternalState;
|
|
if (ConnectionState.Open != state) {
|
|
NotifyWeakReference(OdbcReferenceCollection.Recover); // recover for a potentially finalized reader
|
|
|
|
state = InternalState;
|
|
if (ConnectionState.Open != state) {
|
|
if (0 != (ConnectionState.Fetching & state)) {
|
|
throw ADP.OpenReaderExists();
|
|
}
|
|
throw ADP.OpenConnectionRequired(method, state);
|
|
}
|
|
}
|
|
return transaction;
|
|
}
|
|
|
|
// This adds a type to the list of types that are supported by the driver
|
|
// (don't need to know that for all the types)
|
|
//
|
|
|
|
internal void SetSupportedType (ODBC32.SQL_TYPE sqltype) {
|
|
ODBC32.SQL_CVT sqlcvt;
|
|
|
|
switch (sqltype) {
|
|
case ODBC32.SQL_TYPE.NUMERIC: {
|
|
sqlcvt = ODBC32.SQL_CVT.NUMERIC;
|
|
break;
|
|
}
|
|
case ODBC32.SQL_TYPE.WCHAR: {
|
|
sqlcvt = ODBC32.SQL_CVT.WCHAR;
|
|
break;
|
|
}
|
|
case ODBC32.SQL_TYPE.WVARCHAR: {
|
|
sqlcvt = ODBC32.SQL_CVT.WVARCHAR;
|
|
break;
|
|
}
|
|
case ODBC32.SQL_TYPE.WLONGVARCHAR: {
|
|
sqlcvt = ODBC32.SQL_CVT.WLONGVARCHAR;
|
|
break;
|
|
}
|
|
default:
|
|
// other types are irrelevant at this time
|
|
return;
|
|
}
|
|
ProviderInfo.TestedSQLTypes |= (int)sqlcvt;
|
|
ProviderInfo.SupportedSQLTypes |= (int)sqlcvt;
|
|
}
|
|
|
|
internal void FlagRestrictedSqlBindType(ODBC32.SQL_TYPE sqltype) {
|
|
ODBC32.SQL_CVT sqlcvt;
|
|
|
|
switch (sqltype) {
|
|
case ODBC32.SQL_TYPE.NUMERIC: {
|
|
sqlcvt = ODBC32.SQL_CVT.NUMERIC;
|
|
break;
|
|
}
|
|
case ODBC32.SQL_TYPE.DECIMAL: {
|
|
sqlcvt = ODBC32.SQL_CVT.DECIMAL;
|
|
break;
|
|
}
|
|
default:
|
|
// other types are irrelevant at this time
|
|
return;
|
|
}
|
|
ProviderInfo.RestrictedSQLBindTypes |= (int)sqlcvt;
|
|
}
|
|
|
|
internal void FlagUnsupportedConnectAttr (ODBC32.SQL_ATTR Attribute) {
|
|
switch (Attribute) {
|
|
case ODBC32.SQL_ATTR.CURRENT_CATALOG:
|
|
ProviderInfo.NoCurrentCatalog = true;
|
|
break;
|
|
case ODBC32.SQL_ATTR.CONNECTION_DEAD:
|
|
ProviderInfo.NoConnectionDead = true;
|
|
break;
|
|
default:
|
|
Debug.Assert (false, "Can't flag unknown Attribute");
|
|
break;
|
|
}
|
|
}
|
|
|
|
internal void FlagUnsupportedStmtAttr (ODBC32.SQL_ATTR Attribute) {
|
|
switch (Attribute) {
|
|
case ODBC32.SQL_ATTR.QUERY_TIMEOUT:
|
|
ProviderInfo.NoQueryTimeout = true;
|
|
break;
|
|
case (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.NOBROWSETABLE:
|
|
ProviderInfo.NoSqlSoptSSNoBrowseTable = true;
|
|
break;
|
|
case (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.HIDDEN_COLUMNS:
|
|
ProviderInfo.NoSqlSoptSSHiddenColumns = true;
|
|
break;
|
|
default:
|
|
Debug.Assert (false, "Can't flag unknown Attribute");
|
|
break;
|
|
}
|
|
}
|
|
|
|
internal void FlagUnsupportedColAttr (ODBC32.SQL_DESC v3FieldId, ODBC32.SQL_COLUMN v2FieldId) {
|
|
if (IsV3Driver) {
|
|
switch ( v3FieldId){
|
|
case (ODBC32.SQL_DESC)ODBC32.SQL_CA_SS.COLUMN_KEY:
|
|
// SSS_WARNINGS_OFF
|
|
ProviderInfo.NoSqlCASSColumnKey = true;
|
|
break;
|
|
// SSS_WARNINGS_ON
|
|
default:
|
|
Debug.Assert (false, "Can't flag unknown Attribute");
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
switch ( v2FieldId) {
|
|
default:
|
|
Debug.Assert (false, "Can't flag unknown Attribute");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal Boolean SQLGetFunctions(ODBC32.SQL_API odbcFunction) {
|
|
//SQLGetFunctions
|
|
ODBC32.RetCode retcode;
|
|
Int16 fExists;
|
|
Debug.Assert ((Int16) odbcFunction != 0,"SQL_API_ALL_FUNCTIONS is not supported");
|
|
OdbcConnectionHandle connectionHandle = ConnectionHandle;
|
|
if (null != connectionHandle) {
|
|
retcode = connectionHandle.GetFunctions(odbcFunction, out fExists);
|
|
}
|
|
else {
|
|
Debug.Assert (false, "GetFunctions called and ConnectionHandle is null (connection is disposed?)");
|
|
throw ODBC.ConnectionClosed();
|
|
}
|
|
|
|
if(retcode != ODBC32.RetCode.SUCCESS)
|
|
this.HandleError(connectionHandle, retcode);
|
|
|
|
if (fExists == 0){
|
|
return false;
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
internal bool TestTypeSupport (ODBC32.SQL_TYPE sqltype){
|
|
ODBC32.SQL_CONVERT sqlconvert;
|
|
ODBC32.SQL_CVT sqlcvt;
|
|
|
|
// we need to convert the sqltype to sqlconvert and sqlcvt first
|
|
//
|
|
switch (sqltype) {
|
|
case ODBC32.SQL_TYPE.NUMERIC: {
|
|
sqlconvert = ODBC32.SQL_CONVERT.NUMERIC;
|
|
sqlcvt = ODBC32.SQL_CVT.NUMERIC;
|
|
break;
|
|
}
|
|
case ODBC32.SQL_TYPE.WCHAR: {
|
|
sqlconvert = ODBC32.SQL_CONVERT.CHAR;
|
|
sqlcvt = ODBC32.SQL_CVT.WCHAR;
|
|
break;
|
|
}
|
|
case ODBC32.SQL_TYPE.WVARCHAR: {
|
|
sqlconvert = ODBC32.SQL_CONVERT.VARCHAR;
|
|
sqlcvt = ODBC32.SQL_CVT.WVARCHAR;
|
|
break;
|
|
}
|
|
case ODBC32.SQL_TYPE.WLONGVARCHAR: {
|
|
sqlconvert = ODBC32.SQL_CONVERT.LONGVARCHAR;
|
|
sqlcvt = ODBC32.SQL_CVT.WLONGVARCHAR;
|
|
break;
|
|
}
|
|
default:
|
|
Debug.Assert(false, "Testing that sqltype is currently not supported");
|
|
return false;
|
|
}
|
|
// now we can check if we have already tested that type
|
|
// if not we need to do so
|
|
if (0 == (ProviderInfo.TestedSQLTypes & (int)sqlcvt)) {
|
|
int flags;
|
|
|
|
flags = GetInfoInt32Unhandled((ODBC32.SQL_INFO)sqlconvert);
|
|
flags = flags & (int)sqlcvt;
|
|
|
|
ProviderInfo.TestedSQLTypes |= (int)sqlcvt;
|
|
ProviderInfo.SupportedSQLTypes |= flags;
|
|
}
|
|
|
|
// now check if the type is supported and return the result
|
|
//
|
|
return (0 != (ProviderInfo.SupportedSQLTypes & (int)sqlcvt));
|
|
}
|
|
|
|
internal bool TestRestrictedSqlBindType (ODBC32.SQL_TYPE sqltype){
|
|
ODBC32.SQL_CVT sqlcvt;
|
|
switch (sqltype) {
|
|
case ODBC32.SQL_TYPE.NUMERIC: {
|
|
sqlcvt = ODBC32.SQL_CVT.NUMERIC;
|
|
break;
|
|
}
|
|
case ODBC32.SQL_TYPE.DECIMAL: {
|
|
sqlcvt = ODBC32.SQL_CVT.DECIMAL;
|
|
break;
|
|
}
|
|
default:
|
|
Debug.Assert(false, "Testing that sqltype is currently not supported");
|
|
return false;
|
|
}
|
|
return (0 != (ProviderInfo.RestrictedSQLBindTypes & (int)sqlcvt));
|
|
}
|
|
|
|
// suppress this message - we cannot use SafeHandle here. Also, see notes in the code (VSTFDEVDIV# 560355)
|
|
[SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive")]
|
|
override protected DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) {
|
|
IntPtr hscp;
|
|
|
|
Bid.ScopeEnter(out hscp, "<prov.OdbcConnection.BeginDbTransaction|API> %d#, isolationLevel=%d{ds.IsolationLevel}", ObjectID, (int)isolationLevel);
|
|
try {
|
|
|
|
DbTransaction transaction = InnerConnection.BeginTransaction(isolationLevel);
|
|
|
|
// VSTFDEVDIV# 560355 - InnerConnection doesn't maintain a ref on the outer connection (this) and
|
|
// subsequently leaves open the possibility that the outer connection could be GC'ed before the DbTransaction
|
|
// is fully hooked up (leaving a DbTransaction with a null connection property). Ensure that this is reachable
|
|
// until the completion of BeginTransaction with KeepAlive
|
|
GC.KeepAlive(this);
|
|
|
|
return transaction;
|
|
}
|
|
finally {
|
|
Bid.ScopeLeave(ref hscp);
|
|
}
|
|
}
|
|
|
|
internal OdbcTransaction Open_BeginTransaction(IsolationLevel isolevel) {
|
|
OdbcConnection.ExecutePermission.Demand();
|
|
|
|
CheckState(ADP.BeginTransaction); // MDAC 68323
|
|
|
|
RollbackDeadTransaction();
|
|
|
|
if ((null != this.weakTransaction) && this.weakTransaction.IsAlive) { // regression from Dispose/Finalize work
|
|
throw ADP.ParallelTransactionsNotSupported(this);
|
|
}
|
|
|
|
//Use the default for unspecified.
|
|
switch(isolevel) {
|
|
case IsolationLevel.Unspecified:
|
|
case IsolationLevel.ReadUncommitted:
|
|
case IsolationLevel.ReadCommitted:
|
|
case IsolationLevel.RepeatableRead:
|
|
case IsolationLevel.Serializable:
|
|
case IsolationLevel.Snapshot:
|
|
break;
|
|
case IsolationLevel.Chaos:
|
|
throw ODBC.NotSupportedIsolationLevel(isolevel);
|
|
default:
|
|
throw ADP.InvalidIsolationLevel(isolevel);
|
|
};
|
|
|
|
//Start the transaction
|
|
OdbcConnectionHandle connectionHandle = ConnectionHandle;
|
|
ODBC32.RetCode retcode = connectionHandle.BeginTransaction(ref isolevel);
|
|
if (retcode == ODBC32.RetCode.ERROR) {
|
|
HandleError(connectionHandle, retcode);
|
|
}
|
|
OdbcTransaction transaction = new OdbcTransaction(this, isolevel, connectionHandle);
|
|
this.weakTransaction = new WeakReference(transaction); // MDAC 69188
|
|
return transaction;
|
|
}
|
|
|
|
internal void Open_ChangeDatabase(string value) {
|
|
OdbcConnection.ExecutePermission.Demand();
|
|
|
|
CheckState(ADP.ChangeDatabase);
|
|
|
|
// Database name must not be null, empty or whitspace
|
|
if ((null == value) || (0 == value.Trim().Length)) { // MDAC 62679
|
|
throw ADP.EmptyDatabaseName();
|
|
}
|
|
if (1024 < value.Length*2+2) {
|
|
throw ADP.DatabaseNameTooLong();
|
|
}
|
|
RollbackDeadTransaction();
|
|
|
|
//Set the database
|
|
OdbcConnectionHandle connectionHandle = ConnectionHandle;
|
|
ODBC32.RetCode retcode = connectionHandle.SetConnectionAttribute3(ODBC32.SQL_ATTR.CURRENT_CATALOG, value, checked((Int32)value.Length*2));
|
|
|
|
if (retcode != ODBC32.RetCode.SUCCESS) {
|
|
HandleError(connectionHandle, retcode);
|
|
}
|
|
}
|
|
|
|
internal void Open_EnlistTransaction(SysTx.Transaction transaction) {
|
|
OdbcConnection.VerifyExecutePermission();
|
|
|
|
if ((null != this.weakTransaction) && this.weakTransaction.IsAlive) {
|
|
throw ADP.LocalTransactionPresent();
|
|
}
|
|
|
|
SysTx.IDtcTransaction oleTxTransaction = ADP.GetOletxTransaction(transaction);
|
|
|
|
OdbcConnectionHandle connectionHandle = ConnectionHandle;
|
|
ODBC32.RetCode retcode;
|
|
if (null == oleTxTransaction) {
|
|
retcode = connectionHandle.SetConnectionAttribute2(ODBC32.SQL_ATTR.SQL_COPT_SS_ENLIST_IN_DTC, (IntPtr) ODBC32.SQL_DTC_DONE, ODBC32.SQL_IS_PTR);
|
|
}
|
|
else {
|
|
retcode = connectionHandle.SetConnectionAttribute4(ODBC32.SQL_ATTR.SQL_COPT_SS_ENLIST_IN_DTC, oleTxTransaction, ODBC32.SQL_IS_PTR);
|
|
}
|
|
|
|
if (retcode != ODBC32.RetCode.SUCCESS) {
|
|
HandleError(connectionHandle, retcode);
|
|
}
|
|
|
|
// Tell the base class about our enlistment
|
|
((OdbcConnectionOpen)InnerConnection).EnlistedTransaction = transaction;
|
|
}
|
|
|
|
internal string Open_GetServerVersion() {
|
|
//SQLGetInfo - SQL_DBMS_VER
|
|
return GetInfoStringUnhandled(ODBC32.SQL_INFO.DBMS_VER, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
|