Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data / System / Data / Odbc / OdbcCommandBuilder.cs
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
27         public OdbcCommandBuilder() : base() {
28             GC.SuppressFinalize(this);
29         }
30
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
49         private void OdbcRowUpdatingHandler(object sender, OdbcRowUpdatingEventArgs ruevent) {
50             RowUpdatingHandler(ruevent);
51         }
52
53         new public OdbcCommand GetInsertCommand() {
54             return (OdbcCommand) base.GetInsertCommand();
55         }
56         new public OdbcCommand GetInsertCommand(bool useColumnsForParameterNames) {
57             return (OdbcCommand) base.GetInsertCommand(useColumnsForParameterNames);
58         }
59
60         new public OdbcCommand GetUpdateCommand() {
61             return (OdbcCommand) base.GetUpdateCommand();
62         }
63         new public OdbcCommand GetUpdateCommand(bool useColumnsForParameterNames) {
64             return (OdbcCommand) base.GetUpdateCommand(useColumnsForParameterNames);
65         }
66
67         new public OdbcCommand GetDeleteCommand() {
68             return (OdbcCommand) base.GetDeleteCommand();
69         }
70         new public OdbcCommand GetDeleteCommand(bool useColumnsForParameterNames) {
71             return (OdbcCommand) base.GetDeleteCommand(useColumnsForParameterNames);
72         }
73
74         override protected string GetParameterName(int parameterOrdinal) {
75             return "p" + parameterOrdinal.ToString(System.Globalization.CultureInfo.InvariantCulture);
76         }
77         override protected string GetParameterName(string parameterName) {
78             return parameterName;
79         }
80
81         override protected string GetParameterPlaceholder(int parameterOrdinal) {
82             return "?";
83         }
84
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
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
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
237         public override string QuoteIdentifier(string unquotedIdentifier){
238             return QuoteIdentifier(unquotedIdentifier, null /* use DataAdapter.SelectCommand.Connection if available */);
239         }
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
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
284         public override string UnquoteIdentifier( string quotedIdentifier){
285             return UnquoteIdentifier(quotedIdentifier, null /* use DataAdapter.SelectCommand.Connection if available */);
286         }
287
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 }