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