|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics;
namespace System.Data.Odbc
{
public sealed class OdbcCommandBuilder : DbCommandBuilder
{
public OdbcCommandBuilder() : base()
{
GC.SuppressFinalize(this);
}
public OdbcCommandBuilder(OdbcDataAdapter? adapter) : this()
{
DataAdapter = adapter;
}
public new OdbcDataAdapter? DataAdapter
{
get
{
return (base.DataAdapter as OdbcDataAdapter);
}
set
{
base.DataAdapter = value;
}
}
private void OdbcRowUpdatingHandler(object sender, OdbcRowUpdatingEventArgs ruevent)
{
RowUpdatingHandler(ruevent);
}
public new OdbcCommand GetInsertCommand()
{
return (OdbcCommand)base.GetInsertCommand();
}
public new OdbcCommand GetInsertCommand(bool useColumnsForParameterNames)
{
return (OdbcCommand)base.GetInsertCommand(useColumnsForParameterNames);
}
public new OdbcCommand GetUpdateCommand()
{
return (OdbcCommand)base.GetUpdateCommand();
}
public new OdbcCommand GetUpdateCommand(bool useColumnsForParameterNames)
{
return (OdbcCommand)base.GetUpdateCommand(useColumnsForParameterNames);
}
public new OdbcCommand GetDeleteCommand()
{
return (OdbcCommand)base.GetDeleteCommand();
}
public new OdbcCommand GetDeleteCommand(bool useColumnsForParameterNames)
{
return (OdbcCommand)base.GetDeleteCommand(useColumnsForParameterNames);
}
protected override string GetParameterName(int parameterOrdinal)
{
return "p" + parameterOrdinal.ToString(System.Globalization.CultureInfo.InvariantCulture);
}
protected override string GetParameterName(string parameterName)
{
return parameterName;
}
protected override string GetParameterPlaceholder(int parameterOrdinal)
{
return "?";
}
protected override void ApplyParameterInfo(DbParameter parameter, DataRow datarow, StatementType statementType, bool whereClause)
{
OdbcParameter p = (OdbcParameter)parameter;
object valueType = datarow[SchemaTableColumn.ProviderType];
p.OdbcType = (OdbcType)valueType;
object bvalue = datarow[SchemaTableColumn.NumericPrecision];
if (DBNull.Value != bvalue)
{
byte bval = (byte)(short)bvalue;
p.PrecisionInternal = ((0xff != bval) ? bval : (byte)0);
}
bvalue = datarow[SchemaTableColumn.NumericScale];
if (DBNull.Value != bvalue)
{
byte bval = (byte)(short)bvalue;
p.ScaleInternal = ((0xff != bval) ? bval : (byte)0);
}
}
public static void DeriveParameters(OdbcCommand command)
{
// MDAC 65927
if (null == command)
{
throw ADP.ArgumentNull(nameof(command));
}
switch (command.CommandType)
{
case System.Data.CommandType.Text:
throw ADP.DeriveParametersNotSupported(command);
case System.Data.CommandType.StoredProcedure:
break;
case System.Data.CommandType.TableDirect:
// CommandType.TableDirect - do nothing, parameters are not supported
throw ADP.DeriveParametersNotSupported(command);
default:
throw ADP.InvalidCommandType(command.CommandType);
}
if (string.IsNullOrEmpty(command.CommandText))
{
throw ADP.CommandTextRequired(ADP.DeriveParameters);
}
OdbcConnection? connection = command.Connection;
if (null == connection)
{
throw ADP.ConnectionRequired(ADP.DeriveParameters);
}
ConnectionState state = connection.State;
if (ConnectionState.Open != state)
{
throw ADP.OpenConnectionRequired(ADP.DeriveParameters, state);
}
OdbcParameter[] list = DeriveParametersFromStoredProcedure(connection, command);
OdbcParameterCollection parameters = command.Parameters;
parameters.Clear();
int count = list.Length;
if (0 < count)
{
for (int i = 0; i < list.Length; ++i)
{
parameters.Add(list[i]);
}
}
}
// DeriveParametersFromStoredProcedure (
// OdbcConnection connection,
// OdbcCommand command);
//
// Uses SQLProcedureColumns to create an array of OdbcParameters
//
private static OdbcParameter[] DeriveParametersFromStoredProcedure(OdbcConnection connection, OdbcCommand command)
{
List<OdbcParameter> rParams = new List<OdbcParameter>();
// following call ensures that the command has a statement handle allocated
CMDWrapper cmdWrapper = command.GetStatementHandle();
OdbcStatementHandle hstmt = cmdWrapper.StatementHandle!;
int cColsAffected;
// maps an enforced 4-part qualified string as follows
// parts[0] = null - ignored but removal would be a run-time breaking change from V1.0
// parts[1] = CatalogName (optional, may be null)
// parts[2] = SchemaName (optional, may be null)
// parts[3] = ProcedureName
//
string quote = connection.QuoteChar(ADP.DeriveParameters);
string?[] parts = MultipartIdentifier.ParseMultipartIdentifier(command.CommandText, quote, quote, '.', 4, true, SR.ODBC_ODBCCommandText, false);
if (null == parts[3])
{ // match Everett behavior, if the commandtext is nothing but whitespace set the command text to the whitespace
parts[3] = command.CommandText;
}
// note: native odbc appears to ignore all but the procedure name
ODBC32.SQLRETURN retcode = hstmt.ProcedureColumns(parts[1], parts[2], parts[3], null);
// Note: the driver does not return an error if the given stored procedure does not exist
// therefore we cannot handle that case and just return not parameters.
if (ODBC32.SQLRETURN.SUCCESS != retcode)
{
connection.HandleError(hstmt, retcode);
}
using (OdbcDataReader reader = new OdbcDataReader(command, cmdWrapper, CommandBehavior.Default))
{
reader.FirstResult();
cColsAffected = reader.FieldCount;
// go through the returned rows and filter out relevant parameter data
//
while (reader.Read())
{
// devnote: column types are specified in the ODBC Programmer's Reference
// COLUMN_TYPE Smallint 16bit
// COLUMN_SIZE Integer 32bit
// DECIMAL_DIGITS Smallint 16bit
// NUM_PREC_RADIX Smallint 16bit
OdbcParameter parameter = new OdbcParameter();
parameter.ParameterName = reader.GetString(ODBC32.COLUMN_NAME - 1);
switch ((ODBC32.SQL_PARAM)reader.GetInt16(ODBC32.COLUMN_TYPE - 1))
{
case ODBC32.SQL_PARAM.INPUT:
parameter.Direction = ParameterDirection.Input;
break;
case ODBC32.SQL_PARAM.OUTPUT:
parameter.Direction = ParameterDirection.Output;
break;
case ODBC32.SQL_PARAM.INPUT_OUTPUT:
parameter.Direction = ParameterDirection.InputOutput;
break;
case ODBC32.SQL_PARAM.RETURN_VALUE:
parameter.Direction = ParameterDirection.ReturnValue;
break;
default:
Debug.Fail("Unexpected Parametertype while DeriveParameters");
break;
}
parameter.OdbcType = TypeMap.FromSqlType((ODBC32.SQL_TYPE)reader.GetInt16(ODBC32.DATA_TYPE - 1))._odbcType;
parameter.Size = (int)reader.GetInt32(ODBC32.COLUMN_SIZE - 1);
switch (parameter.OdbcType)
{
case OdbcType.Decimal:
case OdbcType.Numeric:
parameter.ScaleInternal = (byte)reader.GetInt16(ODBC32.DECIMAL_DIGITS - 1);
parameter.PrecisionInternal = (byte)reader.GetInt16(ODBC32.NUM_PREC_RADIX - 1);
break;
}
rParams.Add(parameter);
}
}
hstmt.CloseCursor();
return rParams.ToArray();
}
public override string QuoteIdentifier(string unquotedIdentifier)
{
return QuoteIdentifier(unquotedIdentifier, null /* use DataAdapter.SelectCommand.Connection if available */);
}
public string QuoteIdentifier(string unquotedIdentifier, OdbcConnection? connection)
{
ADP.CheckArgumentNull(unquotedIdentifier, nameof(unquotedIdentifier));
// if the user has specified a prefix use the user specified prefix and suffix
// otherwise get them from the provider
string quotePrefix = QuotePrefix;
string quoteSuffix = QuoteSuffix;
if (string.IsNullOrEmpty(quotePrefix))
{
if (connection == null)
{
// Use the adapter's connection if QuoteIdentifier was called from
// DbCommandBuilder instance (which does not have an overload that gets connection object)
connection = DataAdapter?.SelectCommand?.Connection;
if (connection == null)
{
throw ADP.QuotePrefixNotSet(ADP.QuoteIdentifier);
}
}
quotePrefix = connection.QuoteChar(ADP.QuoteIdentifier);
quoteSuffix = quotePrefix;
}
// by the ODBC spec "If the data source does not support quoted identifiers, a blank is returned."
// So if a blank is returned the string is returned unchanged. Otherwise the returned string is used
// to quote the string
if (!string.IsNullOrEmpty(quotePrefix) && quotePrefix != " ")
{
return ADP.BuildQuotedString(quotePrefix, quoteSuffix, unquotedIdentifier);
}
else
{
return unquotedIdentifier;
}
}
protected override void SetRowUpdatingHandler(DbDataAdapter adapter)
{
Debug.Assert(adapter is OdbcDataAdapter, "!OdbcDataAdapter");
if (adapter == base.DataAdapter)
{ // removal case
((OdbcDataAdapter)adapter).RowUpdating -= OdbcRowUpdatingHandler;
}
else
{ // adding case
((OdbcDataAdapter)adapter).RowUpdating += OdbcRowUpdatingHandler;
}
}
public override string UnquoteIdentifier(string quotedIdentifier)
{
return UnquoteIdentifier(quotedIdentifier, null /* use DataAdapter.SelectCommand.Connection if available */);
}
public string UnquoteIdentifier(string quotedIdentifier, OdbcConnection? connection)
{
ADP.CheckArgumentNull(quotedIdentifier, nameof(quotedIdentifier));
// if the user has specified a prefix use the user specified prefix and suffix
// otherwise get them from the provider
string quotePrefix = QuotePrefix;
string quoteSuffix = QuoteSuffix;
if (string.IsNullOrEmpty(quotePrefix))
{
if (connection == null)
{
// Use the adapter's connection if UnquoteIdentifier was called from
// DbCommandBuilder instance (which does not have an overload that gets connection object)
connection = DataAdapter?.SelectCommand?.Connection;
if (connection == null)
{
throw ADP.QuotePrefixNotSet(ADP.UnquoteIdentifier);
}
}
quotePrefix = connection.QuoteChar(ADP.UnquoteIdentifier);
quoteSuffix = quotePrefix;
}
// ignoring the return value because it is acceptable for the quotedString to not be quoted in this context.
string? unquotedIdentifier;
ADP.RemoveStringQuotes(quotePrefix, quoteSuffix, quotedIdentifier, out unquotedIdentifier);
return unquotedIdentifier!;
}
}
}
|