1 //------------------------------------------------------------------------------
2 // <copyright file="OdbcCommandBuilder.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
10 using System.Collections;
11 using System.Collections.Generic;
12 using System.ComponentModel;
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;
23 namespace System.Data.Odbc {
25 public sealed class OdbcCommandBuilder : DbCommandBuilder {
27 public OdbcCommandBuilder() : base() {
28 GC.SuppressFinalize(this);
31 public OdbcCommandBuilder(OdbcDataAdapter adapter) : this() {
32 DataAdapter = adapter;
37 ResCategoryAttribute(Res.DataCategory_Update),
38 ResDescriptionAttribute(Res.OdbcCommandBuilder_DataAdapter), // MDAC 60524
40 new public OdbcDataAdapter DataAdapter {
42 return (base.DataAdapter as OdbcDataAdapter);
45 base.DataAdapter = value;
49 private void OdbcRowUpdatingHandler(object sender, OdbcRowUpdatingEventArgs ruevent) {
50 RowUpdatingHandler(ruevent);
53 new public OdbcCommand GetInsertCommand() {
54 return (OdbcCommand) base.GetInsertCommand();
56 new public OdbcCommand GetInsertCommand(bool useColumnsForParameterNames) {
57 return (OdbcCommand) base.GetInsertCommand(useColumnsForParameterNames);
60 new public OdbcCommand GetUpdateCommand() {
61 return (OdbcCommand) base.GetUpdateCommand();
63 new public OdbcCommand GetUpdateCommand(bool useColumnsForParameterNames) {
64 return (OdbcCommand) base.GetUpdateCommand(useColumnsForParameterNames);
67 new public OdbcCommand GetDeleteCommand() {
68 return (OdbcCommand) base.GetDeleteCommand();
70 new public OdbcCommand GetDeleteCommand(bool useColumnsForParameterNames) {
71 return (OdbcCommand) base.GetDeleteCommand(useColumnsForParameterNames);
74 override protected string GetParameterName(int parameterOrdinal) {
75 return "p" + parameterOrdinal.ToString(System.Globalization.CultureInfo.InvariantCulture);
77 override protected string GetParameterName(string parameterName) {
81 override protected string GetParameterPlaceholder(int parameterOrdinal) {
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;
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);
96 bvalue = datarow[SchemaTableColumn.NumericScale];
97 if (DBNull.Value != bvalue) {
98 byte bval = (byte)(short)bvalue;
99 p.ScaleInternal = ((0xff != bval) ? bval : (byte)0);
103 static public void DeriveParameters(OdbcCommand command) {
105 OdbcConnection.ExecutePermission.Demand();
107 if (null == command) {
108 throw ADP.ArgumentNull("command");
110 switch (command.CommandType) {
111 case System.Data.CommandType.Text:
112 throw ADP.DeriveParametersNotSupported(command);
113 case System.Data.CommandType.StoredProcedure:
115 case System.Data.CommandType.TableDirect:
116 // CommandType.TableDirect - do nothing, parameters are not supported
117 throw ADP.DeriveParametersNotSupported(command);
119 throw ADP.InvalidCommandType(command.CommandType);
121 if (ADP.IsEmpty(command.CommandText)) {
122 throw ADP.CommandTextRequired(ADP.DeriveParameters);
125 OdbcConnection connection = command.Connection;
127 if (null == connection) {
128 throw ADP.ConnectionRequired(ADP.DeriveParameters);
131 ConnectionState state = connection.State;
133 if (ConnectionState.Open != state) {
134 throw ADP.OpenConnectionRequired(ADP.DeriveParameters, state);
137 OdbcParameter[] list = DeriveParametersFromStoredProcedure(connection, command);
139 OdbcParameterCollection parameters = command.Parameters;
142 int count = list.Length;
144 for(int i = 0; i < list.Length; ++i) {
145 parameters.Add(list[i]);
151 // DeriveParametersFromStoredProcedure (
152 // OdbcConnection connection,
153 // OdbcCommand command);
155 // Uses SQLProcedureColumns to create an array of OdbcParameters
158 static private OdbcParameter[] DeriveParametersFromStoredProcedure(OdbcConnection connection, OdbcCommand command) {
159 List<OdbcParameter> rParams = new List<OdbcParameter>();
161 // following call ensures that the command has a statement handle allocated
162 CMDWrapper cmdWrapper = command.GetStatementHandle();
163 OdbcStatementHandle hstmt = cmdWrapper.StatementHandle;
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
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;
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);
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.
183 if (ODBC32.RetCode.SUCCESS != retcode) {
184 connection.HandleError(hstmt, retcode);
187 using (OdbcDataReader reader = new OdbcDataReader(command, cmdWrapper, CommandBehavior.Default)) {
188 reader.FirstResult();
189 cColsAffected = reader.FieldCount;
191 // go through the returned rows and filter out relevant parameter data
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
200 OdbcParameter parameter = new OdbcParameter();
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;
207 case ODBC32.SQL_PARAM.OUTPUT:
208 parameter.Direction = ParameterDirection.Output;
211 case ODBC32.SQL_PARAM.INPUT_OUTPUT:
212 parameter.Direction = ParameterDirection.InputOutput;
214 case ODBC32.SQL_PARAM.RETURN_VALUE:
215 parameter.Direction = ParameterDirection.ReturnValue;
218 Debug.Assert(false, "Unexpected Parametertype while DeriveParamters");
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);
230 rParams.Add (parameter);
233 retcode = hstmt.CloseCursor();
234 return rParams.ToArray();;
237 public override string QuoteIdentifier(string unquotedIdentifier){
238 return QuoteIdentifier(unquotedIdentifier, null /* use DataAdapter.SelectCommand.Connection if available */);
240 public string QuoteIdentifier( string unquotedIdentifier, OdbcConnection connection){
242 ADP.CheckArgumentNull(unquotedIdentifier, "unquotedIdentifier");
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);
257 quotePrefix = connection.QuoteChar(ADP.QuoteIdentifier);
258 quoteSuffix = quotePrefix;
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);
268 return unquotedIdentifier;
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;
279 else { // adding case
280 ((OdbcDataAdapter)adapter).RowUpdating += OdbcRowUpdatingHandler;
284 public override string UnquoteIdentifier( string quotedIdentifier){
285 return UnquoteIdentifier(quotedIdentifier, null /* use DataAdapter.SelectCommand.Connection if available */);
288 public string UnquoteIdentifier(string quotedIdentifier, OdbcConnection connection){
290 ADP.CheckArgumentNull(quotedIdentifier, "quotedIdentifier");
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);
306 quotePrefix = connection.QuoteChar(ADP.UnquoteIdentifier);
307 quoteSuffix = quotePrefix;
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
317 ADP.RemoveStringQuotes(quotePrefix, quoteSuffix, quotedIdentifier, out unquotedIdentifier);
320 unquotedIdentifier = quotedIdentifier;
322 return unquotedIdentifier;