More patches from Eran Domb <erand@mainsoft.com>.
[mono.git] / mcs / class / System.Data / System.Data / DataColumn.cs
1 //
2 // System.Data.DataColumn.cs
3 //
4 // Author:
5 //   Franklin Wise (gracenote@earthlink.net)
6 //   Christopher Podurgiel (cpodurgiel@msn.com)
7 //   Rodrigo Moya (rodrigo@ximian.com)
8 //   Daniel Morgan (danmorg@sc.rr.com)
9 //   Tim Coleman (tim@timcoleman.com)
10 //
11 // (C) Copyright 2002, Franklin Wise
12 // (C) Chris Podurgiel
13 // (C) Ximian, Inc 2002
14 // Copyright (C) Tim Coleman, 2002
15 // Copyright (C) Daniel Morgan, 2002, 2003
16 //
17
18 using System;
19 using System.ComponentModel;
20 using System.Reflection;
21
22 namespace System.Data {
23         internal delegate void DelegateColumnValueChange(DataColumn column, DataRow row, object proposedValue);
24         
25         /// <summary>
26         /// Summary description for DataColumn.
27         /// </summary>
28
29         [Editor]
30         [ToolboxItem (false)]
31         [DefaultMember ("Item")]
32         [DefaultProperty ("ColumnName")]
33         [DesignTimeVisible (false)]
34         public class DataColumn : MarshalByValueComponent
35         {               
36                 #region Events
37                 [MonoTODO]
38                 //used for constraint validation
39                 //if an exception is fired during this event the change should be canceled
40                 internal event DelegateColumnValueChange ValidateColumnValueChange;
41
42                 //used for FK Constraint Cascading rules
43                 internal event DelegateColumnValueChange ColumnValueChanging;
44                 #endregion //Events
45                 
46                 #region Fields
47
48                 private bool _allowDBNull = true;
49                 private bool _autoIncrement = false;
50                 private long _autoIncrementSeed = 0;
51                 private long _autoIncrementStep = 1;
52                 private long _nextAutoIncrementValue = 0;
53                 private bool dataHasBeenSet = false;
54                 private string _caption = null;
55                 private MappingType _columnMapping = MappingType.Element;
56                 private string _columnName = null;
57                 private Type _dataType = Type.GetType ("System.String");
58                 private object _defaultValue = DBNull.Value;
59                 private string expression = "";
60                 private PropertyCollection _extendedProperties = new PropertyCollection ();
61                 private int maxLength = -1; //-1 represents no length limit
62                 private string nameSpace = "";
63                 private int _ordinal = -1; //-1 represents not part of a collection
64                 private string prefix = "";
65                 private bool readOnly = false;
66                 private DataTable _table = null;
67                 private bool unique = false;
68
69                 #endregion // Fields
70
71                 #region Constructors
72
73                 public DataColumn()
74                 {
75                 }
76
77                 //TODO: Ctor init vars directly
78                 public DataColumn(string columnName): this()
79                 {
80                         ColumnName = columnName;
81                 }
82
83                 public DataColumn(string columnName, Type dataType): this(columnName)
84                 {
85                         if(dataType == null) {
86                                 throw new ArgumentNullException("dataType can't be null.");
87                         }
88                         
89                         DataType = dataType;
90
91                 }
92
93                 public DataColumn( string columnName, Type dataType, 
94                         string expr): this(columnName, dataType)
95                 {
96                         if (expr != null) Expression = expr;
97                 }
98
99                 public DataColumn(string columnName, Type dataType, 
100                         string expr, MappingType type): this(columnName, dataType, expr)
101                 {
102                         ColumnMapping = type;
103                 }
104                 #endregion
105
106                 #region Properties
107
108                 [DataCategory ("Data")]
109                 [DataSysDescription ("Indicates whether null values are allowed in this column.")]
110                 [DefaultValue (true)]
111                 public bool AllowDBNull
112                 {
113                         get {
114                                 return _allowDBNull;
115                         }
116                         set {
117                                 //TODO: If we are a part of the table and this value changes
118                                 //we need to validate that all the existing values conform to the new setting
119
120                                 if (true == value)
121                                 {
122                                         _allowDBNull = true;
123                                         return;
124                                 }
125                                 
126                                 //if Value == false case
127                                 if (null != _table)
128                                 {
129                                         if (_table.Rows.Count > 0)
130                                         {
131                                                 bool nullsFound = false;
132                                                 for(int r = 0; r < _table.Rows.Count; r++) {
133                                                         DataRow row = _table.Rows[r];
134                                                         if(row.IsNull(this)) {
135                                                                 nullsFound = true;
136                                                                 break;
137                                                         }
138                                                 }
139                                                 
140                                                 //TODO: Validate no null values exist
141                                                 //do we also check different versions of the row??
142                                         }
143                                 }
144                                         
145                                 _allowDBNull = value;
146                         }
147                 }
148         
149                 /// <summary>
150                 /// Gets or sets a value indicating whether the column automatically increments the value of the column for new rows added to the table.
151                 /// </summary>
152                 /// <remarks>
153                 ///             If the type of this column is not Int16, Int32, or Int64 when this property is set, 
154                 ///             the DataType property is coerced to Int32. An exception is generated if this is a computed column 
155                 ///             (that is, the Expression property is set.) The incremented value is used only if the row's value for this column, 
156                 ///             when added to the columns collection, is equal to the default value.
157                 ///     </remarks>
158                 [DataCategory ("Data")]
159                 [DataSysDescription ("Indicates whether the column automatically increments itself for new rows added to the table.  The type of this column must be Int16, Int32, or Int64.")]
160                 [DefaultValue (false)]
161                 [RefreshProperties (RefreshProperties.All)]
162                 public bool AutoIncrement
163                 {
164                         get {
165                                 return _autoIncrement;
166                         }
167                         set {
168                                 if(value == true)
169                                 {
170                                         //Can't be true if this is a computed column
171                                         if (Expression != string.Empty)
172                                         {
173                                                 throw new ArgumentException("Can not Auto Increment a computed column."); 
174                                         }
175
176                                         //If the DataType of this Column isn't an Int
177                                         //Make it an int
178                                         TypeCode typeCode = Type.GetTypeCode(_dataType);
179                                         if(typeCode != TypeCode.Int16 && 
180                                            typeCode != TypeCode.Int32 && 
181                                            typeCode != TypeCode.Int64)
182                                         {
183                                                 _dataType = typeof(Int32); 
184                                         }
185                                 }
186                                 _autoIncrement = value;
187                         }
188                 }
189
190                 [DataCategory ("Data")]
191                 [DataSysDescription ("Indicates the starting value for an AutoIncrement column.")]
192                 [DefaultValue (0)]
193                 public long AutoIncrementSeed
194                 {
195                         get {
196                                 return _autoIncrementSeed;
197                         }
198                         set {
199                                 _autoIncrementSeed = value;
200                                 _nextAutoIncrementValue = _autoIncrementSeed;
201                         }
202                 }
203
204                 [DataCategory ("Data")]
205                 [DataSysDescription ("Indicates the increment used by an AutoIncrement column.")]
206                 [DefaultValue (1)]
207                 public long AutoIncrementStep
208                 {
209                         get {
210                                 return _autoIncrementStep;
211                         }
212                         set {
213                                 _autoIncrementStep = value;
214                         }
215                 }
216
217                 internal void UpdateAutoIncrementValue (long value) 
218                 {
219                         if(value > _nextAutoIncrementValue) {
220                                 _nextAutoIncrementValue = value;
221                                 AutoIncrementValue ();
222                         }
223                 }
224
225                 internal long AutoIncrementValue () 
226                 {
227                         long currentValue = _nextAutoIncrementValue;
228                         _nextAutoIncrementValue += AutoIncrementStep;
229                         return currentValue;
230                 }
231
232                 internal bool DataHasBeenSet {
233                         get {
234                                 return dataHasBeenSet;
235                         }
236                         set {
237                                 dataHasBeenSet = value;
238                         }
239                 }
240
241                 [DataCategory ("Data")]
242                 [DataSysDescription ("Indicates the default user-interface caption for this column.")]
243                 public string Caption 
244                 {
245                         get {
246                                 if(_caption == null)
247                                         return ColumnName;
248                                 else
249                                         return _caption;
250                         }
251                         set {
252                                 _caption = value;
253                         }
254                 }
255                 [DataSysDescription ("Indicates how this column persists in XML: as an attribute, element, simple content node, or nothing.")]
256                 [DefaultValue (MappingType.Element)]
257                 public virtual MappingType ColumnMapping
258                 {
259                         get {
260                                 return _columnMapping;
261                         }
262                         set {
263                                 _columnMapping = value;
264                         }
265                 }
266
267                 [DataCategory ("Data")]
268                 [DataSysDescription ("Indicates the name used to look up this column in the Columns collection of a DataTable.")]
269                 [RefreshProperties (RefreshProperties.All)]
270                 [DefaultValue ("")]
271                 public string ColumnName
272                 {
273                         get {
274                                 return "" + _columnName;
275                         }
276                         set {
277                                 //Both are checked after the column is part of the collection
278                                 //TODO: Check Name duplicate
279                                 //TODO: check Name != null
280                                 _columnName = value;
281                         }
282                 }
283
284                 [DataCategory ("Data")]
285                 [DataSysDescription ("Indicates the type of data stored in this column.")]
286                 [DefaultValue (typeof (string))]
287                 [RefreshProperties (RefreshProperties.All)]
288                 public Type DataType
289                 {
290                         get {
291                                 return _dataType;
292                         }
293                         set {
294                                 // check if data already exists can we change the datatype
295                                 if(DataHasBeenSet == true)
296                                         throw new ArgumentException("The column already has data stored.");
297
298                                 // we want to check that the datatype is supported?
299                                 TypeCode typeCode = Type.GetTypeCode(value);
300                                 
301                                 //Check AutoIncrement status, make compatible datatype
302                                 if(AutoIncrement == true) {
303                                         if(typeCode != TypeCode.Int16 &&
304                                            typeCode != TypeCode.Int32 &&
305                                            typeCode != TypeCode.Int64)
306                                                 AutoIncrement = false;
307                                 }
308                                 _dataType = value;
309                         }
310                 }
311
312                 /// <summary>
313                 /// 
314                 /// </summary>
315                 /// <remarks>When AutoIncrement is set to true, there can be no default value.</remarks>
316                 /// <exception cref="System.InvalidCastException"></exception>
317                 /// <exception cref="System.ArgumentException"></exception>
318                 [DataCategory ("Data")]
319                 [DataSysDescription ("Indicates the default column value used when adding new rows to the table.")]
320                 public object DefaultValue
321                 {
322                         get {
323                                 return _defaultValue;
324                         }
325                         set {
326                                 
327                                 //If autoIncrement == true throw
328                                 if (AutoIncrement) 
329                                 {
330                                         throw new ArgumentException("Can not set default value while" +
331                                                         " AutoIncrement is true on this column.");
332                                 }
333                                         
334                                 //Will throw invalid cast exception
335                                 //if value is not the correct type
336                                 //FIXME: some types can be casted
337                                 if (value.GetType() != _dataType)
338                                 {
339                                         throw new InvalidCastException("Default Value type is not compatible with" + 
340                                                         " column type.");
341                                 }
342                                         
343                                 _defaultValue = value;
344                         }
345                 }
346
347                 [MonoTODO]
348                 [DataCategory ("Data")]
349                 [DataSysDescription ("Indicates the value that this column computes for each row based on other columns instead of taking user input.")]
350                 [DefaultValue ("")]
351                 [RefreshProperties (RefreshProperties.All)]
352                 public string Expression
353                 {
354                         get {
355                                 return expression;
356                         }
357                         set {
358                                 //TODO: validation of the expression
359                                 expression = value;  //Check?
360                                 
361                                 if (expression != string.Empty)
362                                         ReadOnly = true;
363                         }
364                 }
365
366                 [Browsable (false)]
367                 [DataCategory ("Data")]
368                 [DataSysDescription ("The collection that holds custom user information.")]
369                 public PropertyCollection ExtendedProperties
370                 {
371                         get {
372                                 return _extendedProperties;
373                         }
374                 }
375
376                 [DataCategory ("Data")]
377                 [DataSysDescription ("Indicates the maximum length of the value this column allows.")]
378                 [DefaultValue (-1)]
379                 public int MaxLength
380                 {
381                         get {
382                                 //Default == -1 no max length
383                                 return maxLength;
384                         }
385                         set {
386                                 //only applies to string columns
387                                 maxLength = value;
388                         }
389                 }
390
391                 [DataCategory ("Data")]
392                 [DataSysDescription ("Indicates the XML uri for elements stored in this  column.")]
393                 public string Namespace
394                 {
395                         get {
396                                 return nameSpace;
397                         }
398                         set {
399                                 nameSpace = value;
400                         }
401                 }
402
403                 //Need a good way to set the Ordinal when the column is added to a columnCollection.
404                 [Browsable (false)]
405                 [DataCategory ("Data")]
406                 [DataSysDescription ("Indicates the index of this column in the Columns collection.")]
407                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
408                 public int Ordinal
409                 {
410                         get {
411                                 //value is -1 if not part of a collection
412                                 return _ordinal;
413                         }
414                 }
415
416                 internal void SetOrdinal(int ordinal)
417                 {
418                         _ordinal = ordinal;
419                 }
420
421                 [DataCategory ("Data")]
422                 [DataSysDescription ("Indicates the prefix used for this DataColumn in the xml representation.")]
423                 [DefaultValue ("")]
424                 public string Prefix
425                 {
426                         get {
427                                 return prefix;
428                         }
429                         set {
430                                 prefix = value;
431                         }
432                 }
433
434                 [DataCategory ("Data")]
435                 [DataSysDescription ("Indicates whether this column allows changes once a row has been added to the table.")]
436                 [DefaultValue (false)]
437                 public bool ReadOnly
438                 {
439                         get {
440                                 return readOnly;
441                         }
442                         set {
443                                 readOnly = value;
444                         }
445                 }
446
447                 [Browsable (false)]
448                 [DataCategory ("Data")]
449                 [DataSysDescription ("Returns the DataTable to which this column belongs.")]
450                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]      
451                 public DataTable Table
452                 {
453                         get {
454                                 return _table;
455                         }
456                 }
457
458                 [MonoTODO]
459                 [DataCategory ("Data")]
460                 [DataSysDescription ("Indicates whether this column should restrict its values in the rows of the table to be unique.")]
461                 [DefaultValue (false)]
462                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
463                 public bool Unique 
464                 {
465                         get {
466                                 return unique;
467                         }
468                         set {
469                                 //if Table == null then the UniqueConstraint is
470                                 //created on addition to the collection
471                                 
472                                 //FIXME?: need to check if value is the same
473                                 //because when calling "new UniqueConstraint"
474                                 //the new object tries to set "column.Unique = True"
475                                 //which creates an infinite loop.
476                                 if(unique != value)
477                                 {
478                                 unique = value;
479
480                                         if( value )
481                                         {
482                                                 if (Expression != null && Expression != "")
483                                                         throw new ArgumentException("Cannot change Unique property for the expression column.");
484                                                 if( _table != null )
485                                                 {
486                                                         UniqueConstraint uc = new UniqueConstraint(this);
487                                                         _table.Constraints.Add(uc);
488                                                 }
489                                         }
490                                         else
491                                         {
492                                                 if( _table != null )
493                                                 {
494                                                         ConstraintCollection cc = _table.Constraints;
495                                                         //foreach (Constraint c in cc) 
496                                                         for (int i = 0; i < cc.Count; i++)
497                                                         {
498                                                                 Constraint c = cc[i];
499                                                                 if (c is UniqueConstraint)
500                                                                 {
501                                                                         DataColumn[] cols = ((UniqueConstraint)c).Columns;
502                                                                         
503                                                                         if (cols.Length == 1 && cols[0] == this)
504                                                                         {
505                                                                                 if (!cc.CanRemove(c))
506                                                                                         throw new ArgumentException("Cannot remove unique constraint '" + c.ConstraintName + "'. Remove foreign key constraint first.");
507
508                                                                                 cc.Remove(c);
509                                                                         }
510                                                                         
511                                                                 }
512                                                         }
513                                                 }
514                                         }
515
516                                 }
517                         }
518                 }
519
520                 #endregion // Properties
521
522                 #region Methods
523                 
524 /* ??
525                 [MonoTODO]
526                 protected internal void CheckNotAllowNull() {
527                 }
528
529                 [MonoTODO]
530                 protected void CheckUnique() {
531                 }
532 */
533
534                 /// <summary>
535                 ///  Sets unique true whithout creating Constraint
536                 /// </summary>
537                 internal void SetUnique() 
538                 {
539                         unique = true;
540                 }
541
542
543                 [MonoTODO]
544                 internal void AssertCanAddToCollection()
545                 {
546                         //Check if Default Value is set and AutoInc is set
547                 }
548                 
549                 [MonoTODO]
550                 protected internal virtual void 
551                 OnPropertyChanging (PropertyChangedEventArgs pcevent) {
552                 }
553
554                 [MonoTODO]
555                 protected internal void RaisePropertyChanging(string name) {
556                 }
557
558                 /// <summary>
559                 /// Gets the Expression of the column, if one exists.
560                 /// </summary>
561                 /// <returns>The Expression value, if the property is set; 
562                 /// otherwise, the ColumnName property.</returns>
563                 [MonoTODO]
564                 public override string ToString()
565                 {
566                         if (expression != string.Empty)
567                                 return ColumnName + " + " + expression;
568                         
569                         return ColumnName;
570                 }
571
572                 [MonoTODO]
573                 internal void SetTable(DataTable table) {
574                         _table = table; 
575                         // this will get called by DataTable 
576                         // and DataColumnCollection
577                 }
578
579                 
580                 // Returns true if all the same collumns are in columnSet and compareSet
581                 internal static bool AreColumnSetsTheSame(DataColumn[] columnSet, DataColumn[] compareSet)
582                 {
583                         if (null == columnSet && null == compareSet) return true;
584                         if (null == columnSet || null == compareSet) return false;
585
586                         if (columnSet.Length != compareSet.Length) return false;
587                         
588                         foreach (DataColumn col in columnSet)
589                         {
590                                 bool matchFound = false;
591                                 foreach (DataColumn compare in compareSet)
592                                 {
593                                         if (col == compare)
594                                         {
595                                                 matchFound = true;                                      
596                                         }
597                                 }
598                                 if (! matchFound) return false;
599                         }
600                         
601                         return true;
602                 }
603                 
604                 #endregion // Methods
605
606         }
607 }