context: //
  // System.Data.DataColumn.cs
  //
  // Author:
  //   Franklin Wise (gracenote@earthlink.net)
  //   Christopher Podurgiel (cpodurgiel@msn.com)
  //   Rodrigo Moya (rodrigo@ximian.com)
  //   Daniel Morgan (danmorg@sc.rr.com)
  //   Tim Coleman (tim@timcoleman.com)
  //
  // (C) Copyright 2002, Franklin Wise
  // (C) Chris Podurgiel
  // (C) Ximian, Inc 2002
  // Copyright (C) Tim Coleman, 2002
  // Copyright (C) Daniel Morgan, 2002, 2003
  //
  
  //
  // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
  // Copyright (C) 2011 Xamarin Inc (http://www.xamarin.com)
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  //
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  
  using System;
  using System.ComponentModel;
  using System.Reflection;
  using System.Collections;
  using System.Data.Common;
  using System.Data.SqlTypes;
  using System.Globalization;
  using Mono.Data.SqlExpressions;
  
  namespace System.Data {
  	internal delegate void DelegateColumnValueChange (DataColumn column, DataRow row, object proposedValue);
  
  	/// <summary>
  	/// Summary description for DataColumn.
  	/// </summary>
  
  	[Editor ("Microsoft.VSDesigner.Data.Design.DataColumnEditor, " + Consts.AssemblyMicrosoft_VSDesigner,
  		 "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
  	[ToolboxItem (false)]
  	[DefaultProperty ("ColumnName")]
  	[DesignTimeVisible (false)]
  	public class DataColumn : MarshalByValueComponent {
  #region Events
  		EventHandlerList _eventHandlers = new EventHandlerList ();
  
  		//used for constraint validation
  		//if an exception is fired during this event the change should be canceled
  		//[MonoTODO]
  		//internal event DelegateColumnValueChange ValidateColumnValueChange;
  
  		//used for FK Constraint Cascading rules
  		//[MonoTODO]
  		//internal event DelegateColumnValueChange ColumnValueChanging;
  
  		static readonly object _propertyChangedKey = new object ();
  		internal event PropertyChangedEventHandler PropertyChanged {
  			add { _eventHandlers.AddHandler (_propertyChangedKey, value); }
  			remove { _eventHandlers.RemoveHandler (_propertyChangedKey, value); }
  		}
  
  		#endregion //Events
  
  		#region Fields
  
  		private bool _allowDBNull = true;
  		private bool _autoIncrement;
  		private long _autoIncrementSeed;
  		private long _autoIncrementStep = 1;
  		private long _nextAutoIncrementValue;
  		private string _caption;
  		private MappingType _columnMapping;
  		private string _columnName = String.Empty;
  		private object _defaultValue = GetDefaultValueForType (null);
  		private string _expression;
  		private IExpression _compiledExpression;
  		private PropertyCollection _extendedProperties = new PropertyCollection ();
  		private int _maxLength = -1; //-1 represents no length limit
  		private string _nameSpace;
  		private int _ordinal = -1; //-1 represents not part of a collection
  		private string _prefix = String.Empty;
  		private bool _readOnly;
  		private DataTable _table;
  		private bool _unique;
  		private DataContainer _dataContainer;
  
  		#endregion // Fields
  
  		#region Constructors
  
  		public DataColumn ()
  			: this (String.Empty, typeof (string), String.Empty, MappingType.Element)
  		{
  		}
  
  		//TODO: Ctor init vars directly
  		public DataColumn (string columnName)
  			: this (columnName, typeof (string), String.Empty, MappingType.Element)
  		{
  		}
  
  		public DataColumn (string columnName, Type dataType)
  			: this (columnName, dataType, String.Empty, MappingType.Element)
  		{
  		}
  
  		public DataColumn (string columnName, Type dataType, string expr)
  			: this (columnName, dataType, expr, MappingType.Element)
  		{
  		}
  
  		public DataColumn (string columnName, Type dataType, string expr, MappingType type)
  		{
  			ColumnName = columnName == null ? String.Empty : columnName;
  
  			if (dataType == null)
  				throw new ArgumentNullException ("dataType");
  
  			DataType = dataType;
  			Expression = expr == null ? String.Empty : expr;
  			ColumnMapping = type;
  		}
  		#endregion
  
  		#region Properties
  
  		internal object this [int index] {
  			get { return DataContainer [index]; }
  			set {
  				if (!(value == null && AutoIncrement)) {
  					try {
  						DataContainer [index] = value;
  					} catch(Exception e) {
  						throw new ArgumentException (
  							String.Format (
  								"{0}. Couldn't store <{1}> in Column named '{2}'. Expected type is {3}.",
  								e.Message, value, ColumnName, DataType.Name),
  							e);
  					}
  				}
  
  				if (AutoIncrement && !DataContainer.IsNull (index)) {
  					long value64 = Convert.ToInt64 (value);
  					UpdateAutoIncrementValue (value64);
  				}
  			}
  		}
  
  #if NET_2_0
  		DataSetDateTime _datetimeMode = DataSetDateTime.UnspecifiedLocal;
  		[DefaultValue (DataSetDateTime.UnspecifiedLocal)]
  		[RefreshProperties (RefreshProperties.All)]
  		public DataSetDateTime DateTimeMode {
  			get { return _datetimeMode; }
  			set {
  				if (DataType != typeof (DateTime))
  					throw new InvalidOperationException ("The DateTimeMode can be set only on DataColumns of type DateTime.");
  
  				if (!Enum.IsDefined (typeof (DataSetDateTime), value))
  					throw new InvalidEnumArgumentException (
  						string.Format (
  							CultureInfo.InvariantCulture, "The {0} enumeration value, {1}, is invalid",
  							typeof (DataSetDateTime).Name, value));
  
  				if (_datetimeMode == value)
  					return;
  				if (_table == null || _table.Rows.Count == 0) {
  					_datetimeMode = value;
  					return;
  				}
  				if ((_datetimeMode == DataSetDateTime.Unspecified || _datetimeMode == DataSetDateTime.UnspecifiedLocal)
  					&& (value == DataSetDateTime.Unspecified || value == DataSetDateTime.UnspecifiedLocal)) {
  					_datetimeMode = value;
  					return;
  				}
  
  				throw new InvalidOperationException (
  					String.Format ("Cannot change DateTimeMode from '{0}' to '{1}' once the table has data.",
  						       _datetimeMode, value));
  			}
  		}
  #endif
  
  		[DataCategory ("Data")]
  #if !NET_2_0
  		[DataSysDescription ("Indicates whether null values are allowed in this column.")]
  #endif
  		[DefaultValue (true)]
  		public bool AllowDBNull {
  			get { return _allowDBNull; }
  			set {
  				if (!value && null != _table) {
  					for (int r = 0; r < _table.Rows.Count; r++) {
  						DataRow row = _table.Rows [r];
  						DataRowVersion version = row.HasVersion (DataRowVersion.Default) ?
  							DataRowVersion.Default : DataRowVersion.Original;
  						if (row.IsNull (this, version))
  							throw new DataException ("Column '" + ColumnName + "' has null values in it.");
  						//TODO: do we also check different versions of the row??
  					}
  				}
  
  				_allowDBNull = value;
  			}
  		}
  
  		/// <summary>
  		/// Gets or sets a value indicating whether the column automatically increments the value of the column for new rows added to the table.
  		/// </summary>
  		/// <remarks>
  		///		If the type of this column is not Int16, Int32, or Int64 when this property is set,
  		///		the DataType property is coerced to Int32. An exception is generated if this is a computed column
  		///		(that is, the Expression property is set.) The incremented value is used only if the row's value for this column,
  		///		when added to the columns collection, is equal to the default value.
  		///	</remarks>
  		[DataCategory ("Data")]
  #if !NET_2_0
  		[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.")]
  #endif
  		[DefaultValue (false)]
  		[RefreshProperties (RefreshProperties.All)]
  		public bool AutoIncrement {
  			get { return _autoIncrement; }
  			set {
  				if (value) {
  					//Can't be true if this is a computed column
  					if (Expression != string.Empty)
  						throw new ArgumentException ("Can not Auto Increment a computed column.");
  
  					if (DefaultValue != DBNull.Value)
  						throw new ArgumentException ("Can not set AutoIncrement while default value exists for this column.");
  
  					if (!CanAutoIncrement (DataType))
  						DataType = typeof (Int32);
  				}
  
  				if (_table != null)
  					_table.Columns.UpdateAutoIncrement (this, value);
  				_autoIncrement = value;
  			}
  		}
  
  		[DataCategory ("Data")]
  #if !NET_2_0
  		[DataSysDescription ("Indicates the starting value for an AutoIncrement column.")]
  #endif
  		[DefaultValue (0)]
  		public long AutoIncrementSeed {
  			get { return _autoIncrementSeed; }
  			set {
  				_autoIncrementSeed = value;
  				_nextAutoIncrementValue = _autoIncrementSeed;
  			}
  		}
  
  		[DataCategory ("Data")]
  #if !NET_2_0
  		[DataSysDescription ("Indicates the increment used by an AutoIncrement column.")]
  #endif
  		[DefaultValue (1)]
  		public long AutoIncrementStep {
  			get { return _autoIncrementStep; }
  			set { _autoIncrementStep = value; }
  		}
  
  		internal void UpdateAutoIncrementValue (long value64)
  		{
  			if (_autoIncrementStep > 0) {
  				if (value64 >= _nextAutoIncrementValue) {
  					_nextAutoIncrementValue = value64;
  					AutoIncrementValue ();
  				}
  			} else if (value64 <= _nextAutoIncrementValue) {
  				_nextAutoIncrementValue = value64;
  				AutoIncrementValue ();
  			}
  		}
  
  		internal long AutoIncrementValue ()
  		{
  			long currentValue = _nextAutoIncrementValue;
  			_nextAutoIncrementValue += AutoIncrementStep;
  			return currentValue;
  		}
  
  		internal long GetAutoIncrementValue ()
  		{
  			return _nextAutoIncrementValue;
  		}
  
  		internal void SetDefaultValue (int index)
  		{
  			if (AutoIncrement)
  				this [index] = _nextAutoIncrementValue;
  			else
  				DataContainer.CopyValue (Table.DefaultValuesRowIndex, index);
  		}
  
  		[DataCategory ("Data")]
  #if !NET_2_0
  		[DataSysDescription ("Indicates the default user-interface caption for this column.")]
  #endif
  		public string Caption {
  			get { return _caption == null ? ColumnName : _caption; }
  			set { _caption = value == null ? String.Empty : value; }
  		}
  
  #if !NET_2_0
  		[DataSysDescription ("Indicates how this column persists in XML: as an attribute, element, simple content node, or nothing.")]
  #endif
  		[DefaultValue (MappingType.Element)]
  		public virtual MappingType ColumnMapping {
  			get { return _columnMapping; }
  			set { _columnMapping = value; }
  		}
  
  		[DataCategory ("Data")]
  #if !NET_2_0
  		[DataSysDescription ("Indicates the name used to look up this column in the Columns collection of a DataTable.")]
  #endif
  		[RefreshProperties (RefreshProperties.All)]
  		[DefaultValue ("")]
  		public string ColumnName {
  			get { return _columnName; }
  			set {
  				if (value == null)
  					value = String.Empty;
  
  				CultureInfo info = Table != null ? Table.Locale : CultureInfo.CurrentCulture;
  				if (String.Compare (value, _columnName, true, info) != 0) {
  					if (Table != null) {
  						if (value.Length == 0)
  							throw new ArgumentException ("ColumnName is required when it is part of a DataTable.");
  
  						Table.Columns.RegisterName (value, this);
  						if (_columnName.Length > 0)
  							Table.Columns.UnregisterName (_columnName);
  					}
  
  					RaisePropertyChanging ("ColumnName");
  					_columnName = value;
  
  					if (Table != null)
  						Table.ResetPropertyDescriptorsCache ();
  				} else if (String.Compare (value, _columnName, false, info) != 0) {
  					RaisePropertyChanging ("ColumnName");
  					_columnName = value;
  
  					if (Table != null)
  						Table.ResetPropertyDescriptorsCache ();
  				}
  			}
  		}
  
  		[DataCategory ("Data")]
  #if !NET_2_0
  		[DataSysDescription ("Indicates the type of data stored in this column.")]
  #endif
  		[DefaultValue (typeof (string))]
  		[RefreshProperties (RefreshProperties.All)]
  		[TypeConverterAttribute (typeof (ColumnTypeConverter))]
  		public Type DataType {
  			get { return DataContainer.Type; }
  			set {
  				if (value == null)
  					return;
  
  				if (_dataContainer != null) {
  					if (value == _dataContainer.Type)
  						return;
  
  					// check if data already exists can we change the datatype
  					if (_dataContainer.Capacity > 0)
  						throw new ArgumentException ("The column already has data stored.");
  				}
  
  				if (null != GetParentRelation () || null != GetChildRelation ())
  					throw new InvalidConstraintException ("Cannot change datatype when column is part of a relation");
  
  				Type prevType = _dataContainer != null ? _dataContainer.Type : null; // current
  
  #if NET_2_0
  				if (_dataContainer != null && _dataContainer.Type == typeof (DateTime))
  					_datetimeMode = DataSetDateTime.UnspecifiedLocal;
  #endif
  				_dataContainer = DataContainer.Create (value, this);
  
  				//Check AutoIncrement status, make compatible datatype
  				if(AutoIncrement == true) {
  					// we want to check that the datatype is supported?					
  					if (!CanAutoIncrement (value))
  						AutoIncrement = false;
  				}
  
  				if (DefaultValue != GetDefaultValueForType (prevType))
  					SetDefaultValue (DefaultValue, true);
  				else
  					_defaultValue = GetDefaultValueForType (DataType);
  			}
  		}
  
  		/// <summary>
  		///
  		/// </summary>
  		/// <remarks>When AutoIncrement is set to true, there can be no default value.</remarks>
  		/// <exception cref="System.InvalidCastException"></exception>
  		/// <exception cref="System.ArgumentException"></exception>
  		[DataCategory ("Data")]
  #if !NET_2_0
  		[DataSysDescription ("Indicates the default column value used when adding new rows to the table.")]
  #endif
  		[TypeConverterAttribute (typeof (System.Data.DefaultValueTypeConverter))]
  		public object DefaultValue {
  			get { return _defaultValue; }
  
  			set {
  				if (AutoIncrement)
  					throw new ArgumentException ("Can not set default value while AutoIncrement is true on this column.");
  				SetDefaultValue (value, false);
  			}
  		}
  
  		void SetDefaultValue (object value, bool forcedTypeCheck)
  		{
  			if (forcedTypeCheck || !this._defaultValue.Equals (value)) {
  
gt: 				if (value == null || value == DBNull.Value)