2002-10-01 Luis Fernandez <luifer@onetel.net.uk>
[mono.git] / mcs / class / System.Data / System.Data / DataRow.cs
1 //
2 // System.Data.DataRow.cs
3 //
4 // Author:
5 //   Rodrigo Moya <rodrigo@ximian.com>
6 //   Daniel Morgan <danmorg@sc.rr.com>
7 //   Tim Coleman <tim@timcoleman.com>
8 //
9 // (C) Ximian, Inc 2002
10 // (C) Daniel Morgan 2002
11 // Copyright (C) 2002 Tim Coleman
12 //
13
14 using System;
15 using System.Collections;
16
17 namespace System.Data
18 {
19         /// <summary>
20         /// Represents a row of data in a DataTable.
21         /// </summary>
22         public class DataRow
23         {
24                 #region Fields
25
26                 private DataTable _table;
27
28                 private object[] original;
29                 private object[] proposed;
30                 private object[] current;
31
32                 private string[] columnErrors;
33                 private string rowError;
34                 private DataRowState rowState;
35
36                 #endregion
37
38                 #region Constructors
39
40                 /// <summary>
41                 /// This member supports the .NET Framework infrastructure and is not intended to be 
42                 /// used directly from your code.
43                 /// </summary>
44                 protected internal DataRow (DataRowBuilder builder)
45                 {
46                         _table = builder.Table;
47
48                         original = null; 
49                         proposed = null;
50                         current = new object[_table.Columns.Count];
51
52                         columnErrors = new string[_table.Columns.Count];
53                         rowError = String.Empty;
54
55                         //rowState = DataRowState.Unchanged;
56
57                         //on first creating a DataRow it is always detached.
58                         rowState = DataRowState.Detached;
59                 }
60
61                 #endregion
62
63                 #region Properties
64
65                 /// <summary>
66                 /// Gets a value indicating whether there are errors in a row.
67                 /// </summary>
68                 public bool HasErrors {
69                         [MonoTODO]
70                         get {
71                                 throw new NotImplementedException ();
72                         }
73                 }
74
75                 /// <summary>
76                 /// Gets or sets the data stored in the column specified by name.
77                 /// </summary>
78                 public object this[string columnName] {
79                         [MonoTODO] //FIXME: will return different values depending on DataRowState
80                         get { return this[columnName, DataRowVersion.Current]; }
81                         [MonoTODO]
82                         set {
83                                 DataColumn column = _table.Columns[columnName];
84                                 if (column == null) 
85                                         throw new IndexOutOfRangeException ();
86                                 this[column] = value;
87                         }
88                 }
89
90                 /// <summary>
91                 /// Gets or sets the data stored in specified DataColumn
92                 /// </summary>
93                 public object this[DataColumn column] {
94                         [MonoTODO] //FIXME: will return different values depending on DataRowState
95                         get { return this[column, DataRowVersion.Current]; } 
96                                                                 
97                         [MonoTODO]
98                         set {
99                                 bool objIsDBNull = value.Equals(DBNull.Value);
100                                 if (column == null)
101                                         throw new ArgumentNullException ();
102                                 int columnIndex = _table.Columns.IndexOf (column);
103                                 if (columnIndex == -1)
104                                         throw new ArgumentException ();
105                                 if(column.DataType != value.GetType ()) {
106                                         if(objIsDBNull == true && column.AllowDBNull == false)
107                                                 throw new InvalidCastException ();
108                                         else if(objIsDBNull == false)
109                                                 throw new InvalidCastException ();
110                                 }
111                                 if (rowState == DataRowState.Deleted)
112                                         throw new DeletedRowInaccessibleException ();
113
114                                 //MS Implementation doesn't seem to create the proposed or original
115                                 //set of values when a datarow has just been created or added to the
116                                 //DataTable and AcceptChanges() has not been called yet.
117                                 if(rowState == DataRowState.Detached || rowState == DataRowState.Added)
118                                 {
119                                         if(objIsDBNull)
120                                                 current[columnIndex] = DBNull.Value;
121                                         else
122                                                 current[columnIndex] = value;
123
124                                 }
125                                 else
126                                 {
127                                 BeginEdit ();  // implicitly called
128                                 if(objIsDBNull)
129                                         proposed[columnIndex] = DBNull.Value;
130                                 else
131                                         proposed[columnIndex] = value;
132                                 }
133
134                                 //Don't know if this is the rigth thing to do,
135                                 //but it fixes my test. I believe the MS docs only say this
136                                 //method is implicitly called when calling AcceptChanges()
137
138                                 //EndEdit (); // is this the right thing to do?
139
140                         }
141                 }
142
143                 /// <summary>
144                 /// Gets or sets the data stored in column specified by index.
145                 /// </summary>
146                 public object this[int columnIndex] {
147                         [MonoTODO] //FIXME: not always supposed to return current
148                         get { return this[columnIndex, DataRowVersion.Current]; }
149                         [MonoTODO]
150                         set {
151                                 DataColumn column = _table.Columns[columnIndex]; //FIXME: will throw
152                                 if (column == null)  
153                                         throw new IndexOutOfRangeException ();
154                                 this[column] = value;
155                         }
156                 }
157
158                 /// <summary>
159                 /// Gets the specified version of data stored in the named column.
160                 /// </summary>
161                 public object this[string columnName, DataRowVersion version] {
162                         [MonoTODO]
163                         get {
164                                 DataColumn column = _table.Columns[columnName]; //FIXME: will throw
165                                 if (column == null) 
166                                         throw new IndexOutOfRangeException ();
167                                 return this[column, version];
168                         }
169                 }
170
171                 /// <summary>
172                 /// Gets the specified version of data stored in the specified DataColumn.
173                 /// </summary>
174                 public object this[DataColumn column, DataRowVersion version] {
175                         get {
176                                 if (column == null)
177                                         throw new ArgumentNullException ();     
178
179                                 int columnIndex = _table.Columns.IndexOf (column);
180
181                                 if (columnIndex == -1)
182                                         throw new ArgumentException ();
183
184                                 if (version == DataRowVersion.Default)
185                                         return column.DefaultValue;
186
187                                 if (!HasVersion (version))
188                                         throw new VersionNotFoundException ();
189
190                                 switch (version)
191                                 {
192                                         case DataRowVersion.Proposed:
193                                                 return proposed[columnIndex];
194                                         case DataRowVersion.Current:
195                                                 return current[columnIndex];
196                                         case DataRowVersion.Original:
197                                                 return original[columnIndex];
198                                         default:
199                                                 throw new ArgumentException ();
200                                 }
201                         }
202                 }
203
204                 /// <summary>
205                 /// Gets the data stored in the column, specified by index and version of the data to
206                 /// retrieve.
207                 /// </summary>
208                 public object this[int columnIndex, DataRowVersion version] {
209                         [MonoTODO]
210                         get {
211                                 DataColumn column = _table.Columns[columnIndex]; //FIXME: throws
212                                 if (column == null) 
213                                         throw new IndexOutOfRangeException ();
214                                 return this[column, version];
215                         }
216                 }
217
218                 /// <summary>
219                 /// Gets or sets all of the values for this row through an array.
220                 /// </summary>
221                 [MonoTODO]
222                 public object[] ItemArray {
223                         get { return current; }
224                         set {
225                                 if (value.Length > _table.Columns.Count)
226                                         throw new ArgumentException ();
227                                 if (rowState == DataRowState.Deleted)
228                                         throw new DeletedRowInaccessibleException ();
229
230                                 for (int i = 0; i < value.Length; i += 1)
231                                 {
232                                         if (_table.Columns[i].ReadOnly && value[i] != this[i])
233                                                 throw new ReadOnlyException ();
234
235                                         if (value[i] == null)
236                                         {
237                                                 if (!_table.Columns[i].AllowDBNull)
238                                                         throw new NoNullAllowedException ();
239                                                 continue;
240                                         }
241                                                 
242                                         //FIXME: int strings can be converted to ints
243                                         if (_table.Columns[i].DataType != value[i].GetType())
244                                                 throw new InvalidCastException ();
245                                 }
246
247                                 //FIXME: BeginEdit() not correct 
248                                 BeginEdit ();  // implicitly called
249                                 //FIXME: this isn't correct.  a shorter array can set the first few values
250                                 //and not touch the rest.  So not all the values will get replaced
251                                 proposed = value;
252                         }
253                 }
254
255                 /// <summary>
256                 /// Gets or sets the custom error description for a row.
257                 /// </summary>
258                 public string RowError {
259                         get { return rowError; }
260                         set { rowError = value; }
261                 }
262
263                 /// <summary>
264                 /// Gets the current state of the row in regards to its relationship to the
265                 /// DataRowCollection.
266                 /// </summary>
267                 public DataRowState RowState {
268                         get { return rowState; }
269                 }
270
271                 //FIXME?: Couldn't find a way to set the RowState when adding the DataRow
272                 //to a Datatable so I added this method. Delete if there is a better way.
273                 internal DataRowState RowStateInternal
274                 {
275                         set { rowState = value;}
276                 }
277
278                 /// <summary>
279                 /// Gets the DataTable for which this row has a schema.
280                 /// </summary>
281                 public DataTable Table {
282                         get { return _table; }
283                 }
284
285                 #endregion
286
287                 #region Methods
288
289                 /// <summary>
290                 /// Commits all the changes made to this row since the last time AcceptChanges was
291                 /// called.
292                 /// </summary>
293                 [MonoTODO]
294                 public void AcceptChanges () 
295                 {
296                         
297                         if(rowState == DataRowState.Added)
298                         {
299                                 //Instantiate original and proposed values so that we can call
300                                 //EndEdit()
301                                 this.BeginEdit();
302                         }
303
304                         this.EndEdit ();
305
306                         switch (rowState)
307                         {
308                                 case DataRowState.Added:
309                                         rowState = DataRowState.Unchanged;
310                                         break;
311                                 case DataRowState.Modified:
312                                         rowState = DataRowState.Unchanged;
313                                         break;
314                                 case DataRowState.Deleted:
315                                         _table.Rows.Remove (this); //FIXME: this should occur in end edit
316                                         break;
317                         }
318
319                         //MS implementation assigns the Proposed values
320                         //to both current and original and keeps original after calling AcceptChanges
321                         //Copy proposed to original in this.EndEdit()
322                         //original = null;
323                 }
324
325                 /// <summary>
326                 /// Begins an edit operation on a DataRow object.
327                 /// </summary>
328                 [MonoTODO]
329                 public void BeginEdit() 
330                 {
331                         if (rowState == DataRowState.Deleted)
332                                 throw new DeletedRowInaccessibleException ();
333
334                         if (!HasVersion (DataRowVersion.Proposed))
335                         {
336                                 proposed = new object[_table.Columns.Count];
337                                 Array.Copy (current, proposed, _table.Columns.Count);
338                         }
339                         //TODO: Suspend validation
340
341                         //FIXME: this doesn't happen on begin edit
342                         if (!HasVersion (DataRowVersion.Original))
343                         {
344                                 original = new object[_table.Columns.Count];
345                                 Array.Copy (current, original, _table.Columns.Count);
346                         }
347                 }
348
349                 /// <summary>
350                 /// Cancels the current edit on the row.
351                 /// </summary>
352                 [MonoTODO]
353                 public void CancelEdit () 
354                 {
355                         //FIXME: original doesn't get erased on CancelEdit
356                         //TODO: Events
357                         if (HasVersion (DataRowVersion.Proposed))
358                         {
359                                 original = null;
360                                 proposed = null;
361                                 rowState = DataRowState.Unchanged;
362                         }
363                 }
364
365                 /// <summary>
366                 /// Clears the errors for the row, including the RowError and errors set with
367                 /// SetColumnError.
368                 /// </summary>
369                 public void ClearErrors () 
370                 {
371                         rowError = String.Empty;
372                         columnErrors = new String[_table.Columns.Count];
373                 }
374
375                 /// <summary>
376                 /// Deletes the DataRow.
377                 /// </summary>
378                 [MonoTODO]
379                 public void Delete () 
380                 {
381                         if (rowState == DataRowState.Deleted)
382                                 throw new DeletedRowInaccessibleException ();
383
384                         //TODO: Events, Constraints
385                         rowState = DataRowState.Deleted;
386                 }
387
388                 /// <summary>
389                 /// Ends the edit occurring on the row.
390                 /// </summary>
391                 [MonoTODO]
392                 public void EndEdit () 
393                 {
394                         if (HasVersion (DataRowVersion.Proposed))
395                         {
396                                 rowState = DataRowState.Modified;
397                                 //TODO: Validate Constraints, Events
398                                 Array.Copy (proposed, current, _table.Columns.Count);
399                                 
400                                 //FIXME: MS implementation assigns the proposed values to
401                                 //the original values. Should this be done here or on the
402                                 //AcceptChanges() method?
403                                 Array.Copy (proposed, original, _table.Columns.Count);
404
405                                 proposed = null;
406                         }
407                 }
408
409                 /// <summary>
410                 /// Gets the child rows of this DataRow using the specified DataRelation.
411                 /// </summary>
412                 [MonoTODO]
413                 public DataRow[] GetChildRows (DataRelation relation) 
414                 {
415                         throw new NotImplementedException ();
416                 }
417
418                 /// <summary>
419                 /// Gets the child rows of a DataRow using the specified RelationName of a
420                 /// DataRelation.
421                 /// </summary>
422                 [MonoTODO]
423                 public DataRow[] GetChildRows (string relationName) 
424                 {
425                         throw new NotImplementedException ();
426                 }
427
428                 /// <summary>
429                 /// Gets the child rows of a DataRow using the specified DataRelation, and
430                 /// DataRowVersion.
431                 /// </summary>
432                 [MonoTODO]
433                 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version) 
434                 {
435                         throw new NotImplementedException ();
436                 }
437
438                 /// <summary>
439                 /// Gets the child rows of a DataRow using the specified RelationName of a
440                 /// DataRelation, and DataRowVersion.
441                 /// </summary>
442                 [MonoTODO]
443                 public DataRow[] GetChildRows (string relationName, DataRowVersion version) 
444                 {
445                         throw new NotImplementedException ();
446                 }
447
448                 /// <summary>
449                 /// Gets the error description of the specified DataColumn.
450                 /// </summary>
451                 public string GetColumnError (DataColumn column) 
452                 {
453                         return GetColumnError (_table.Columns.IndexOf(column));
454                 }
455
456                 /// <summary>
457                 /// Gets the error description for the column specified by index.
458                 /// </summary>
459                 public string GetColumnError (int columnIndex) 
460                 {
461                         if (columnIndex < 0 || columnIndex >= columnErrors.Length)
462                                 throw new IndexOutOfRangeException ();
463
464                         return columnErrors[columnIndex];
465                 }
466
467                 /// <summary>
468                 /// Gets the error description for the column, specified by name.
469                 /// </summary>
470                 public string GetColumnError (string columnName) 
471                 {
472                         return GetColumnError (_table.Columns.IndexOf(columnName));
473                 }
474
475                 /// <summary>
476                 /// Gets an array of columns that have errors.
477                 /// </summary>
478                 public DataColumn[] GetColumnsInError () 
479                 {
480                         ArrayList dataColumns = new ArrayList ();
481
482                         for (int i = 0; i < columnErrors.Length; i += 1)
483                         {
484                                 if (columnErrors[i] != String.Empty)
485                                         dataColumns.Add (_table.Columns[i]);
486                         }
487
488                         return (DataColumn[])(dataColumns.ToArray ());
489                 }
490
491                 /// <summary>
492                 /// Gets the parent row of a DataRow using the specified DataRelation.
493                 /// </summary>
494                 [MonoTODO]
495                 public DataRow GetParentRow (DataRelation relation) 
496                 {
497                         throw new NotImplementedException ();
498                 }
499
500                 /// <summary>
501                 /// Gets the parent row of a DataRow using the specified RelationName of a
502                 /// DataRelation.
503                 /// </summary>
504                 [MonoTODO]
505                 public DataRow GetParentRow (string relationName) 
506                 {
507                         throw new NotImplementedException ();
508                 }
509
510                 /// <summary>
511                 /// Gets the parent row of a DataRow using the specified DataRelation, and
512                 /// DataRowVersion.
513                 /// </summary>
514                 [MonoTODO]
515                 public DataRow GetParentRow (DataRelation relation, DataRowVersion version) 
516                 {
517                         throw new NotImplementedException ();
518                 }
519
520                 /// <summary>
521                 /// Gets the parent row of a DataRow using the specified RelationName of a 
522                 /// DataRelation, and DataRowVersion.
523                 /// </summary>
524                 [MonoTODO]
525                 public DataRow GetParentRow (string relationName, DataRowVersion version) 
526                 {
527                         throw new NotImplementedException ();
528                 }
529
530                 /// <summary>
531                 /// Gets the parent rows of a DataRow using the specified DataRelation.
532                 /// </summary>
533                 [MonoTODO]
534                 public DataRow[] GetParentRows (DataRelation relation) 
535                 {
536                         throw new NotImplementedException ();
537                 }
538
539                 /// <summary>
540                 /// Gets the parent rows of a DataRow using the specified RelationName of a 
541                 /// DataRelation.
542                 /// </summary>
543                 [MonoTODO]
544                 public DataRow[] GetParentRows (string relationName) 
545                 {
546                         throw new NotImplementedException ();
547                 }
548
549                 /// <summary>
550                 /// Gets the parent rows of a DataRow using the specified DataRelation, and
551                 /// DataRowVersion.
552                 /// </summary>
553                 [MonoTODO]
554                 public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version) 
555                 {
556                         throw new NotImplementedException ();
557                 }
558
559                 /// <summary>
560                 /// Gets the parent rows of a DataRow using the specified RelationName of a 
561                 /// DataRelation, and DataRowVersion.
562                 /// </summary>
563                 [MonoTODO]
564                 public DataRow[] GetParentRows (string relationName, DataRowVersion version) 
565                 {
566                         throw new NotImplementedException ();
567                 }
568
569                 /// <summary>
570                 /// Gets a value indicating whether a specified version exists.
571                 /// </summary>
572                 public bool HasVersion (DataRowVersion version) 
573                 {
574                         switch (version)
575                         {
576                                 case DataRowVersion.Default:
577                                         return true;
578                                 case DataRowVersion.Proposed:
579                                         return (proposed != null);
580                                 case DataRowVersion.Current:
581                                         return (current != null);
582                                 case DataRowVersion.Original:
583                                         return (original != null);
584                         }
585                         return false;
586                 }
587
588                 /// <summary>
589                 /// Gets a value indicating whether the specified DataColumn contains a null value.
590                 /// </summary>
591                 public bool IsNull (DataColumn column) 
592                 {
593                         return (this[column] == null);
594                 }
595
596                 /// <summary>
597                 /// Gets a value indicating whether the column at the specified index contains a null
598                 /// value.
599                 /// </summary>
600                 public bool IsNull (int columnIndex) 
601                 {
602                         return (this[columnIndex] == null);
603                 }
604
605                 /// <summary>
606                 /// Gets a value indicating whether the named column contains a null value.
607                 /// </summary>
608                 public bool IsNull (string columnName) 
609                 {
610                         return (this[columnName] == null);
611                 }
612
613                 /// <summary>
614                 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
615                 /// contains a null value.
616                 /// </summary>
617                 public bool IsNull (DataColumn column, DataRowVersion version) 
618                 {
619                         return (this[column, version] == null);
620                 }
621
622                 /// <summary>
623                 /// Rejects all changes made to the row since AcceptChanges was last called.
624                 /// </summary>
625                 public void RejectChanges () 
626                 {
627                         // If original is null, then nothing has happened since AcceptChanges
628                         // was last called.  We have no "original" to go back to.
629                         if (original != null)
630                         {
631                                 Array.Copy (original, current, _table.Columns.Count);
632                                 CancelEdit ();
633                                 switch (rowState)
634                                 {
635                                         case DataRowState.Added:
636                                                 _table.Rows.Remove (this);
637                                                 break;
638                                         case DataRowState.Modified:
639                                                 rowState = DataRowState.Unchanged;
640                                                 break;
641                                         case DataRowState.Deleted:
642                                                 rowState = DataRowState.Unchanged;
643                                                 break;
644                                 }
645                         }
646                 }
647
648                 /// <summary>
649                 /// Sets the error description for a column specified as a DataColumn.
650                 /// </summary>
651                 public void SetColumnError (DataColumn column, string error) 
652                 {
653                         SetColumnError (_table.Columns.IndexOf (column), error);
654                 }
655
656                 /// <summary>
657                 /// Sets the error description for a column specified by index.
658                 /// </summary>
659                 public void SetColumnError (int columnIndex, string error) 
660                 {
661                         if (columnIndex < 0 || columnIndex >= columnErrors.Length)
662                                 throw new IndexOutOfRangeException ();
663                         columnErrors[columnIndex] = error;
664                 }
665
666                 /// <summary>
667                 /// Sets the error description for a column specified by name.
668                 /// </summary>
669                 public void SetColumnError (string columnName, string error) 
670                 {
671                         SetColumnError (_table.Columns.IndexOf (columnName), error);
672                 }
673
674                 /// <summary>
675                 /// Sets the value of the specified DataColumn to a null value.
676                 /// </summary>
677                 [MonoTODO]
678                 protected void SetNull (DataColumn column) 
679                 {
680                         throw new NotImplementedException ();
681                 }
682
683                 /// <summary>
684                 /// Sets the parent row of a DataRow with specified new parent DataRow.
685                 /// </summary>
686                 [MonoTODO]
687                 public void SetParentRow (DataRow parentRow) 
688                 {
689                         throw new NotImplementedException ();
690                 }
691
692                 /// <summary>
693                 /// Sets the parent row of a DataRow with specified new parent DataRow and
694                 /// DataRelation.
695                 /// </summary>
696                 [MonoTODO]
697                 public void SetParentRow (DataRow parentRow, DataRelation relation) 
698                 {
699                         throw new NotImplementedException ();
700                 }
701
702                 
703                 #endregion // Methods
704         }
705 }