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