1 //------------------------------------------------------------------------------ 2 // <copyright file="OdbcCommandBuilder.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 // <owner current="true" primary="true">Microsoft</owner> 6 // <owner current="true" primary="false">Microsoft</owner> 7 //------------------------------------------------------------------------------ 8 9 using System; 10 using System.Collections; 11 using System.Collections.Generic; 12 using System.ComponentModel; 13 using System.Data; 14 using System.Data.Common; 15 using System.Data.ProviderBase; 16 using System.Diagnostics; 17 using System.Runtime.InteropServices; 18 using System.Threading; 19 using System.Globalization; 20 using System.Text; 21 22 23 namespace System.Data.Odbc { 24 25 public sealed class OdbcCommandBuilder : DbCommandBuilder { 26 OdbcCommandBuilder()27 public OdbcCommandBuilder() : base() { 28 GC.SuppressFinalize(this); 29 } 30 OdbcCommandBuilder(OdbcDataAdapter adapter)31 public OdbcCommandBuilder(OdbcDataAdapter adapter) : this() { 32 DataAdapter = adapter; 33 } 34 35 [ 36 DefaultValue(null), 37 ResCategoryAttribute(Res.DataCategory_Update), 38 ResDescriptionAttribute(Res.OdbcCommandBuilder_DataAdapter), // MDAC 60524 39 ] 40 new public OdbcDataAdapter DataAdapter { 41 get { 42 return (base.DataAdapter as OdbcDataAdapter); 43 } 44 set { 45 base.DataAdapter = value; 46 } 47 } 48 OdbcRowUpdatingHandler(object sender, OdbcRowUpdatingEventArgs ruevent)49 private void OdbcRowUpdatingHandler(object sender, OdbcRowUpdatingEventArgs ruevent) { 50 RowUpdatingHandler(ruevent); 51 } 52 GetInsertCommand()53 new public OdbcCommand GetInsertCommand() { 54 return (OdbcCommand) base.GetInsertCommand(); 55 } GetInsertCommand(bool useColumnsForParameterNames)56 new public OdbcCommand GetInsertCommand(bool useColumnsForParameterNames) { 57 return (OdbcCommand) base.GetInsertCommand(useColumnsForParameterNames); 58 } 59 GetUpdateCommand()60 new public OdbcCommand GetUpdateCommand() { 61 return (OdbcCommand) base.GetUpdateCommand(); 62 } GetUpdateCommand(bool useColumnsForParameterNames)63 new public OdbcCommand GetUpdateCommand(bool useColumnsForParameterNames) { 64 return (OdbcCommand) base.GetUpdateCommand(useColumnsForParameterNames); 65 } 66 GetDeleteCommand()67 new public OdbcCommand GetDeleteCommand() { 68 return (OdbcCommand) base.GetDeleteCommand(); 69 } GetDeleteCommand(bool useColumnsForParameterNames)70 new public OdbcCommand GetDeleteCommand(bool useColumnsForParameterNames) { 71 return (OdbcCommand) base.GetDeleteCommand(useColumnsForParameterNames); 72 } 73 GetParameterName(int parameterOrdinal)74 override protected string GetParameterName(int parameterOrdinal) { 75 return "p" + parameterOrdinal.ToString(System.Globalization.CultureInfo.InvariantCulture); 76 } GetParameterName(string parameterName)77 override protected string GetParameterName(string parameterName) { 78 return parameterName; 79 } 80 GetParameterPlaceholder(int parameterOrdinal)81 override protected string GetParameterPlaceholder(int parameterOrdinal) { 82 return "?"; 83 } 84 ApplyParameterInfo(DbParameter parameter, DataRow datarow, StatementType statementType, bool whereClause)85 override protected void ApplyParameterInfo(DbParameter parameter, DataRow datarow, StatementType statementType, bool whereClause) { 86 OdbcParameter p = (OdbcParameter) parameter; 87 object valueType = datarow[SchemaTableColumn.ProviderType]; 88 p.OdbcType = (OdbcType) valueType; 89 90 object bvalue = datarow[SchemaTableColumn.NumericPrecision]; 91 if (DBNull.Value != bvalue) { 92 byte bval = (byte)(short)bvalue; 93 p.PrecisionInternal = ((0xff != bval) ? bval : (byte)0); 94 } 95 96 bvalue = datarow[SchemaTableColumn.NumericScale]; 97 if (DBNull.Value != bvalue) { 98 byte bval = (byte)(short)bvalue; 99 p.ScaleInternal = ((0xff != bval) ? bval : (byte)0); 100 } 101 } 102 DeriveParameters(OdbcCommand command)103 static public void DeriveParameters(OdbcCommand command) { 104 // MDAC 65927 105 OdbcConnection.ExecutePermission.Demand(); 106 107 if (null == command) { 108 throw ADP.ArgumentNull("command"); 109 } 110 switch (command.CommandType) { 111 case System.Data.CommandType.Text: 112 throw ADP.DeriveParametersNotSupported(command); 113 case System.Data.CommandType.StoredProcedure: 114 break; 115 case System.Data.CommandType.TableDirect: 116 // CommandType.TableDirect - do nothing, parameters are not supported 117 throw ADP.DeriveParametersNotSupported(command); 118 default: 119 throw ADP.InvalidCommandType(command.CommandType); 120 } 121 if (ADP.IsEmpty(command.CommandText)) { 122 throw ADP.CommandTextRequired(ADP.DeriveParameters); 123 } 124 125 OdbcConnection connection = command.Connection; 126 127 if (null == connection) { 128 throw ADP.ConnectionRequired(ADP.DeriveParameters); 129 } 130 131 ConnectionState state = connection.State; 132 133 if (ConnectionState.Open != state) { 134 throw ADP.OpenConnectionRequired(ADP.DeriveParameters, state); 135 } 136 137 OdbcParameter[] list = DeriveParametersFromStoredProcedure(connection, command); 138 139 OdbcParameterCollection parameters = command.Parameters; 140 parameters.Clear(); 141 142 int count = list.Length; 143 if (0 < count) { 144 for(int i = 0; i < list.Length; ++i) { 145 parameters.Add(list[i]); 146 } 147 } 148 } 149 150 151 // DeriveParametersFromStoredProcedure ( 152 // OdbcConnection connection, 153 // OdbcCommand command); 154 // 155 // Uses SQLProcedureColumns to create an array of OdbcParameters 156 // 157 DeriveParametersFromStoredProcedure(OdbcConnection connection, OdbcCommand command)158 static private OdbcParameter[] DeriveParametersFromStoredProcedure(OdbcConnection connection, OdbcCommand command) { 159 List<OdbcParameter> rParams = new List<OdbcParameter>(); 160 161 // following call ensures that the command has a statement handle allocated 162 CMDWrapper cmdWrapper = command.GetStatementHandle(); 163 OdbcStatementHandle hstmt = cmdWrapper.StatementHandle; 164 int cColsAffected; 165 166 // maps an enforced 4-part qualified string as follows 167 // parts[0] = null - ignored but removal would be a run-time breaking change from V1.0 168 // parts[1] = CatalogName (optional, may be null) 169 // parts[2] = SchemaName (optional, may be null) 170 // parts[3] = ProcedureName 171 // 172 string quote = connection.QuoteChar(ADP.DeriveParameters); 173 string[] parts = MultipartIdentifier.ParseMultipartIdentifier(command.CommandText, quote, quote, '.', 4, true, Res.ODBC_ODBCCommandText, false); 174 if (null == parts[3]) { // match everett behavior, if the commandtext is nothing but whitespace set the command text to the whitespace 175 parts[3] = command.CommandText; 176 } 177 // note: native odbc appears to ignore all but the procedure name 178 ODBC32.RetCode retcode = hstmt.ProcedureColumns(parts[1], parts[2], parts[3], null); 179 180 // Note: the driver does not return an error if the given stored procedure does not exist 181 // therefore we cannot handle that case and just return not parameters. 182 183 if (ODBC32.RetCode.SUCCESS != retcode) { 184 connection.HandleError(hstmt, retcode); 185 } 186 187 using (OdbcDataReader reader = new OdbcDataReader(command, cmdWrapper, CommandBehavior.Default)) { 188 reader.FirstResult(); 189 cColsAffected = reader.FieldCount; 190 191 // go through the returned rows and filter out relevant parameter data 192 // 193 while (reader.Read()) { 194 // devnote: column types are specified in the ODBC Programmer's Reference 195 // COLUMN_TYPE Smallint 16bit 196 // COLUMN_SIZE Integer 32bit 197 // DECIMAL_DIGITS Smallint 16bit 198 // NUM_PREC_RADIX Smallint 16bit 199 200 OdbcParameter parameter = new OdbcParameter(); 201 202 parameter.ParameterName = reader.GetString(ODBC32.COLUMN_NAME-1); 203 switch ((ODBC32.SQL_PARAM)reader.GetInt16(ODBC32.COLUMN_TYPE-1)){ 204 case ODBC32.SQL_PARAM.INPUT: 205 parameter.Direction = ParameterDirection.Input; 206 break; 207 case ODBC32.SQL_PARAM.OUTPUT: 208 parameter.Direction = ParameterDirection.Output; 209 break; 210 211 case ODBC32.SQL_PARAM.INPUT_OUTPUT: 212 parameter.Direction = ParameterDirection.InputOutput; 213 break; 214 case ODBC32.SQL_PARAM.RETURN_VALUE: 215 parameter.Direction = ParameterDirection.ReturnValue; 216 break; 217 default: 218 Debug.Assert(false, "Unexpected Parametertype while DeriveParamters"); 219 break; 220 } 221 parameter.OdbcType = TypeMap.FromSqlType((ODBC32.SQL_TYPE)reader.GetInt16(ODBC32.DATA_TYPE-1))._odbcType; 222 parameter.Size = (int)reader.GetInt32(ODBC32.COLUMN_SIZE-1); 223 switch(parameter.OdbcType){ 224 case OdbcType.Decimal: 225 case OdbcType.Numeric: 226 parameter.ScaleInternal = (Byte)reader.GetInt16(ODBC32.DECIMAL_DIGITS-1); 227 parameter.PrecisionInternal = (Byte)reader.GetInt16(ODBC32.NUM_PREC_RADIX-1); 228 break; 229 } 230 rParams.Add (parameter); 231 } 232 } 233 retcode = hstmt.CloseCursor(); 234 return rParams.ToArray();; 235 } 236 QuoteIdentifier(string unquotedIdentifier)237 public override string QuoteIdentifier(string unquotedIdentifier){ 238 return QuoteIdentifier(unquotedIdentifier, null /* use DataAdapter.SelectCommand.Connection if available */); 239 } QuoteIdentifier( string unquotedIdentifier, OdbcConnection connection)240 public string QuoteIdentifier( string unquotedIdentifier, OdbcConnection connection){ 241 242 ADP.CheckArgumentNull(unquotedIdentifier, "unquotedIdentifier"); 243 244 // if the user has specificed a prefix use the user specified prefix and suffix 245 // otherwise get them from the provider 246 string quotePrefix = QuotePrefix; 247 string quoteSuffix = QuoteSuffix; 248 if (ADP.IsEmpty(quotePrefix) == true) { 249 if (connection == null) { 250 // VSTFDEVDIV 479567: use the adapter's connection if QuoteIdentifier was called from 251 // DbCommandBuilder instance (which does not have an overload that gets connection object) 252 connection = base.GetConnection() as OdbcConnection; 253 if (connection == null) { 254 throw ADP.QuotePrefixNotSet(ADP.QuoteIdentifier); 255 } 256 } 257 quotePrefix = connection.QuoteChar(ADP.QuoteIdentifier); 258 quoteSuffix = quotePrefix; 259 } 260 261 // by the ODBC spec "If the data source does not support quoted identifiers, a blank is returned." 262 // So if a blank is returned the string is returned unchanged. Otherwise the returned string is used 263 // to quote the string 264 if ((ADP.IsEmpty(quotePrefix) == false) && (quotePrefix != " ")) { 265 return ADP.BuildQuotedString(quotePrefix,quoteSuffix,unquotedIdentifier); 266 } 267 else { 268 return unquotedIdentifier; 269 } 270 } 271 272 273 SetRowUpdatingHandler(DbDataAdapter adapter)274 override protected void SetRowUpdatingHandler(DbDataAdapter adapter) { 275 Debug.Assert(adapter is OdbcDataAdapter, "!OdbcDataAdapter"); 276 if (adapter == base.DataAdapter) { // removal case 277 ((OdbcDataAdapter)adapter).RowUpdating -= OdbcRowUpdatingHandler; 278 } 279 else { // adding case 280 ((OdbcDataAdapter)adapter).RowUpdating += OdbcRowUpdatingHandler; 281 } 282 } 283 UnquoteIdentifier( string quotedIdentifier)284 public override string UnquoteIdentifier( string quotedIdentifier){ 285 return UnquoteIdentifier(quotedIdentifier, null /* use DataAdapter.SelectCommand.Connection if available */); 286 } 287 UnquoteIdentifier(string quotedIdentifier, OdbcConnection connection)288 public string UnquoteIdentifier(string quotedIdentifier, OdbcConnection connection){ 289 290 ADP.CheckArgumentNull(quotedIdentifier, "quotedIdentifier"); 291 292 293 // if the user has specificed a prefix use the user specified prefix and suffix 294 // otherwise get them from the provider 295 string quotePrefix = QuotePrefix; 296 string quoteSuffix = QuoteSuffix; 297 if (ADP.IsEmpty(quotePrefix) == true) { 298 if (connection == null) { 299 // VSTFDEVDIV 479567: use the adapter's connection if UnquoteIdentifier was called from 300 // DbCommandBuilder instance (which does not have an overload that gets connection object) 301 connection = base.GetConnection() as OdbcConnection; 302 if (connection == null) { 303 throw ADP.QuotePrefixNotSet(ADP.UnquoteIdentifier); 304 } 305 } 306 quotePrefix = connection.QuoteChar(ADP.UnquoteIdentifier); 307 quoteSuffix = quotePrefix; 308 } 309 310 String unquotedIdentifier; 311 // by the ODBC spec "If the data source does not support quoted identifiers, a blank is returned." 312 // So if a blank is returned the string is returned unchanged. Otherwise the returned string is used 313 // to unquote the string 314 if ((ADP.IsEmpty(quotePrefix) == false) || (quotePrefix != " ")) { 315 // ignoring the return value because it is acceptable for the quotedString to not be quoted in this 316 // context. 317 ADP.RemoveStringQuotes(quotePrefix, quoteSuffix, quotedIdentifier, out unquotedIdentifier); 318 } 319 else { 320 unquotedIdentifier = quotedIdentifier; 321 } 322 return unquotedIdentifier; 323 324 } 325 326 } 327 } 328