|
//------------------------------------------------------------------------------
// <copyright file="OdbcCommand.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>
//------------------------------------------------------------------------------
using System;
using System.ComponentModel; //Component
using System.Data;
using System.Data.Common;
using System.Data.ProviderBase;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
// todo:
// There may be two ways to improve performance:
// 1. pool statements on the connection object
// 2. Do not create a datareader object for non-datareader returning command execution.
//
// We do not want to do the effort unless we have to squeze performance.
namespace System.Data.Odbc {
[
DefaultEvent("RecordsAffected"),
ToolboxItem(true),
Designer("Microsoft.VSDesigner.Data.VS.OdbcCommandDesigner, " + AssemblyRef.MicrosoftVSDesigner)
]
public sealed class OdbcCommand : DbCommand, ICloneable {
private static int _objectTypeCount; // Bid counter
internal readonly int ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
private string _commandText;
private CommandType _commandType;
private int _commandTimeout = ADP.DefaultCommandTimeout;
private UpdateRowSource _updatedRowSource = UpdateRowSource.Both;
private bool _designTimeInvisible;
private bool _isPrepared; // true if the command is prepared
private OdbcConnection _connection;
private OdbcTransaction _transaction;
private WeakReference weakDataReaderReference;
private CMDWrapper _cmdWrapper;
private OdbcParameterCollection _parameterCollection; // Parameter collection
private ConnectionState cmdState;
public OdbcCommand() : base() {
GC.SuppressFinalize(this);
}
public OdbcCommand(string cmdText) : this() {
// note: arguments are assigned to properties so we do not have to trace them.
// We still need to include them into the argument list of the definition!
CommandText = cmdText;
}
public OdbcCommand(string cmdText, OdbcConnection connection) : this() {
CommandText = cmdText;
Connection = connection;
}
public OdbcCommand(string cmdText, OdbcConnection connection, OdbcTransaction transaction) : this() {
CommandText = cmdText;
Connection = connection;
Transaction = transaction;
}
private void DisposeDeadDataReader() {
if (ConnectionState.Fetching == cmdState) {
if (null != this.weakDataReaderReference && !this.weakDataReaderReference.IsAlive) {
if (_cmdWrapper != null) {
_cmdWrapper.FreeKeyInfoStatementHandle(ODBC32.STMT.CLOSE);
_cmdWrapper.FreeStatementHandle(ODBC32.STMT.CLOSE);
}
CloseFromDataReader();
}
}
}
private void DisposeDataReader() {
if (null != this.weakDataReaderReference) {
IDisposable reader = (IDisposable) this.weakDataReaderReference.Target;
if ((null != reader) && this.weakDataReaderReference.IsAlive) {
((IDisposable) reader).Dispose();
}
CloseFromDataReader();
}
}
internal void DisconnectFromDataReaderAndConnection () {
// get a reference to the datareader if it is alive
OdbcDataReader liveReader = null;
if (this.weakDataReaderReference != null){
OdbcDataReader reader;
reader = (OdbcDataReader)this.weakDataReaderReference.Target;
if (this.weakDataReaderReference.IsAlive) {
liveReader = reader;
}
}
// remove reference to this from the live datareader
if (liveReader != null) {
liveReader.Command = null;
}
_transaction = null;
if (null != _connection) {
_connection.RemoveWeakReference(this);
_connection = null;
}
// if the reader is dead we have to dismiss the statement
if (liveReader == null){
CloseCommandWrapper();
}
// else DataReader now has exclusive ownership
_cmdWrapper = null;
}
override protected void Dispose(bool disposing) { // MDAC 65459
if (disposing) {
// release mananged objects
// in V1.0, V1.1 the Connection,Parameters,CommandText,Transaction where reset
this.DisconnectFromDataReaderAndConnection ();
_parameterCollection = null;
CommandText = null;
}
_cmdWrapper = null; // let go of the CommandWrapper
_isPrepared = false;
base.Dispose(disposing); // notify base classes
}
internal bool Canceling {
get {
return _cmdWrapper.Canceling;
}
}
[
ResCategoryAttribute(Res.DataCategory_Data),
DefaultValue(""),
RefreshProperties(RefreshProperties.All), // MDAC 67707
ResDescriptionAttribute(Res.DbCommand_CommandText),
Editor("Microsoft.VSDesigner.Data.Odbc.Design.OdbcCommandTextEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing)
]
override public string CommandText {
get {
string value = _commandText;
return ((null != value) ? value : ADP.StrEmpty);
}
set {
if (Bid.TraceOn) {
Bid.Trace("<odbc.OdbcCommand.set_CommandText|API> %d#, '", ObjectID);
Bid.PutStr(value); // Use PutStr to write out entire string
Bid.Trace("'\n");
}
if (0 != ADP.SrcCompare(_commandText, value)) {
PropertyChanging();
_commandText = value;
}
}
}
[
ResCategoryAttribute(Res.DataCategory_Data),
ResDescriptionAttribute(Res.DbCommand_CommandTimeout),
]
override public int CommandTimeout { // V1.2.3300, XXXCommand V1.0.5000
get {
return _commandTimeout;
}
set {
Bid.Trace("<odbc.OdbcCommand.set_CommandTimeout|API> %d#, %d\n", ObjectID, value);
if (value < 0) {
throw ADP.InvalidCommandTimeout(value);
}
if (value != _commandTimeout) {
PropertyChanging();
_commandTimeout = value;
}
}
}
public void ResetCommandTimeout() { // V1.2.3300
if (ADP.DefaultCommandTimeout != _commandTimeout) {
PropertyChanging();
_commandTimeout = ADP.DefaultCommandTimeout;
}
}
private bool ShouldSerializeCommandTimeout() { // V1.2.3300
return (ADP.DefaultCommandTimeout != _commandTimeout);
}
[
DefaultValue(System.Data.CommandType.Text),
RefreshProperties(RefreshProperties.All),
ResCategoryAttribute(Res.DataCategory_Data),
ResDescriptionAttribute(Res.DbCommand_CommandType),
]
override public CommandType CommandType {
get {
CommandType cmdType = _commandType;
return ((0 != cmdType) ? cmdType : CommandType.Text);
}
set {
switch(value) { // @perfnote: Enum.IsDefined
case CommandType.Text:
case CommandType.StoredProcedure:
PropertyChanging();
_commandType = value;
break;
case CommandType.TableDirect:
throw ODBC.NotSupportedCommandType(value);
default:
throw ADP.InvalidCommandType(value);
}
}
}
// This will establish a relationship between the command and the connection
[
DefaultValue(null),
ResCategoryAttribute(Res.DataCategory_Behavior),
ResDescriptionAttribute(Res.DbCommand_Connection),
Editor("Microsoft.VSDesigner.Data.Design.DbConnectionEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing),
]
new public OdbcConnection Connection {
get {
return _connection;
}
set {
if (value != _connection) {
PropertyChanging();
this.DisconnectFromDataReaderAndConnection();
Debug.Assert(null == _cmdWrapper, "has CMDWrapper when setting connection");
_connection = value;
//OnSchemaChanged();
}
}
}
override protected DbConnection DbConnection { // V1.2.3300
get {
return Connection;
}
set {
Connection = (OdbcConnection)value;
}
}
override protected DbParameterCollection DbParameterCollection { // V1.2.3300
get {
return Parameters;
}
}
override protected DbTransaction DbTransaction { // V1.2.3300
get {
return Transaction;
}
set {
Transaction = (OdbcTransaction)value;
}
}
// @devnote: By default, the cmd object is visible on the design surface (i.e. VS7 Server Tray)
// to limit the number of components that clutter the design surface,
// when the DataAdapter design wizard generates the insert/update/delete commands it will
// set the DesignTimeVisible property to false so that cmds won't appear as individual objects
[
DefaultValue(true),
DesignOnly(true),
Browsable(false),
EditorBrowsableAttribute(EditorBrowsableState.Never),
]
public override bool DesignTimeVisible { // V1.2.3300, XXXCommand V1.0.5000
get {
return !_designTimeInvisible;
}
set {
_designTimeInvisible = !value;
TypeDescriptor.Refresh(this); // VS7 208845
}
}
internal bool HasParameters {
get {
return (null != _parameterCollection);
}
}
[
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
ResCategoryAttribute(Res.DataCategory_Data),
ResDescriptionAttribute(Res.DbCommand_Parameters),
]
new public OdbcParameterCollection Parameters {
get {
if (null == _parameterCollection) {
_parameterCollection = new OdbcParameterCollection();
}
return _parameterCollection;
}
}
[
Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
ResDescriptionAttribute(Res.DbCommand_Transaction),
]
new public OdbcTransaction Transaction {
get {
if ((null != _transaction) && (null == _transaction.Connection)) {
_transaction = null; // Dawn of the Dead
}
return _transaction;
}
set {
if (_transaction != value) {
PropertyChanging(); // fire event before value is validated
_transaction = value;
}
}
}
[
DefaultValue(System.Data.UpdateRowSource.Both),
ResCategoryAttribute(Res.DataCategory_Update),
ResDescriptionAttribute(Res.DbCommand_UpdatedRowSource),
]
override public UpdateRowSource UpdatedRowSource { // V1.2.3300, XXXCommand V1.0.5000
get {
return _updatedRowSource;
}
set {
switch(value) { // @perfnote: Enum.IsDefined
case UpdateRowSource.None:
case UpdateRowSource.OutputParameters:
case UpdateRowSource.FirstReturnedRecord:
case UpdateRowSource.Both:
_updatedRowSource = value;
break;
default:
throw ADP.InvalidUpdateRowSource(value);
}
}
}
internal OdbcDescriptorHandle GetDescriptorHandle(ODBC32.SQL_ATTR attribute) {
return _cmdWrapper.GetDescriptorHandle(attribute);
}
// GetStatementHandle
// ------------------
// Try to return a cached statement handle.
//
// Creates a CmdWrapper object if necessary
// If no handle is available a handle will be allocated.
// Bindings will be unbound if a handle is cached and the bindings are invalid.
//
internal CMDWrapper GetStatementHandle () {
// update the command wrapper object, allocate buffer
// create reader object
//
if (_cmdWrapper==null) {
_cmdWrapper = new CMDWrapper(_connection);
Debug.Assert(null != _connection, "GetStatementHandle without connection?");
_connection.AddWeakReference(this, OdbcReferenceCollection.CommandTag);
}
if (_cmdWrapper._dataReaderBuf == null) {
_cmdWrapper._dataReaderBuf = new CNativeBuffer(4096);
}
// if there is already a statement handle we need to do some cleanup
//
if (null == _cmdWrapper.StatementHandle) {
_isPrepared = false;
_cmdWrapper.CreateStatementHandle();
}
else if ((null != _parameterCollection) && _parameterCollection.RebindCollection) {
_cmdWrapper.FreeStatementHandle(ODBC32.STMT.RESET_PARAMS);
}
return _cmdWrapper;
}
// OdbcCommand.Cancel()
//
// In ODBC3.0 ... a call to SQLCancel when no processing is done has no effect at all
// (ODBC Programmer's Reference ...)
//
override public void Cancel() {
CMDWrapper wrapper = _cmdWrapper;
if (null != wrapper) {
wrapper.Canceling = true;
OdbcStatementHandle stmt = wrapper.StatementHandle;
if (null != stmt) {
lock (stmt) {
// Cancel the statement
ODBC32.RetCode retcode = stmt.Cancel();
// copy of StatementErrorHandler, because stmt may become null
switch(retcode) {
case ODBC32.RetCode.SUCCESS:
case ODBC32.RetCode.SUCCESS_WITH_INFO:
// don't fire info message events on cancel
break;
default:
throw wrapper.Connection.HandleErrorNoThrow(stmt, retcode);
}
}
}
}
}
object ICloneable.Clone() {
OdbcCommand clone = new OdbcCommand();
Bid.Trace("<odbc.OdbcCommand.Clone|API> %d#, clone=%d#\n", ObjectID, clone.ObjectID);
clone.CommandText = CommandText;
clone.CommandTimeout = this.CommandTimeout;
clone.CommandType = CommandType;
clone.Connection = this.Connection;
clone.Transaction = this.Transaction;
clone.UpdatedRowSource = UpdatedRowSource;
if ((null != _parameterCollection) && (0 < Parameters.Count)) {
OdbcParameterCollection parameters = clone.Parameters;
foreach(ICloneable parameter in Parameters) {
parameters.Add(parameter.Clone());
}
}
return clone;
}
internal bool RecoverFromConnection() {
DisposeDeadDataReader();
return (ConnectionState.Closed == cmdState);
}
private void CloseCommandWrapper() {
CMDWrapper wrapper = _cmdWrapper;
if (null != wrapper) {
try {
wrapper.Dispose();
if (null != _connection) {
_connection.RemoveWeakReference(this);
}
}
finally {
_cmdWrapper = null;
}
}
}
internal void CloseFromConnection () {
if (null != _parameterCollection) {
_parameterCollection.RebindCollection = true;
}
DisposeDataReader();
CloseCommandWrapper();
_isPrepared = false;
_transaction = null;
}
internal void CloseFromDataReader() {
this.weakDataReaderReference = null;
this.cmdState = ConnectionState.Closed;
}
new public OdbcParameter CreateParameter() {
return new OdbcParameter();
}
override protected DbParameter CreateDbParameter() {
return CreateParameter();
}
override protected DbDataReader ExecuteDbDataReader(CommandBehavior behavior) {
return ExecuteReader(behavior);
}
override public int ExecuteNonQuery() {
OdbcConnection.ExecutePermission.Demand();
using (OdbcDataReader reader = ExecuteReaderObject(0, ADP.ExecuteNonQuery, false)) {
reader.Close();
return reader.RecordsAffected;
}
}
new public OdbcDataReader ExecuteReader() {
return ExecuteReader(0/*CommandBehavior*/);
}
new public OdbcDataReader ExecuteReader(CommandBehavior behavior) {
OdbcConnection.ExecutePermission.Demand();
return ExecuteReaderObject(behavior, ADP.ExecuteReader, true);
}
internal OdbcDataReader ExecuteReaderFromSQLMethod(object[] methodArguments,
ODBC32.SQL_API method){
return ExecuteReaderObject(CommandBehavior.Default,method.ToString(),true,methodArguments,method);
}
private OdbcDataReader ExecuteReaderObject(CommandBehavior behavior, string method, bool needReader) { // MDAC 68324
if ((CommandText == null) || (CommandText.Length == 0)) {
throw (ADP.CommandTextRequired(method));
}
// using all functions to tell ExecuteReaderObject that
return ExecuteReaderObject(behavior,method,needReader,null,ODBC32.SQL_API.SQLEXECDIRECT);
}
private OdbcDataReader ExecuteReaderObject(CommandBehavior behavior,
string method,
bool needReader,
object[] methodArguments,
ODBC32.SQL_API odbcApiMethod) { // MDAC 68324
OdbcDataReader localReader = null;
try {
DisposeDeadDataReader(); // this is a no-op if cmdState is not Fetching
ValidateConnectionAndTransaction(method); // cmdState will change to Executing
if(0 != (CommandBehavior.SingleRow & behavior)) {
// CommandBehavior.SingleRow implies CommandBehavior.SingleResult
behavior |= CommandBehavior.SingleResult;
}
ODBC32.RetCode retcode;
OdbcStatementHandle stmt = GetStatementHandle().StatementHandle;
_cmdWrapper.Canceling = false;
if(null != weakDataReaderReference) {
if(weakDataReaderReference.IsAlive) {
object target = weakDataReaderReference.Target;
if(null != target && weakDataReaderReference.IsAlive) {
if(!((OdbcDataReader)target).IsClosed) {
throw ADP.OpenReaderExists(); // MDAC 66411
}
}
}
}
localReader = new OdbcDataReader(this, _cmdWrapper, behavior);
//Set command properties
//Not all drivers support timeout. So fail silently if error
if(!Connection.ProviderInfo.NoQueryTimeout) {
TrySetStatementAttribute(stmt,
ODBC32.SQL_ATTR.QUERY_TIMEOUT,
(IntPtr)this.CommandTimeout);
}
// todo: If we remember the state we can omit a lot of SQLSetStmtAttrW calls ...
// if we do not create a reader we do not even need to do that
if(needReader) {
if(Connection.IsV3Driver) {
if(!Connection.ProviderInfo.NoSqlSoptSSNoBrowseTable && !Connection.ProviderInfo.NoSqlSoptSSHiddenColumns) {
// Need to get the metadata information
//SQLServer actually requires browse info turned on ahead of time...
//Note: We ignore any failures, since this is SQLServer specific
//We won't specialcase for SQL Server but at least for non-V3 drivers
if(localReader.IsBehavior(CommandBehavior.KeyInfo)) {
if(!_cmdWrapper._ssKeyInfoModeOn) {
TrySetStatementAttribute(stmt, (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.NOBROWSETABLE, (IntPtr)ODBC32.SQL_NB.ON);
TrySetStatementAttribute(stmt, (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.HIDDEN_COLUMNS, (IntPtr)ODBC32.SQL_HC.ON);
_cmdWrapper._ssKeyInfoModeOff = false;
_cmdWrapper._ssKeyInfoModeOn = true;
}
}
else {
if(!_cmdWrapper._ssKeyInfoModeOff) {
TrySetStatementAttribute(stmt, (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.NOBROWSETABLE, (IntPtr)ODBC32.SQL_NB.OFF);
TrySetStatementAttribute(stmt, (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.HIDDEN_COLUMNS, (IntPtr)ODBC32.SQL_HC.OFF);
_cmdWrapper._ssKeyInfoModeOff = true;
_cmdWrapper._ssKeyInfoModeOn = false;
}
}
}
}
}
if(localReader.IsBehavior(CommandBehavior.KeyInfo) ||
localReader.IsBehavior(CommandBehavior.SchemaOnly)) {
retcode = stmt.Prepare(CommandText);
if(ODBC32.RetCode.SUCCESS != retcode) {
_connection.HandleError(stmt, retcode);
}
}
bool mustRelease = false;
CNativeBuffer parameterBuffer = _cmdWrapper._nativeParameterBuffer;
RuntimeHelpers.PrepareConstrainedRegions();
try {
//Handle Parameters
//Note: We use the internal variable as to not instante a new object collection,
//for the the common case of using no parameters.
if((null != _parameterCollection) && (0 < _parameterCollection.Count)) {
int parameterBufferSize = _parameterCollection.CalcParameterBufferSize(this);
if(null == parameterBuffer || parameterBuffer.Length < parameterBufferSize) {
if (null != parameterBuffer) {
parameterBuffer.Dispose();
}
parameterBuffer = new CNativeBuffer(parameterBufferSize);
_cmdWrapper._nativeParameterBuffer = parameterBuffer;
}
else {
parameterBuffer.ZeroMemory();
}
parameterBuffer.DangerousAddRef(ref mustRelease);
_parameterCollection.Bind(this, _cmdWrapper, parameterBuffer);
}
if(!localReader.IsBehavior(CommandBehavior.SchemaOnly)) {
// Can't get the KeyInfo after command execution (SQL Server only since it does not support multiple
// results on the same connection). Stored procedures (SP) do not return metadata before actual execution
// Need to check the column count since the command type may not be set to SP for a SP.
if((localReader.IsBehavior(CommandBehavior.KeyInfo) || localReader.IsBehavior(CommandBehavior.SchemaOnly))
&& (CommandType != CommandType.StoredProcedure)) {
Int16 cColsAffected;
retcode = stmt.NumberOfResultColumns(out cColsAffected);
if(retcode == ODBC32.RetCode.SUCCESS || retcode == ODBC32.RetCode.SUCCESS_WITH_INFO) {
if(cColsAffected > 0) {
localReader.GetSchemaTable();
}
}
else if(retcode == ODBC32.RetCode.NO_DATA) {
// do nothing
}
else {
// any other returncode indicates an error
_connection.HandleError(stmt, retcode);
}
}
switch(odbcApiMethod) {
case ODBC32.SQL_API.SQLEXECDIRECT:
if(localReader.IsBehavior(CommandBehavior.KeyInfo) || _isPrepared) {
//Already prepared, so use SQLExecute
retcode = stmt.Execute();
// Build metadata here
// localReader.GetSchemaTable();
}
else {
#if DEBUG
//if (AdapterSwitches.OleDbTrace.TraceInfo) {
// ADP.DebugWriteLine("SQLExecDirectW: " + CommandText);
//}
#endif
//SQLExecDirect
retcode = stmt.ExecuteDirect(CommandText);
}
break;
case ODBC32.SQL_API.SQLTABLES:
retcode = stmt.Tables((string)methodArguments[0], //TableCatalog
(string)methodArguments[1], //TableSchema,
(string)methodArguments[2], //TableName
(string)methodArguments[3]); //TableType
break;
case ODBC32.SQL_API.SQLCOLUMNS:
retcode = stmt.Columns((string)methodArguments[0], //TableCatalog
(string)methodArguments[1], //TableSchema
(string)methodArguments[2], //TableName
(string)methodArguments[3]); //ColumnName
break;
case ODBC32.SQL_API.SQLPROCEDURES:
retcode = stmt.Procedures((string)methodArguments[0], //ProcedureCatalog
(string)methodArguments[1], //ProcedureSchema
(string)methodArguments[2]); //procedureName
break;
case ODBC32.SQL_API.SQLPROCEDURECOLUMNS:
retcode = stmt.ProcedureColumns((string)methodArguments[0], //ProcedureCatalog
(string)methodArguments[1], //ProcedureSchema
(string)methodArguments[2], //procedureName
(string)methodArguments[3]); //columnName
break;
case ODBC32.SQL_API.SQLSTATISTICS:
retcode = stmt.Statistics((string)methodArguments[0], //TableCatalog
(string)methodArguments[1], //TableSchema
(string)methodArguments[2], //TableName
(Int16)methodArguments[3], //IndexTrpe
(Int16)methodArguments[4]); //Accuracy
break;
case ODBC32.SQL_API.SQLGETTYPEINFO:
retcode = stmt.GetTypeInfo((Int16)methodArguments[0]); //SQL Type
break;
default:
// this should NEVER happen
Debug.Assert(false, "ExecuteReaderObjectcalled with unsupported ODBC API method.");
throw ADP.InvalidOperation(method.ToString());
}
//Note: Execute will return NO_DATA for Update/Delete non-row returning queries
if((ODBC32.RetCode.SUCCESS != retcode) && (ODBC32.RetCode.NO_DATA != retcode)) {
_connection.HandleError(stmt, retcode);
}
} // end SchemaOnly
}
finally {
if(mustRelease) {
parameterBuffer.DangerousRelease();
}
}
this.weakDataReaderReference = new WeakReference(localReader);
// XXXCommand.Execute should position reader on first row returning result
// any exceptions in the initial non-row returning results should be thrown
// from from ExecuteXXX not the DataReader
if(!localReader.IsBehavior(CommandBehavior.SchemaOnly)) {
localReader.FirstResult();
}
cmdState = ConnectionState.Fetching;
}
finally {
if(ConnectionState.Fetching != cmdState) {
if(null != localReader) {
// clear bindings so we don't grab output parameters on a failed execute
if(null != _parameterCollection) {
_parameterCollection.ClearBindings();
}
((IDisposable)localReader).Dispose();
}
if(ConnectionState.Closed != cmdState) {
cmdState = ConnectionState.Closed;
}
}
}
return localReader;
}
override public object ExecuteScalar() {
OdbcConnection.ExecutePermission.Demand();
object value = null;
using(IDataReader reader = ExecuteReaderObject(0, ADP.ExecuteScalar, false)) {
if (reader.Read() && (0 < reader.FieldCount)) {
value = reader.GetValue(0);
}
reader.Close();
}
return value;
}
internal string GetDiagSqlState() {
return _cmdWrapper.GetDiagSqlState();
}
private void PropertyChanging() {
_isPrepared = false;
}
// Prepare
//
// if the CommandType property is set to TableDirect Prepare does nothing.
// if the CommandType property is set to StoredProcedure Prepare should succeed but result
// in a no-op
//
// throw InvalidOperationException
// if the connection is not set
// if the connection is not open
//
override public void Prepare() {
OdbcConnection.ExecutePermission.Demand();
ODBC32.RetCode retcode;
ValidateOpenConnection(ADP.Prepare);
if (0 != (ConnectionState.Fetching & _connection.InternalState)) {
throw ADP.OpenReaderExists();
}
if (CommandType == CommandType.TableDirect) {
return; // do nothing
}
DisposeDeadDataReader();
GetStatementHandle();
OdbcStatementHandle stmt = _cmdWrapper.StatementHandle;
retcode = stmt.Prepare(CommandText);
if (ODBC32.RetCode.SUCCESS != retcode) {
_connection.HandleError(stmt, retcode);
}
_isPrepared = true;
}
void TrySetStatementAttribute (OdbcStatementHandle stmt, ODBC32.SQL_ATTR stmtAttribute, IntPtr value) {
ODBC32.RetCode retcode = stmt.SetStatementAttribute(
stmtAttribute,
value,
ODBC32.SQL_IS.UINTEGER);
if (retcode == ODBC32.RetCode.ERROR) {
string sqlState;
stmt.GetDiagnosticField(out sqlState);
if ((sqlState == "HYC00") || (sqlState == "HY092")) {
Connection.FlagUnsupportedStmtAttr(stmtAttribute);
}
else {
// now what? Should we throw?
}
}
}
private void ValidateOpenConnection(string methodName) {
// see if we have a connection
OdbcConnection connection = Connection;
if (null == connection) {
throw ADP.ConnectionRequired(methodName);
}
// must have an open and available connection
ConnectionState state = connection.State;
if (ConnectionState.Open != state) {
throw ADP.OpenConnectionRequired(methodName, state);
}
}
private void ValidateConnectionAndTransaction(string method) {
if (null == _connection) {
throw ADP.ConnectionRequired(method);
}
_transaction = _connection.SetStateExecuting(method, Transaction);
cmdState = ConnectionState.Executing;
}
}
sealed internal class CMDWrapper {
private OdbcStatementHandle _stmt; // hStmt
private OdbcStatementHandle _keyinfostmt; // hStmt for keyinfo
internal OdbcDescriptorHandle _hdesc; // hDesc
internal CNativeBuffer _nativeParameterBuffer; // Native memory for internal memory management
// (Performance optimization)
internal CNativeBuffer _dataReaderBuf; // Reusable DataReader buffer
private readonly OdbcConnection _connection; // Connection
private bool _canceling; // true if the command is canceling
internal bool _hasBoundColumns;
internal bool _ssKeyInfoModeOn; // tells us if the SqlServer specific options are on
internal bool _ssKeyInfoModeOff; // a tri-state value would be much better ...
internal CMDWrapper (OdbcConnection connection) {
_connection = connection;
}
internal bool Canceling {
get {
return _canceling;
}
set {
_canceling = value;
}
}
internal OdbcConnection Connection {
get {
return _connection;
}
}
internal bool HasBoundColumns {
// get {
// return _hasBoundColumns;
// }
set {
_hasBoundColumns = value;
}
}
internal OdbcStatementHandle StatementHandle {
get { return _stmt; }
}
internal OdbcStatementHandle KeyInfoStatement {
get {
return _keyinfostmt;
}
}
internal void CreateKeyInfoStatementHandle() {
DisposeKeyInfoStatementHandle();
_keyinfostmt = _connection.CreateStatementHandle();
}
internal void CreateStatementHandle() {
DisposeStatementHandle();
_stmt = _connection.CreateStatementHandle();
}
internal void Dispose() {
if (null != _dataReaderBuf) {
_dataReaderBuf.Dispose();
_dataReaderBuf = null;
}
DisposeStatementHandle();
CNativeBuffer buffer = _nativeParameterBuffer;
_nativeParameterBuffer = null;
if (null != buffer) {
buffer.Dispose();
}
_ssKeyInfoModeOn = false;
_ssKeyInfoModeOff = false;
}
private void DisposeDescriptorHandle() {
OdbcDescriptorHandle handle = _hdesc;
if (null != handle) {
_hdesc = null;
handle.Dispose();
}
}
internal void DisposeStatementHandle() {
DisposeKeyInfoStatementHandle();
DisposeDescriptorHandle();
OdbcStatementHandle handle = _stmt;
if (null != handle) {
_stmt = null;
handle.Dispose();
}
}
internal void DisposeKeyInfoStatementHandle() {
OdbcStatementHandle handle = _keyinfostmt;
if (null != handle) {
_keyinfostmt = null;
handle.Dispose();
}
}
internal void FreeStatementHandle(ODBC32.STMT stmt) {
DisposeDescriptorHandle();
OdbcStatementHandle handle = _stmt;
if (null != handle) {
try {
ODBC32.RetCode retcode;
retcode = handle.FreeStatement(stmt);
StatementErrorHandler(retcode);
}
catch (Exception e) {
//
if (ADP.IsCatchableExceptionType(e)) {
_stmt = null;
handle.Dispose();
}
throw;
}
}
}
internal void FreeKeyInfoStatementHandle(ODBC32.STMT stmt) {
OdbcStatementHandle handle = _keyinfostmt;
if (null != handle) {
try {
handle.FreeStatement(stmt);
}
catch (Exception e) {
//
if (ADP.IsCatchableExceptionType(e)) {
_keyinfostmt = null;
handle.Dispose();
}
throw;
}
}
}
// Get the Descriptor Handle for the current statement
//
internal OdbcDescriptorHandle GetDescriptorHandle(ODBC32.SQL_ATTR attribute) {
OdbcDescriptorHandle hdesc = _hdesc;
if (null == _hdesc) {
_hdesc = hdesc = new OdbcDescriptorHandle(_stmt, attribute);
}
return hdesc;
}
internal string GetDiagSqlState () {
string sqlstate;
_stmt.GetDiagnosticField(out sqlstate);
return sqlstate;
}
internal void StatementErrorHandler(ODBC32.RetCode retcode) {
switch(retcode) {
case ODBC32.RetCode.SUCCESS:
case ODBC32.RetCode.SUCCESS_WITH_INFO:
_connection.HandleErrorNoThrow(_stmt, retcode);
break;
default:
throw _connection.HandleErrorNoThrow(_stmt, retcode);
}
}
internal void UnbindStmtColumns() {
if (_hasBoundColumns) {
FreeStatementHandle(ODBC32.STMT.UNBIND);
_hasBoundColumns = false;
}
}
}
}
|