|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Data.Common;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace System.Data.Odbc
{
internal abstract class OdbcHandle : SafeHandle
{
private readonly ODBC32.SQL_HANDLE _handleType;
private OdbcHandle? _parentHandle;
protected OdbcHandle(ODBC32.SQL_HANDLE handleType, OdbcHandle? parentHandle) : base(IntPtr.Zero, true)
{
_handleType = handleType;
bool mustRelease = false;
ODBC32.SQLRETURN retcode = ODBC32.SQLRETURN.SUCCESS;
try
{
// validate handleType
switch (handleType)
{
case ODBC32.SQL_HANDLE.ENV:
Debug.Assert(null == parentHandle, "did not expect a parent handle");
retcode = Interop.Odbc.SQLAllocHandle(handleType, IntPtr.Zero, out base.handle);
break;
case ODBC32.SQL_HANDLE.DBC:
case ODBC32.SQL_HANDLE.STMT:
// must addref before calling native so it won't be released just after
Debug.Assert(null != parentHandle, "expected a parent handle"); // safehandle can't be null
parentHandle.DangerousAddRef(ref mustRelease);
retcode = Interop.Odbc.SQLAllocHandle(handleType, parentHandle, out base.handle);
break;
// case ODBC32.SQL_HANDLE.DESC:
default:
Debug.Fail("unexpected handleType");
break;
}
}
finally
{
if (mustRelease)
{
switch (handleType)
{
case ODBC32.SQL_HANDLE.DBC:
case ODBC32.SQL_HANDLE.STMT:
if (IntPtr.Zero != base.handle)
{
// must assign _parentHandle after a handle is actually created
// since ReleaseHandle will only call DangerousRelease if a handle exists
_parentHandle = parentHandle;
}
else
{
// without a handle, ReleaseHandle may not be called
parentHandle!.DangerousRelease();
}
break;
}
}
}
if ((ADP.PtrZero == base.handle) || (ODBC32.SQLRETURN.SUCCESS != retcode))
{
//
throw ODBC.CantAllocateEnvironmentHandle(retcode);
}
}
internal OdbcHandle(OdbcStatementHandle parentHandle, ODBC32.SQL_ATTR attribute) : base(IntPtr.Zero, true)
{
Debug.Assert((ODBC32.SQL_ATTR.APP_PARAM_DESC == attribute) || (ODBC32.SQL_ATTR.APP_ROW_DESC == attribute), "invalid attribute");
_handleType = ODBC32.SQL_HANDLE.DESC;
int cbActual;
ODBC32.SQLRETURN retcode;
bool mustRelease = false;
try
{
// must addref before calling native so it won't be released just after
parentHandle.DangerousAddRef(ref mustRelease);
retcode = parentHandle.GetStatementAttribute(attribute, out base.handle, out cbActual);
}
finally
{
if (mustRelease)
{
if (IntPtr.Zero != base.handle)
{
// must call DangerousAddRef after a handle is actually created
// since ReleaseHandle will only call DangerousRelease if a handle exists
_parentHandle = parentHandle;
}
else
{
// without a handle, ReleaseHandle may not be called
parentHandle.DangerousRelease();
}
}
}
if (ADP.PtrZero == base.handle)
{
throw ODBC.FailedToGetDescriptorHandle(retcode);
}
// no info-message handle on getting a descriptor handle
}
internal ODBC32.SQL_HANDLE HandleType
{
get
{
return _handleType;
}
}
public override bool IsInvalid
{
get
{
// we should not have a parent if we do not have a handle
return (IntPtr.Zero == base.handle);
}
}
protected override bool ReleaseHandle()
{
// NOTE: The SafeHandle class guarantees this will be called exactly once and is non-interrutible.
IntPtr handle = base.handle;
base.handle = IntPtr.Zero;
if (IntPtr.Zero != handle)
{
ODBC32.SQL_HANDLE handleType = HandleType;
switch (handleType)
{
case ODBC32.SQL_HANDLE.DBC:
// Disconnect happens in OdbcConnectionHandle.ReleaseHandle
case ODBC32.SQL_HANDLE.ENV:
case ODBC32.SQL_HANDLE.STMT:
Interop.Odbc.SQLFreeHandle(handleType, handle);
break;
case ODBC32.SQL_HANDLE.DESC:
// nothing to free on the handle
break;
// case 0: ThreadAbortException setting handle before HandleType
default:
Debug.Assert(ADP.PtrZero == handle, "unknown handle type");
break;
}
}
// If we ended up getting released, then we have to release
// our reference on our parent.
OdbcHandle? parentHandle = _parentHandle;
if (parentHandle != null)
{
_parentHandle = null;
parentHandle.DangerousRelease();
}
return true;
}
internal ODBC32.SQLRETURN GetDiagnosticField(out string sqlState)
{
// ODBC (MSDN) documents it expects a buffer large enough to hold 5(+L'\0') unicode characters
char[] buffer = new char[6];
ODBC32.SQLRETURN retcode = Interop.Odbc.SQLGetDiagFieldW(
HandleType,
this,
(short)1,
ODBC32.SQL_DIAG_SQLSTATE,
buffer,
checked((short)(2 * buffer.Length)), // expects number of bytes, see \\kbinternal\kb\articles\294\1\69.HTM
out _);
ODBC.TraceODBC(3, "SQLGetDiagFieldW", retcode);
if ((retcode == ODBC32.SQLRETURN.SUCCESS) || (retcode == ODBC32.SQLRETURN.SUCCESS_WITH_INFO))
{
sqlState = new string(buffer.AsSpan(0, buffer.AsSpan().IndexOf('\0')));
}
else
{
sqlState = string.Empty;
}
return retcode;
}
internal ODBC32.SQLRETURN GetDiagnosticRecord(short record, out string sqlState, StringBuilder messageBuilder, out int nativeError, out short cchActual)
{
// SQLGetDiagRecW expects a buffer large enough to hold a five-character state code plus a null-terminator
// See https://learn.microsoft.com/sql/odbc/reference/syntax/sqlgetdiagrec-function
char[] buffer = new char[6];
char[] message = new char[1024];
ODBC32.SQLRETURN retcode = Interop.Odbc.SQLGetDiagRecW(HandleType, this, record, buffer, out nativeError, message, checked((short)message.Length), out cchActual);
ODBC.TraceODBC(3, "SQLGetDiagRecW", retcode);
if ((retcode == ODBC32.SQLRETURN.SUCCESS) || (retcode == ODBC32.SQLRETURN.SUCCESS_WITH_INFO))
{
sqlState = new string(buffer.AsSpan(0, buffer.AsSpan().IndexOf('\0')));
}
else
{
sqlState = string.Empty;
}
messageBuilder.Append(new string(message.AsSpan(0, message.AsSpan().IndexOf('\0'))));
return retcode;
}
}
internal sealed class OdbcDescriptorHandle : OdbcHandle
{
internal OdbcDescriptorHandle(OdbcStatementHandle statementHandle, ODBC32.SQL_ATTR attribute) : base(statementHandle, attribute)
{
}
internal ODBC32.SQLRETURN GetDescriptionField(int i, ODBC32.SQL_DESC attribute, CNativeBuffer buffer, out int numericAttribute)
{
ODBC32.SQLRETURN retcode = Interop.Odbc.SQLGetDescFieldW(this, checked((short)i), attribute, buffer, buffer.ShortLength, out numericAttribute);
ODBC.TraceODBC(3, "SQLGetDescFieldW", retcode);
return retcode;
}
internal ODBC32.SQLRETURN SetDescriptionField1(short ordinal, ODBC32.SQL_DESC type, IntPtr value)
{
ODBC32.SQLRETURN retcode = Interop.Odbc.SQLSetDescFieldW(this, ordinal, type, value, 0);
ODBC.TraceODBC(3, "SQLSetDescFieldW", retcode);
return retcode;
}
internal ODBC32.SQLRETURN SetDescriptionField2(short ordinal, ODBC32.SQL_DESC type, HandleRef value)
{
ODBC32.SQLRETURN retcode = Interop.Odbc.SQLSetDescFieldW(this, ordinal, type, value, 0);
ODBC.TraceODBC(3, "SQLSetDescFieldW", retcode);
return retcode;
}
}
}
|