Moved ProviderCollectionTest.cs from System assembly to System.Configuration.
[mono.git] / mcs / class / System.Data / System.Data.Common / DbCommandBuilder.cs
1 //
2 // System.Data.Common.DbCommandBuilder
3 //
4 // Author:
5 //   Tim Coleman (tim@timcoleman.com)
6 //
7 // Copyright (C) Tim Coleman, 2003
8 //
9
10 //
11 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 #if NET_2_0 || TARGET_JVM
34
35 using System.ComponentModel;
36 using System.Data;
37 using System.Text;
38
39 namespace System.Data.Common {
40         public abstract class DbCommandBuilder : Component
41         {
42                 bool _setAllValues = false;
43                 bool _disposed = false;
44
45                 DataTable _dbSchemaTable;
46                 DbDataAdapter _dbDataAdapter = null;
47                 private CatalogLocation _catalogLocation = CatalogLocation.Start;
48                 private ConflictOption _conflictOption;
49
50                 private string _tableName;
51                 private string _catalogSeperator = ".";
52                 private string _quotePrefix;
53                 private string _quoteSuffix;
54                 private string _schemaSeperator = ".";
55                 private DbCommand _dbCommand = null;
56
57                 // Used to construct WHERE clauses
58                 static readonly string clause1 = "({0} = 1 AND {1} IS NULL)";
59                 static readonly string clause2 = "({0} = {1})";
60
61                 DbCommand _deleteCommand;
62                 DbCommand _insertCommand;
63                 DbCommand _updateCommand;
64
65                 #region Constructors
66
67                 protected DbCommandBuilder ()
68                 {
69                 }
70
71                 #endregion // Constructors
72
73                 #region Properties
74
75                 private void BuildCache (bool closeConnection)
76                 {
77                         DbCommand sourceCommand = SourceCommand;
78                         if (sourceCommand == null)
79                                 throw new InvalidOperationException ("The DataAdapter.SelectCommand property needs to be initialized.");
80                         DbConnection connection = sourceCommand.Connection;
81                         if (connection == null)
82                                 throw new InvalidOperationException ("The DataAdapter.SelectCommand.Connection property needs to be initialized.");
83
84                         if (_dbSchemaTable == null) {
85                                 if (connection.State == ConnectionState.Open)
86                                         closeConnection = false;        
87                                 else
88                                         connection.Open ();
89         
90                                 DbDataReader reader = sourceCommand.ExecuteReader (CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo);
91                                 _dbSchemaTable = reader.GetSchemaTable ();
92                                 reader.Close ();
93                                 if (closeConnection)
94                                         connection.Close ();    
95                                 BuildInformation (_dbSchemaTable);
96                         }
97                 }
98                 
99                 private string QuotedTableName {
100                         get { return GetQuotedString (_tableName); }
101                 }
102
103                 private string GetQuotedString (string value)
104                 {
105                         if (value == String.Empty || value == null)
106                                 return value;
107                         if (_quotePrefix == String.Empty && _quoteSuffix == String.Empty)
108                                 return value;
109                         return String.Format ("{0}{1}{2}", _quotePrefix, value, _quoteSuffix);
110                 }
111
112                 private void BuildInformation (DataTable schemaTable)
113                 {
114                         _tableName = String.Empty;
115                         foreach (DataRow schemaRow in schemaTable.Rows) {
116                                 if (schemaRow.IsNull ("BaseTableName") || (string) schemaRow ["BaseTableName"] == String.Empty)
117                                         continue;
118
119                                 if (_tableName == String.Empty) 
120                                         _tableName = (string) schemaRow ["BaseTableName"];
121                                 else if (_tableName != (string) schemaRow["BaseTableName"])
122                                         throw new InvalidOperationException ("Dynamic SQL generation is not supported against multiple base tables.");
123                         }
124                         if (_tableName == String.Empty)
125                                 throw new InvalidOperationException ("Dynamic SQL generation is not supported with no base table.");
126                         _dbSchemaTable = schemaTable;
127                 }
128
129                 private bool IncludedInInsert (DataRow schemaRow)
130                 {
131                         // If the parameter has one of these properties, then we don't include it in the insert:
132                         // AutoIncrement, Hidden, Expression, RowVersion, ReadOnly
133
134                         if (!schemaRow.IsNull ("IsAutoIncrement") && (bool) schemaRow ["IsAutoIncrement"])
135                                 return false;
136 //                      if (!schemaRow.IsNull ("IsHidden") && (bool) schemaRow ["IsHidden"])
137 //                              return false;
138                         if (!schemaRow.IsNull ("IsExpression") && (bool) schemaRow ["IsExpression"])
139                                 return false;
140                         if (!schemaRow.IsNull ("IsRowVersion") && (bool) schemaRow ["IsRowVersion"])
141                                 return false;
142                         if (!schemaRow.IsNull ("IsReadOnly") && (bool) schemaRow ["IsReadOnly"])
143                                 return false;
144                         return true;
145                 }
146
147                 private bool IncludedInUpdate (DataRow schemaRow)
148                 {
149                         // If the parameter has one of these properties, then we don't include it in the insert:
150                         // AutoIncrement, Hidden, RowVersion
151
152                         if (!schemaRow.IsNull ("IsAutoIncrement") && (bool) schemaRow ["IsAutoIncrement"])
153                                 return false;
154 //                      if (!schemaRow.IsNull ("IsHidden") && (bool) schemaRow ["IsHidden"])
155 //                              return false;
156                         if (!schemaRow.IsNull ("IsRowVersion") && (bool) schemaRow ["IsRowVersion"])
157                                 return false;
158                         if (!schemaRow.IsNull ("IsExpression") && (bool) schemaRow ["IsExpression"])
159                                 return false;
160                         if (!schemaRow.IsNull ("IsReadOnly") && (bool) schemaRow ["IsReadOnly"])
161                                 return false;
162
163                         return true;
164                 }
165
166                 private bool IncludedInWhereClause (DataRow schemaRow)
167                 {
168                         if ((bool) schemaRow ["IsLong"])
169                                 return false;
170                         return true;
171                 }
172
173                 private DbCommand CreateDeleteCommand (bool option)
174                 {
175                         // If no table was found, then we can't do an delete
176                         if (QuotedTableName == String.Empty)
177                                 return null;
178
179                         CreateNewCommand (ref _deleteCommand);
180
181                         string command = String.Format ("DELETE FROM {0}", QuotedTableName);
182                         StringBuilder whereClause = new StringBuilder ();
183                         bool keyFound = false;
184                         int parmIndex = 1;
185
186                         foreach (DataRow schemaRow in _dbSchemaTable.Rows) {
187                                 if ((bool)schemaRow["IsExpression"] == true)
188                                         continue;
189                                 if (!IncludedInWhereClause (schemaRow)) 
190                                         continue;
191
192                                 if (whereClause.Length > 0) 
193                                         whereClause.Append (" AND ");
194
195                                 bool isKey = (bool) schemaRow ["IsKey"];
196                                 DbParameter parameter = null;
197
198                                 if (isKey)
199                                         keyFound = true;
200
201                                 //ms.net 1.1 generates the null check for columns even if AllowDBNull is false
202                                 //while ms.net 2.0 does not. Anyways, since both forms are logically equivalent
203                                 //following the 2.0 approach
204                                 bool allowNull = (bool) schemaRow ["AllowDBNull"];
205                                 if (!isKey && allowNull) {
206                                         parameter = _deleteCommand.CreateParameter ();
207                                         if (option) {
208                                                 parameter.ParameterName = String.Format ("@{0}",
209                                                                                          schemaRow ["BaseColumnName"]);
210                                         } else {
211                                                 parameter.ParameterName = String.Format ("@p{0}", parmIndex++);
212                                         }
213                                         String sourceColumnName = (string) schemaRow ["BaseColumnName"];
214                                         parameter.Value = 1;
215
216                                         whereClause.Append ("(");
217                                         whereClause.Append (String.Format (clause1, parameter.ParameterName, 
218                                                                            GetQuotedString (sourceColumnName)));
219                                         whereClause.Append (" OR ");
220                                 }
221
222                                 int index = 0;
223                                 if (option) {
224                                         index = CreateParameter (_deleteCommand, schemaRow);
225                                 } else {
226                                         index = CreateParameter (_deleteCommand, parmIndex++, schemaRow);
227                                 }
228                                 parameter = _deleteCommand.Parameters [index];
229                                 parameter.SourceVersion = DataRowVersion.Original;
230
231                                 whereClause.Append (String.Format (clause2, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
232
233                                 if (!isKey && allowNull)
234                                         whereClause.Append (")");
235                         }
236                         if (!keyFound)
237                                 throw new InvalidOperationException ("Dynamic SQL generation for the DeleteCommand is not supported against a SelectCommand that does not return any key column information.");
238
239                         // We're all done, so bring it on home
240                         string sql = String.Format ("{0} WHERE ({1})", command, whereClause.ToString ());
241                         _deleteCommand.CommandText = sql;
242                         _dbCommand = _deleteCommand;
243                         return _deleteCommand;
244                 }
245
246                 private DbCommand CreateInsertCommand (bool option)
247                 {
248                         if (QuotedTableName == String.Empty)
249                                 return null;
250
251                         CreateNewCommand (ref _insertCommand);
252
253                         string command = String.Format ("INSERT INTO {0}", QuotedTableName);
254                         string sql;
255                         StringBuilder columns = new StringBuilder ();
256                         StringBuilder values = new StringBuilder ();
257
258                         int parmIndex = 1;
259                         foreach (DataRow schemaRow in _dbSchemaTable.Rows) {
260                                 if (!IncludedInInsert (schemaRow))
261                                         continue;
262
263                                 if (parmIndex > 1) {
264                                         columns.Append (", ");
265                                         values.Append (", ");
266                                 }
267
268                                 int index = -1;
269                                 if (option) {
270                                         index = CreateParameter (_insertCommand, schemaRow);
271                                 } else {
272                                         index = CreateParameter (_insertCommand, parmIndex++, schemaRow);
273                                 }
274                                 DbParameter parameter = _insertCommand.Parameters [index];
275                                 parameter.SourceVersion = DataRowVersion.Current;
276
277                                 columns.Append (GetQuotedString (parameter.SourceColumn));
278                                 values.Append (parameter.ParameterName);
279                         }
280
281                         sql = String.Format ("{0} ({1}) VALUES ({2})", command, columns.ToString (), values.ToString ());
282                         _insertCommand.CommandText = sql;
283                         _dbCommand = _insertCommand;
284                         return _insertCommand;
285                 }
286
287                 private void CreateNewCommand (ref DbCommand command)
288                 {
289                         DbCommand sourceCommand = SourceCommand;
290                         if (command == null) {
291                                 command = sourceCommand.Connection.CreateCommand ();
292                                 command.CommandTimeout = sourceCommand.CommandTimeout;
293                                 command.Transaction = sourceCommand.Transaction;
294                         }
295                         command.CommandType = CommandType.Text;
296                         command.UpdatedRowSource = UpdateRowSource.None;
297                         command.Parameters.Clear ();
298                 }
299
300                 private DbCommand CreateUpdateCommand (bool option)
301                 {
302                         // If no table was found, then we can't do an update
303                         if (QuotedTableName == String.Empty)
304                                 return null;
305
306                         CreateNewCommand (ref _updateCommand);
307
308                         string command = String.Format ("UPDATE {0} SET ", QuotedTableName);
309                         StringBuilder columns = new StringBuilder ();
310                         StringBuilder whereClause = new StringBuilder ();
311                         int parmIndex = 1;
312                         bool keyFound = false;
313
314                         // First, create the X=Y list for UPDATE
315                         foreach (DataRow schemaRow in _dbSchemaTable.Rows) {
316                                 if (!IncludedInUpdate (schemaRow))
317                                         continue;
318                                 if (columns.Length > 0) 
319                                         columns.Append (", ");
320
321
322                                 int index = -1;
323                                 if (option) {
324                                         index = CreateParameter (_updateCommand, schemaRow);
325                                 } else {
326                                         index = CreateParameter (_updateCommand, parmIndex++, schemaRow);
327                                 }
328                                 DbParameter parameter = _updateCommand.Parameters [index];
329                                 parameter.SourceVersion = DataRowVersion.Current;
330
331                                 columns.Append (String.Format ("{0} = {1}", GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
332                         }
333
334                         // Now, create the WHERE clause.  This may be optimizable, but it would be ugly to incorporate
335                         // into the loop above.  "Premature optimization is the root of all evil." -- Knuth
336                         foreach (DataRow schemaRow in _dbSchemaTable.Rows) {
337                                 if ((bool) schemaRow ["IsExpression"] == true)
338                                         continue;
339
340                                 if (!IncludedInWhereClause (schemaRow)) 
341                                         continue;
342
343                                 if (whereClause.Length > 0) 
344                                         whereClause.Append (" AND ");
345
346                                 bool isKey = (bool) schemaRow ["IsKey"];
347                                 DbParameter parameter = null;
348
349                                 if (isKey)
350                                         keyFound = true;
351
352                                 //ms.net 1.1 generates the null check for columns even if AllowDBNull is false
353                                 //while ms.net 2.0 does not. Anyways, since both forms are logically equivalent
354                                 //following the 2.0 approach
355                                 bool allowNull = (bool) schemaRow ["AllowDBNull"];
356                                 int index;
357                                 if (!isKey && allowNull) {
358                                         parameter = _updateCommand.CreateParameter ();
359                                         if (option) {
360                                                 parameter.ParameterName = String.Format ("@{0}",
361                                                                                          schemaRow ["BaseColumnName"]);
362                                         } else {
363                                                 parameter.ParameterName = String.Format ("@p{0}", parmIndex++);
364                                         }
365                                         parameter.Value = 1;
366                                         whereClause.Append ("(");
367                                         whereClause.Append (String.Format (clause1, parameter.ParameterName,
368                                                                            GetQuotedString ((string) schemaRow ["BaseColumnName"])));
369                                         whereClause.Append (" OR ");
370                                 }
371
372                                 if (option) {
373                                         index = CreateParameter (_updateCommand, schemaRow);
374                                 } else {
375                                         index = CreateParameter (_updateCommand, parmIndex++, schemaRow);
376                                 }
377                                 parameter = _updateCommand.Parameters [index];
378                                 parameter.SourceVersion = DataRowVersion.Original;
379
380                                 whereClause.Append (String.Format (clause2, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
381
382                                 if (!isKey && allowNull)
383                                         whereClause.Append (")");
384                         }
385                         if (!keyFound)
386                                 throw new InvalidOperationException ("Dynamic SQL generation for the UpdateCommand is not supported against a SelectCommand that does not return any key column information.");
387
388                         // We're all done, so bring it on home
389                         string sql = String.Format ("{0}{1} WHERE ({2})", command, columns.ToString (), whereClause.ToString ());
390                         _updateCommand.CommandText = sql;
391                         _dbCommand = _updateCommand;
392                         return _updateCommand;
393                 }
394
395                 private int CreateParameter (DbCommand _dbCommand, int parmIndex, DataRow schemaRow)
396                 {
397                         DbParameter parameter = _dbCommand.CreateParameter ();
398                         parameter.ParameterName = String.Format ("@p{0}", parmIndex);
399                         parameter.SourceColumn = (string) schemaRow ["BaseColumnName"];
400                         parameter.Size = (int) schemaRow ["ColumnSize"];
401                         return _dbCommand.Parameters.Add (parameter);
402                 }
403
404                 private int CreateParameter (DbCommand _dbCommand, DataRow schemaRow)
405                 {
406                         DbParameter parameter = _dbCommand.CreateParameter ();
407                         parameter.ParameterName = String.Format ("@{0}",
408                                                                  schemaRow ["BaseColumnName"]);
409                         parameter.SourceColumn = (string) schemaRow ["BaseColumnName"];
410                         parameter.Size = (int) schemaRow ["ColumnSize"];
411                         return _dbCommand.Parameters.Add (parameter);
412                 }
413
414                 [DefaultValue (CatalogLocation.Start)]
415                 public virtual CatalogLocation CatalogLocation {
416                         get { return _catalogLocation; }
417                         set { _catalogLocation = value; }
418                 }
419
420                 [DefaultValue (".")]
421                 public virtual string CatalogSeparator {
422                         get { return _catalogSeperator; }
423                         set { if (value != null) _catalogSeperator = value; }
424                 }
425
426                 [DefaultValue (ConflictOption.CompareAllSearchableValues)]
427                 public virtual ConflictOption ConflictOption {
428                         get { return _conflictOption; }
429                         set { _conflictOption = value; }
430                 }
431
432                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
433                 [Browsable (false)]
434                 public DbDataAdapter DataAdapter {
435                         get { return _dbDataAdapter; }
436                         set {  if (value != null) _dbDataAdapter = value; }
437                 }
438
439                 [DefaultValue ("")]
440                 public virtual string QuotePrefix {
441                         get { return _quotePrefix; }
442                         set { if (value != null) _quotePrefix = value; }
443                 }
444
445                 [DefaultValue ("")]
446                 public virtual string QuoteSuffix {
447                         get { return _quoteSuffix; }
448                         set {  if (value != null) _quoteSuffix = value; }
449                 }
450
451                 [DefaultValue (".")]
452                 public virtual string SchemaSeparator {
453                         get { return _schemaSeperator; }
454                         set {  if (value != null) _schemaSeperator = value; }
455                 }
456
457                 [DefaultValue (false)]
458                 public bool SetAllValues {
459                         get { return _setAllValues; }
460                         set { _setAllValues = value; }
461                 }               
462
463                 private DbCommand SourceCommand {
464                         get {
465                                 if (_dbDataAdapter != null)
466                                         return _dbDataAdapter.SelectCommand;
467                                 return null;
468                         }
469                 }
470                 #endregion // Properties
471
472                 #region Methods
473
474                 protected abstract void ApplyParameterInfo (DbParameter parameter, 
475                                                             DataRow row, 
476                                                             StatementType statementType, 
477                                                             bool whereClause);
478
479                 protected override void Dispose (bool disposing)
480                 {
481                         if (!_disposed) {
482                                 if (disposing) {
483                                         if (_insertCommand != null)
484                                                 _insertCommand.Dispose ();
485                                         if (_deleteCommand != null)
486                                                 _deleteCommand.Dispose ();
487                                         if (_updateCommand != null)
488                                                 _updateCommand.Dispose ();
489                                         if (_dbSchemaTable != null)
490                                                 _dbSchemaTable.Dispose ();
491                                 }
492                                 _disposed = true;
493                         }
494                 }
495
496                 public DbCommand GetDeleteCommand ()
497                 {
498                         BuildCache (true);
499                         if (_deleteCommand == null)
500                                 return CreateDeleteCommand (false);
501                         return _deleteCommand;
502                 }
503
504                 public DbCommand GetDeleteCommand (bool option)
505                 {
506                         BuildCache (true);
507                         if (_deleteCommand == null)
508                                 return CreateDeleteCommand (option);
509                         return _deleteCommand;
510                 }
511
512                 public DbCommand GetInsertCommand ()
513                 {
514                         BuildCache (true);
515                         if (_insertCommand == null)
516                                 return CreateInsertCommand (false);
517                         return _insertCommand;
518                 }
519
520                 public DbCommand GetInsertCommand (bool option)
521                 {
522                         BuildCache (true);
523                         if (_insertCommand == null)
524                                 return CreateInsertCommand (option);
525                         return _insertCommand;
526                 }
527
528                 public DbCommand GetUpdateCommand ()
529                 {
530                         BuildCache (true);
531                         if (_updateCommand == null)
532                                 return CreateUpdateCommand (false);
533                         return _updateCommand;
534                 }
535
536                 public DbCommand GetUpdateCommand (bool option)
537                 {
538                         BuildCache (true);
539                         if (_updateCommand == null)
540                                 return CreateUpdateCommand (option);
541                         return _updateCommand;
542                 }
543
544                 protected virtual DbCommand InitializeCommand (DbCommand command)
545                 {
546                         if (_dbCommand == null) {
547                                 _dbCommand = SourceCommand;
548                         } else {
549                                 _dbCommand.CommandTimeout = 30;
550                                 _dbCommand.Transaction = null;
551                                 _dbCommand.CommandType = CommandType.Text;
552                                 _dbCommand.UpdatedRowSource = UpdateRowSource.None;
553                         }
554                         return _dbCommand;
555
556                 }
557
558                 public virtual string QuoteIdentifier (string unquotedIdentifier)
559                 {
560                         if (unquotedIdentifier == null) {
561                                 throw new ArgumentNullException("Unquoted identifier parameter cannot be null");
562                         }
563                         return String.Format ("{0}{1}{2}", this.QuotePrefix, unquotedIdentifier, this.QuoteSuffix);
564                 }
565
566                 public virtual string UnquoteIdentifier (string quotedIdentifier)
567                 {
568                         if (quotedIdentifier == null) {
569                                 throw new ArgumentNullException ("Quoted identifier parameter cannot be null");
570                         }
571                         string unquotedIdentifier = quotedIdentifier.Trim ();
572                         if (unquotedIdentifier.StartsWith (this.QuotePrefix)) {
573                                 unquotedIdentifier = unquotedIdentifier.Remove (0, 1);
574                         }
575                         if (unquotedIdentifier.EndsWith (this.QuoteSuffix)) {
576                                 unquotedIdentifier = unquotedIdentifier.Remove (unquotedIdentifier.Length - 1, 1);
577                         }
578                         return unquotedIdentifier;
579                 }
580
581                 public virtual void RefreshSchema ()
582                 {
583                         _tableName = String.Empty;
584                         _dbSchemaTable = null;
585                         CreateNewCommand (ref _deleteCommand);
586                         CreateNewCommand (ref _updateCommand);
587                         CreateNewCommand (ref _insertCommand);
588                 }
589
590                 protected void RowUpdatingHandler (RowUpdatingEventArgs args)
591                 {
592                         if (args.Command != null)
593                                 return;
594                         try {
595                                 switch (args.StatementType) {
596                                 case StatementType.Insert:
597                                         args.Command = GetInsertCommand ();
598                                         break;
599                                 case StatementType.Update:
600                                         args.Command = GetUpdateCommand ();
601                                         break;
602                                 case StatementType.Delete:
603                                         args.Command = GetDeleteCommand ();
604                                         break;
605                                 }
606                         } catch (Exception e) {
607                                 args.Errors = e;
608                                 args.Status = UpdateStatus.ErrorsOccurred;
609                         }
610                 }
611
612                 protected abstract string GetParameterName (int parameterOrdinal);
613                 protected abstract string GetParameterName (String parameterName);
614                 protected abstract string GetParameterPlaceholder (int parameterOrdinal);
615
616                 protected abstract void SetRowUpdatingHandler (DbDataAdapter adapter);
617
618                 protected virtual DataTable GetSchemaTable (DbCommand cmd)
619                 {
620                         using (DbDataReader rdr = cmd.ExecuteReader ())
621                                 return rdr.GetSchemaTable ();
622                 }
623
624                 #endregion // Methods
625         }
626 }
627
628 #endif