* roottypes.cs: Rename from tree.cs.
[mono.git] / mcs / class / FirebirdSql.Data.Firebird / FirebirdSql.Data.Firebird / FbDataAdapter.cs
1 /*
2  *      Firebird ADO.NET Data provider for .NET and Mono 
3  * 
4  *         The contents of this file are subject to the Initial 
5  *         Developer's Public License Version 1.0 (the "License"); 
6  *         you may not use this file except in compliance with the 
7  *         License. You may obtain a copy of the License at 
8  *         http://www.firebirdsql.org/index.php?op=doc&id=idpl
9  *
10  *         Software distributed under the License is distributed on 
11  *         an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either 
12  *         express or implied. See the License for the specific 
13  *         language governing rights and limitations under the License.
14  * 
15  *      Copyright (c) 2002, 2005 Carlos Guzman Alvarez
16  *      All Rights Reserved.
17  */
18
19 using System;
20 using System.Collections;
21 using System.ComponentModel;
22 using System.Data;
23 using System.Data.Common;
24 using System.Drawing;
25
26 namespace FirebirdSql.Data.Firebird
27 {
28         /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/overview/*'/>
29 #if     (NET)
30         [ToolboxItem(true)]
31         [ToolboxBitmap(typeof(FbDataAdapter), "Resources.FbDataAdapter.bmp")]
32         [DefaultEvent("RowUpdated")]
33         [DesignerAttribute(typeof(Design.FbDataAdapterDesigner), typeof(System.ComponentModel.Design.IDesigner))]
34 #endif
35         public sealed class FbDataAdapter : DbDataAdapter, IDbDataAdapter
36         {
37                 #region Static Fields
38
39                 private static readonly object EventRowUpdated = new object();
40                 private static readonly object EventRowUpdating = new object();
41
42                 #endregion
43
44                 #region Events
45
46                 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/event[@name="RowUpdated"]/*'/>
47                 public event FbRowUpdatedEventHandler RowUpdated
48                 {
49                         add { base.Events.AddHandler(EventRowUpdated, value); }
50                         remove { base.Events.RemoveHandler(EventRowUpdated, value); }
51                 }
52
53                 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/event[@name="RowUpdating"]/*'/>
54                 public event FbRowUpdatingEventHandler RowUpdating
55                 {
56                         add
57                         {
58                                 base.Events.AddHandler(EventRowUpdating, value);
59                         }
60
61                         remove
62                         {
63                                 base.Events.RemoveHandler(EventRowUpdating, value);
64                         }
65                 }
66
67                 #endregion
68
69                 #region Fields
70
71                 private FbCommand selectCommand;
72                 private FbCommand insertCommand;
73                 private FbCommand updateCommand;
74                 private FbCommand deleteCommand;
75
76                 private bool disposed;
77
78                 #endregion
79
80                 #region Properties
81
82                 IDbCommand IDbDataAdapter.SelectCommand
83                 {
84                         get { return this.selectCommand; }
85                         set { this.selectCommand = (FbCommand)value; }
86                 }
87
88                 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/property[@name="SelectCommand"]/*'/>
89 #if     (!NETCF)
90                 [Category("Fill"), DefaultValue(null)]
91 #endif
92                 public FbCommand SelectCommand
93                 {
94                         get { return this.selectCommand; }
95                         set { this.selectCommand = value; }
96                 }
97
98                 IDbCommand IDbDataAdapter.InsertCommand
99                 {
100                         get { return this.insertCommand; }
101                         set { this.insertCommand = (FbCommand)value; }
102                 }
103
104                 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/property[@name="InsertCommand"]/*'/>
105 #if     (!NETCF)
106                 [Category("Update"), DefaultValue(null)]
107 #endif
108                 public FbCommand InsertCommand
109                 {
110                         get { return this.insertCommand; }
111                         set { this.insertCommand = value; }
112                 }
113
114                 IDbCommand IDbDataAdapter.UpdateCommand
115                 {
116                         get { return this.updateCommand; }
117                         set { this.updateCommand = (FbCommand)value; }
118                 }
119
120                 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/property[@name="UpdateCommand"]/*'/>             
121 #if     (!NETCF)
122                 [Category("Update"), DefaultValue(null)]
123 #endif
124                 public FbCommand UpdateCommand
125                 {
126                         get { return this.updateCommand; }
127                         set { this.updateCommand = value; }
128                 }
129
130                 IDbCommand IDbDataAdapter.DeleteCommand
131                 {
132                         get { return this.deleteCommand; }
133                         set { this.deleteCommand = (FbCommand)value; }
134                 }
135
136                 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/property[@name="DeleteCommand"]/*'/>
137 #if     (!NETCF)
138                 [Category("Update"), DefaultValue(null)]
139 #endif
140                 public FbCommand DeleteCommand
141                 {
142                         get { return this.deleteCommand; }
143                         set { this.deleteCommand = value; }
144                 }
145
146                 #endregion
147
148                 #region Constructors
149
150                 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/constructor[@name="ctor"]/*'/>
151                 public FbDataAdapter() : base()
152                 {
153                 }
154
155                 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/constructor[@name="ctor(FbCommand)"]/*'/>
156                 public FbDataAdapter(FbCommand selectCommand) : base()
157                 {
158                         this.SelectCommand = selectCommand;
159                 }
160
161                 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/constructor[@name="ctor(System.String,FbConnection)"]/*'/>               
162                 public FbDataAdapter(string selectCommandText, FbConnection selectConnection)
163                         : base()
164                 {
165                         this.SelectCommand = new FbCommand(selectCommandText, selectConnection);
166                 }
167
168                 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/constructor[@name="ctor(System.String,System.String)"]/*'/>
169                 public FbDataAdapter(string selectCommandText, string selectConnectionString)
170                         : base()
171                 {
172                         FbConnection connection = new FbConnection(selectConnectionString);
173                         this.SelectCommand = new FbCommand(selectCommandText, connection);
174                 }
175
176                 #endregion
177
178                 #region IDisposable     Methods
179
180                 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/method[@name="Dispose(System.Boolean)"]/*'/>
181                 protected override void Dispose(bool disposing)
182                 {
183                         lock (this)
184                         {
185                                 if (!this.disposed)
186                                 {
187                                         try
188                                         {
189                                                 // Release any managed resources
190                                                 if (disposing)
191                                                 {
192                                                         if (this.SelectCommand != null)
193                                                         {
194                                                                 this.SelectCommand.Dispose();
195                                                         }
196                                                         if (this.InsertCommand != null)
197                                                         {
198                                                                 this.InsertCommand.Dispose();
199                                                         }
200                                                         if (this.UpdateCommand != null)
201                                                         {
202                                                                 this.UpdateCommand.Dispose();
203                                                         }
204                                                 }
205
206                                                 // release any unmanaged resources
207
208                                                 this.disposed = true;
209                                         }
210                                         finally
211                                         {
212                                                 base.Dispose(disposing);
213                                         }
214                                 }
215                         }
216                 }
217
218                 #endregion
219
220                 #region Protected Methods
221
222                 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/method[@name="CreateRowUpdatingEvent(System.Data.DataRow,System.Data.IDbCommand,System.Data.StatementType,System.Data.Common.DataTableMapping)"]/*'/>
223                 protected override RowUpdatingEventArgs CreateRowUpdatingEvent(
224                         DataRow dataRow, IDbCommand command, StatementType statementType, DataTableMapping tableMapping)
225                 {
226                         return new FbRowUpdatingEventArgs(dataRow, command, statementType, tableMapping);
227                 }
228
229                 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/method[@name="CreateRowUpdatedEvent(System.Data.DataRow,System.Data.IDbCommand,System.Data.StatementType,System.Data.Common.DataTableMapping)"]/*'/>
230                 protected override RowUpdatedEventArgs CreateRowUpdatedEvent(
231                         DataRow dataRow, IDbCommand command, StatementType statementType, DataTableMapping tableMapping)
232                 {
233                         return new FbRowUpdatedEventArgs(dataRow, command, statementType, tableMapping);
234                 }
235
236                 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/method[@name="OnRowUpdating(System.Data.Common.RowUpdatingEventArgs)"]/*'/>
237                 protected override void OnRowUpdating(RowUpdatingEventArgs value)
238                 {
239                         FbRowUpdatingEventHandler handler = null;
240
241                         handler = (FbRowUpdatingEventHandler)base.Events[EventRowUpdating];
242
243                         if ((null != handler) &&
244                                 (value is FbRowUpdatingEventArgs) &&
245                                 (value != null))
246                         {
247                                 handler(this, (FbRowUpdatingEventArgs)value);
248                         }
249                 }
250
251                 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/method[@name="OnRowUpdated(System.Data.Common.RowUpdatedEventArgs)"]/*'/>
252                 protected override void OnRowUpdated(RowUpdatedEventArgs value)
253                 {
254                         FbRowUpdatedEventHandler handler = null;
255
256                         handler = (FbRowUpdatedEventHandler)base.Events[EventRowUpdated];
257
258                         if ((handler != null) &&
259                                 (value is FbRowUpdatedEventArgs) &&
260                                 (value != null))
261                         {
262                                 handler(this, (FbRowUpdatedEventArgs)value);
263                         }
264                 }
265
266                 #endregion
267
268                 #region Update DataRow Collection
269
270                 /// <summary>
271                 /// Review .NET Framework documentation.
272                 /// </summary>
273                 protected override int Update(DataRow[] dataRows, DataTableMapping tableMapping)
274                 {
275                         int                                             updated                 = 0;
276                         IDbCommand                              command                 = null;
277                         StatementType                   statementType   = StatementType.Insert;
278                         ArrayList                               connections             = new ArrayList();
279                         RowUpdatingEventArgs    updatingArgs    = null;
280                         Exception                               updateException = null;
281
282                         foreach (DataRow row in dataRows)
283                         {
284                                 if (row.RowState == DataRowState.Detached ||
285                                         row.RowState == DataRowState.Unchanged)
286                                 {
287                                         continue;
288                                 }
289
290                                 switch (row.RowState)
291                                 {
292                                         case DataRowState.Added:
293                                                 command = this.insertCommand;
294                                                 statementType = StatementType.Insert;
295                                                 break;
296
297                                         case DataRowState.Modified:
298                                                 command = this.updateCommand;
299                                                 statementType = StatementType.Update;
300                                                 break;
301
302                                         case DataRowState.Deleted:
303                                                 command = this.deleteCommand;
304                                                 statementType = StatementType.Delete;
305                                                 break;
306                                 }
307
308                                 /* The order of execution can be reviewed in the .NET 1.1 documentation
309                                         *
310                                         * 1. The values in      the     DataRow are     moved to the parameter values. 
311                                         * 2. The OnRowUpdating  event is raised. 
312                                         * 3. The command executes.      
313                                         * 4. If the command is  set     to FirstReturnedRecord, then the first returned result is placed in     the     DataRow. 
314                                         * 5. If there are output parameters, they are placed in the DataRow. 
315                                         * 6. The OnRowUpdated event is  raised. 
316                                         * 7 AcceptChanges is called. 
317                                         */
318
319                                 try
320                                 {
321                                         /* 1. Update Parameter values (It's     very similar to what we 
322                                          * are doing in the     FbCommandBuilder class).
323                                          *
324                                          * Only input parameters should be updated.
325                                          */
326                                         if (command != null && command.Parameters.Count > 0)
327                                         {
328                                                 this.UpdateParameterValues(command, statementType, row, tableMapping);
329                                         }
330
331                                         // 2. Raise     RowUpdating     event
332                                         updatingArgs = this.CreateRowUpdatingEvent(row, command, statementType, tableMapping);
333                                         this.OnRowUpdating(updatingArgs);
334
335                                         if (updatingArgs.Status == UpdateStatus.SkipAllRemainingRows)
336                                         {
337                                                 break;
338                                         }
339                                         else if (updatingArgs.Status == UpdateStatus.ErrorsOccurred)
340                                         {
341                                                 if (updatingArgs.Errors == null)
342                                                 {
343                                                         throw new InvalidOperationException("RowUpdatingEvent: Errors occurred; no additional is information available.");
344                                                 }
345                                                 throw updatingArgs.Errors;
346                                         }
347                                         else if (updatingArgs.Status == UpdateStatus.SkipCurrentRow)
348                                         {
349                                         }
350                                         else if (updatingArgs.Status == UpdateStatus.Continue)
351                                         {
352                                                 if (command != updatingArgs.Command)
353                                                 {
354                                                         command = updatingArgs.Command;
355                                                 }
356                                                 if (command == null)
357                                                 {
358                                                         /* Samples of exceptions thrown by DbDataAdapter class
359                                                                 *
360                                                                 *       Update requires a valid InsertCommand when passed DataRow collection with new rows
361                                                                 *       Update requires a valid UpdateCommand when passed DataRow collection with modified rows.
362                                                                 *       Update requires a valid DeleteCommand when passed DataRow collection with deleted rows.
363                                                                 */
364                                                         string message = this.CreateExceptionMessage(statementType);
365                                                         throw new InvalidOperationException(message);
366                                                 }
367
368                                                 /* Validate that the command has a connection */
369                                                 if (command.Connection == null)
370                                                 {
371                                                         throw new InvalidOperationException("Update requires a command with a valid connection.");
372                                                 }
373
374                                                 // 3. Execute the command
375                                                 if (command.Connection.State == ConnectionState.Closed)
376                                                 {
377                                                         command.Connection.Open();
378                                                         // Track command connection
379                                                         connections.Add(command.Connection);
380                                                 }
381
382                                                 int rowsAffected = command.ExecuteNonQuery();
383                                                 if (rowsAffected == 0)
384                                                 {
385                                                         throw new DBConcurrencyException("An attempt to execute an INSERT, UPDATE, or DELETE statement resulted in zero records affected.");
386                                                 }
387
388                                                 updated++;
389
390                                                 /* 4. If the command is set     to FirstReturnedRecord, then the 
391                                                         * first returned result is      placed in the DataRow. 
392                                                         * 
393                                                         * We have nothing to do in      this case as there are no 
394                                                         * support for batch commands.
395                                                         */
396
397                                                 /* 5. Check     if we have output parameters and they should 
398                                                         * be updated.
399                                                         *
400                                                         * Only  output paraneters should be     updated
401                                                         */
402                                                 if (command.UpdatedRowSource == UpdateRowSource.OutputParameters ||
403                                                         command.UpdatedRowSource == UpdateRowSource.Both)
404                                                 {
405                                                         // Process output parameters
406                                                         foreach (IDataParameter parameter in command.Parameters)
407                                                         {
408                                                                 if ((parameter.Direction == ParameterDirection.Output ||
409                                                                         parameter.Direction == ParameterDirection.ReturnValue ||
410                                                                         parameter.Direction == ParameterDirection.InputOutput) &&
411                                                                         parameter.SourceColumn != null &&
412                                                                         parameter.SourceColumn.Length > 0)
413                                                                 {
414                                                                         DataColumn column = null;
415
416                                                                         DataColumnMapping columnMapping = tableMapping.GetColumnMappingBySchemaAction(
417                                                                                 parameter.SourceColumn,
418                                                                                 this.MissingMappingAction);
419
420                                                                         if (columnMapping != null)
421                                                                         {
422                                                                                 column = columnMapping.GetDataColumnBySchemaAction(
423                                                                                         row.Table,
424                                                                                         null,
425                                                                                         this.MissingSchemaAction);
426
427                                                                                 if (column != null)
428                                                                                 {
429                                                                                         row[column] = parameter.Value;
430                                                                                 }
431                                                                         }
432                                                                 }
433                                                         }
434                                                 }
435                                         }
436                                 }
437                                 catch (Exception ex)
438                                 {
439                                         row.RowError    = ex.Message;
440                                         updateException = ex;
441                                 }
442
443                                 if (updatingArgs.Status == UpdateStatus.Continue)
444                                 {
445                                         // 6. Raise     RowUpdated event
446                                         RowUpdatedEventArgs     updatedArgs = this.CreateRowUpdatedEvent(row, command, statementType, tableMapping);
447                                         this.OnRowUpdated(updatedArgs);
448
449                                         if (updatedArgs.Status == UpdateStatus.SkipAllRemainingRows)
450                                         {
451                                                 break;
452                                         }
453                                         else if (updatedArgs.Status == UpdateStatus.ErrorsOccurred)
454                                         {
455                                                 if (updatingArgs.Errors == null)
456                                                 {
457                                                         throw new InvalidOperationException("RowUpdatedEvent: Errors occurred; no additional information available.");
458                                                 }
459                                                 throw updatedArgs.Errors;
460                                         }
461                                         else if (updatedArgs.Status == UpdateStatus.SkipCurrentRow)
462                                         {
463                                         }
464                                         else if (updatingArgs.Status == UpdateStatus.Continue)
465                                         {
466                                                 // If the update result is an exception throw it
467                                                 if (!this.ContinueUpdateOnError && updateException != null)
468                                                 {
469                                                         this.CloseConnections(connections);
470                                                         throw updateException;
471                                                 }
472
473                                                 // 7. Call AcceptChanges
474                                                 row.AcceptChanges();
475                                         }
476                                 }
477                                 else
478                                 {
479                                         // If the update result is an exception throw it
480                                         if (!this.ContinueUpdateOnError && updateException != null)
481                                         {
482                                                 this.CloseConnections(connections);
483                                                 throw updateException;
484                                         }
485                                 }
486
487                                 updateException = null;
488                         }
489
490                         this.CloseConnections(connections);
491
492                         return updated;
493                 }
494
495                 #endregion
496
497                 #region Private Methods
498
499                 private string CreateExceptionMessage(StatementType statementType)
500                 {
501                         System.Text.StringBuilder sb = new System.Text.StringBuilder();
502
503                         sb.Append("Update requires a valid ");
504                         sb.Append(statementType.ToString());
505                         sb.Append("Command when passed DataRow collection with ");
506
507                         switch (statementType)
508                         {
509                                 case StatementType.Insert:
510                                         sb.Append("new");
511                                         break;
512
513                                 case StatementType.Update:
514                                         sb.Append("modified");
515                                         break;
516
517                                 case StatementType.Delete:
518                                         sb.Append("deleted");
519                                         break;
520                         }
521
522                         sb.Append(" rows.");
523
524                         return sb.ToString();
525                 }
526
527                 private void UpdateParameterValues(
528                         IDbCommand command,
529                         StatementType statementType,
530                         DataRow row,
531                         DataTableMapping tableMapping)
532                 {
533                         foreach (IDataParameter parameter in command.Parameters)
534                         {
535                                 // Process only input parameters
536                                 if (parameter.Direction != ParameterDirection.Input &&
537                                         parameter.Direction != ParameterDirection.InputOutput)
538                                 {
539                                         continue;
540                                 }
541
542                                 DataColumn column = null;
543
544                                 /* Get the DataColumnMapping that matches the given
545                                  * column name
546                                  */
547                                 DataColumnMapping columnMapping = tableMapping.GetColumnMappingBySchemaAction(
548                                         parameter.SourceColumn,
549                                         this.MissingMappingAction);
550
551                                 if (columnMapping != null)
552                                 {
553                                         column = columnMapping.GetDataColumnBySchemaAction(
554                                                 row.Table,
555                                                 null,
556                                                 this.MissingSchemaAction);
557
558                                         if (column != null)
559                                         {
560                                                 DataRowVersion dataRowVersion = DataRowVersion.Default;
561
562                                                 if (statementType == StatementType.Insert)
563                                                 {
564                                                         dataRowVersion = DataRowVersion.Current;
565                                                 }
566                                                 else if (statementType == StatementType.Update)
567                                                 {
568                                                         dataRowVersion = parameter.SourceVersion;
569                                                 }
570                                                 else if (statementType == StatementType.Delete)
571                                                 {
572                                                         dataRowVersion = DataRowVersion.Original;
573                                                 }
574
575                                                 parameter.Value = row[column, dataRowVersion];
576                                         }
577                                 }
578                         }
579                 }
580
581                 private void CloseConnections(ArrayList connections)
582                 {
583                         foreach (IDbConnection c in connections)
584                         {
585                                 c.Close();
586                         }
587                         connections.Clear();
588                 }
589
590                 #endregion
591         }
592 }