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