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