// Odp.Net Data Provider.
// http://www.oracle.com/technology/tech/windows/odpnet/index.html
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;

using BLToolkit.Aspects;
using BLToolkit.Common;
using BLToolkit.Mapping;
using BLToolkit.Reflection;

#if MANAGED
using Oracle.ManagedDataAccess.Client;
using Oracle.ManagedDataAccess.Types;
#else
using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;
#endif

namespace BLToolkit.Data.DataProvider
{
	using Sql.SqlProvider;
	using BLToolkit.Data.Sql;

	/// <summary>
	/// Implements access to the Data Provider for Oracle.
	/// </summary>
	/// <remarks>
	/// See the <see cref="DbManager.AddDataProvider(DataProviderBase)"/> method to find an example.
	/// </remarks>
	/// <seealso cref="DbManager.AddDataProvider(DataProviderBase)">AddDataManager Method</seealso>
#if !MANAGED
	public class OdpDataProvider : DataProviderBase
	{
		public OdpDataProvider()
		{
			MappingSchema = new OdpMappingSchema();
		}

		public const string NameString = DataProvider.ProviderName.Oracle;

		private const string DbTypeTableName = "Oracle.DataAccess.Client.OraDb_DbTypeTable";

		static OdpDataProvider()
		{
#else
	public class OdpManagedDataProvider : DataProviderBase
	{
		public OdpManagedDataProvider()
		{
			MappingSchema = new OdpMappingSchema();
		}

		public const string NameString = DataProvider.ProviderName.OracleManaged;

		private const string DbTypeTableName = "Oracle.ManagedDataAccess.Client.OraDb_DbTypeTable";

		static OdpManagedDataProvider()
		{
#endif
			// Fix Oracle.Net bug #1: Array types are not handled.
			//
			var oraDbDbTypeTableType = typeof(OracleParameter).Assembly.GetType(DbTypeTableName);

			if (null != oraDbDbTypeTableType)
			{
				var typeTable = (Hashtable)oraDbDbTypeTableType.InvokeMember(
					"s_table", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.GetField,
					null, null, Type.EmptyTypes);

				if (null != typeTable)
				{
					typeTable[typeof(DateTime[])]          = OracleDbType.TimeStamp;
					typeTable[typeof(Int16[])]             = OracleDbType.Int16;
					typeTable[typeof(Int32[])]             = OracleDbType.Int32;
					typeTable[typeof(Int64[])]             = OracleDbType.Int64;
					typeTable[typeof(Single[])]            = OracleDbType.Single;
					typeTable[typeof(Double[])]            = OracleDbType.Double;
					typeTable[typeof(Decimal[])]           = OracleDbType.Decimal;
					typeTable[typeof(TimeSpan[])]          = OracleDbType.IntervalDS;
					typeTable[typeof(String[])]            = OracleDbType.Varchar2;
					typeTable[typeof(OracleBFile[])]       = OracleDbType.BFile;
					typeTable[typeof(OracleBinary[])]      = OracleDbType.Raw;
					typeTable[typeof(OracleBlob[])]        = OracleDbType.Blob;
					typeTable[typeof(OracleClob[])]        = OracleDbType.Clob;
					typeTable[typeof(OracleDate[])]        = OracleDbType.Date;
					typeTable[typeof(OracleDecimal[])]     = OracleDbType.Decimal;
					typeTable[typeof(OracleIntervalDS[])]  = OracleDbType.IntervalDS;
					typeTable[typeof(OracleIntervalYM[])]  = OracleDbType.IntervalYM;
					typeTable[typeof(OracleRefCursor[])]   = OracleDbType.RefCursor;
					typeTable[typeof(OracleString[])]      = OracleDbType.Varchar2;
					typeTable[typeof(OracleTimeStamp[])]   = OracleDbType.TimeStamp;
					typeTable[typeof(OracleTimeStampLTZ[])]= OracleDbType.TimeStampLTZ;
					typeTable[typeof(OracleTimeStampTZ[])] = OracleDbType.TimeStampTZ;
#if !MANAGED
					typeTable[typeof(OracleXmlType[])]     = OracleDbType.XmlType;
#endif

					typeTable[typeof(Boolean)]             = OracleDbType.Byte;
					typeTable[typeof(Guid)]                = OracleDbType.Raw;
					typeTable[typeof(SByte)]               = OracleDbType.Decimal;
					typeTable[typeof(UInt16)]              = OracleDbType.Decimal;
					typeTable[typeof(UInt32)]              = OracleDbType.Decimal;
					typeTable[typeof(UInt64)]              = OracleDbType.Decimal;

					typeTable[typeof(Boolean[])]           = OracleDbType.Byte;
					typeTable[typeof(Guid[])]              = OracleDbType.Raw;
					typeTable[typeof(SByte[])]             = OracleDbType.Decimal;
					typeTable[typeof(UInt16[])]            = OracleDbType.Decimal;
					typeTable[typeof(UInt32[])]            = OracleDbType.Decimal;
					typeTable[typeof(UInt64[])]            = OracleDbType.Decimal;

					typeTable[typeof(Boolean?)]            = OracleDbType.Byte;
					typeTable[typeof(Guid?)]               = OracleDbType.Raw;
					typeTable[typeof(SByte?)]              = OracleDbType.Decimal;
					typeTable[typeof(UInt16?)]             = OracleDbType.Decimal;
					typeTable[typeof(UInt32?)]             = OracleDbType.Decimal;
					typeTable[typeof(UInt64?)]             = OracleDbType.Decimal;
					typeTable[typeof(DateTime?[])]         = OracleDbType.TimeStamp;
					typeTable[typeof(Int16?[])]            = OracleDbType.Int16;
					typeTable[typeof(Int32?[])]            = OracleDbType.Int32;
					typeTable[typeof(Int64?[])]            = OracleDbType.Int64;
					typeTable[typeof(Single?[])]           = OracleDbType.Single;
					typeTable[typeof(Double?[])]           = OracleDbType.Double;
					typeTable[typeof(Decimal?[])]          = OracleDbType.Decimal;
					typeTable[typeof(TimeSpan?[])]         = OracleDbType.IntervalDS;
					typeTable[typeof(Boolean?[])]          = OracleDbType.Byte;
					typeTable[typeof(Guid?[])]             = OracleDbType.Raw;
					typeTable[typeof(SByte?[])]            = OracleDbType.Decimal;
					typeTable[typeof(UInt16?[])]           = OracleDbType.Decimal;
					typeTable[typeof(UInt32?[])]           = OracleDbType.Decimal;
					typeTable[typeof(UInt64?[])]           = OracleDbType.Decimal;

					typeTable[typeof(XmlReader)]           = OracleDbType.XmlType;
					typeTable[typeof(XmlDocument)]         = OracleDbType.XmlType;
					typeTable[typeof(MemoryStream)]        = OracleDbType.Blob;
					typeTable[typeof(XmlReader[])]         = OracleDbType.XmlType;
					typeTable[typeof(XmlDocument[])]       = OracleDbType.XmlType;
					typeTable[typeof(MemoryStream[])]      = OracleDbType.Blob;
				}
			}
		}

		/// <summary>
		/// Creates the database connection object.
		/// </summary>
		/// <remarks>
		/// See the <see cref="DbManager.AddDataProvider(DataProviderBase)"/> method to find an example.
		/// </remarks>
		/// <seealso cref="DbManager.AddDataProvider(DataProviderBase)">AddDataManager Method</seealso>
		/// <returns>The database connection object.</returns>
		public override IDbConnection CreateConnectionObject()
		{
			return new OracleConnection();
		}

		public override IDbCommand CreateCommandObject(IDbConnection connection)
		{
			var oraConnection = connection as OracleConnection;

			if (null != oraConnection)
			{
				var oraCommand = oraConnection.CreateCommand();

				// Fix Oracle.Net bug #2: Empty arrays can not be sent to the server.
				//
				oraCommand.BindByName = true;

				return oraCommand;
			}

			return base.CreateCommandObject(connection);
		}

		public override void SetParameterValue(IDbDataParameter parameter, object value)
		{
			base.SetParameterValue(parameter, value);

			// strings and byte arrays larger than 4000 bytes may be handled improperly
			if (parameter is OracleParameterWrap)
			{
				const int ThresholdSize = 4000;
				if (value is string && Encoding.UTF8.GetBytes((string)value).Length > ThresholdSize)
				{
					((OracleParameterWrap)parameter).OracleParameter.OracleDbType = OracleDbType.Clob;
				}
				else if (value is byte[] && ((byte[])value).Length > ThresholdSize)
				{
					((OracleParameterWrap)parameter).OracleParameter.OracleDbType = OracleDbType.Blob;
				}
			}
		}

		public override IDbDataParameter CloneParameter(IDbDataParameter parameter)
		{
			var oraParameter = (parameter is OracleParameterWrap)?
				(parameter as OracleParameterWrap).OracleParameter: parameter as OracleParameter;

			if (null != oraParameter)
			{
				var oraParameterClone = (OracleParameter)oraParameter.Clone();

				// Fix Oracle.Net bug #3: CollectionType property is not cloned.
				//
				oraParameterClone.CollectionType = oraParameter.CollectionType;

				// Fix Oracle.Net bug #8423178
				// See http://forums.oracle.com/forums/thread.jspa?threadID=975902&tstart=0
				//
				if (oraParameterClone.OracleDbType == OracleDbType.RefCursor)
				{
					// Set OracleDbType to itself to reset m_bSetDbType and m_bOracleDbTypeExSet
					//
					oraParameterClone.OracleDbType = OracleDbType.RefCursor;
				}

				return OracleParameterWrap.CreateInstance(oraParameterClone);
			}

			return base.CloneParameter(parameter);
		}

		public override void SetUserDefinedType(IDbDataParameter parameter, string typeName)
		{
			var oraParameter = (parameter is OracleParameterWrap) ?
				(parameter as OracleParameterWrap).OracleParameter : parameter as OracleParameter;

			if (oraParameter == null)
				throw new ArgumentException("OracleParameter expected.", "parameter");

			oraParameter.UdtTypeName = typeName;
		}

		/// <summary>
		/// Creates the data adapter object.
		/// </summary>
		/// <remarks>
		/// See the <see cref="DbManager.AddDataProvider(DataProviderBase)"/> method to find an example.
		/// </remarks>
		/// <seealso cref="DbManager.AddDataProvider(DataProviderBase)">AddDataManager Method</seealso>
		/// <returns>A data adapter object.</returns>
		public override DbDataAdapter CreateDataAdapterObject()
		{
			return new OracleDataAdapter();
		}

		/// <summary>
		/// Populates the specified IDbCommand object's Parameters collection with 
		/// parameter information for the stored procedure specified in the IDbCommand.
		/// </summary>
		/// <remarks>
		/// See the <see cref="DbManager.AddDataProvider(DataProviderBase)"/> method to find an example.
		/// </remarks>
		/// <seealso cref="DbManager.AddDataProvider(DataProviderBase)">AddDataManager Method</seealso>
		/// <param name="command">The IDbCommand referencing the stored procedure for which the parameter
		/// information is to be derived. The derived parameters will be populated into
		/// the Parameters of this command.</param>
		public override bool DeriveParameters(IDbCommand command)
		{
			var oraCommand = command as OracleCommand;

			if (null != oraCommand)
			{
				try
				{
					OracleCommandBuilder.DeriveParameters(oraCommand);
				}
				catch (Exception ex)
				{
					// Make Oracle less laconic.
					//
					throw new DataException(string.Format("{0}\nCommandText: {1}", ex.Message, oraCommand.CommandText), ex);
				}

				return true;
			}

			return false;
		}

		/// <summary>
		/// Open an <see cref="IDataReader"/> into the given <see cref="OracleRefCursor"/> object
		/// </summary>
		/// <param name="refCursor">an <see cref="OracleRefCursor"/> to perform GetDataReader() on</param>
		/// <returns>The <see cref="IDataReader"/> into the returned by GetDataReader()</returns>
		public override IDataReader GetRefCursorDataReader(object refCursor)
		{
			var oracleRefCursor = refCursor as OracleRefCursor;

			if (oracleRefCursor == null)
				throw new ArgumentException("Argument must be of type 'OracleRefCursor'", "refCursor");

			return oracleRefCursor.GetDataReader();
		}

		public override object Convert(object value, ConvertType convertType)
		{
			switch (convertType)
			{
				case ConvertType.NameToCommandParameter:
				case ConvertType.NameToSprocParameter:
					return ParameterPrefix == null? value: ParameterPrefix + value;

				case ConvertType.SprocParameterToName:
					var name = (string)value;

					if (name.Length > 0)
					{
						if (name[0] == ':')
							return name.Substring(1);

						if (ParameterPrefix != null &&
							name.ToUpper(CultureInfo.InvariantCulture).StartsWith(ParameterPrefix))
						{
							return name.Substring(ParameterPrefix.Length);
						}
					}

					break;

				case ConvertType.ExceptionToErrorNumber:
					if (value is OracleException)
						return ((OracleException)value).Number;
					break;
			}

			return SqlProvider.Convert(value, convertType);
		}

		public override void PrepareCommand(ref CommandType commandType, ref string commandText, ref IDbDataParameter[] commandParameters)
		{
			base.PrepareCommand(ref commandType, ref commandText, ref commandParameters);

			if (commandType == CommandType.Text)
			{
				// Fix Oracle bug #11 '\r' is not a valid character!
				//
				commandText = commandText.Replace('\r', ' ');
			}
		}

		public override void AttachParameter(IDbCommand command, IDbDataParameter parameter)
		{
			var oraParameter = (parameter is OracleParameterWrap)?
				(parameter as OracleParameterWrap).OracleParameter: parameter as OracleParameter;

			if (null != oraParameter)
			{
				if (oraParameter.CollectionType == OracleCollectionType.PLSQLAssociativeArray)
				{
					if (oraParameter.Direction == ParameterDirection.Input
						|| oraParameter.Direction == ParameterDirection.InputOutput)
					{
						var ar = oraParameter.Value as Array;

						if (null != ar && !(ar is byte[] || ar is char[]))
						{
							oraParameter.Size = ar.Length;

							if (oraParameter.DbType == DbType.String
								&& oraParameter.Direction == ParameterDirection.InputOutput)
							{
								var arrayBindSize = new int[oraParameter.Size];

								for (var i = 0; i < oraParameter.Size; ++i)
								{
									arrayBindSize[i] = 1024;
								}
								
								oraParameter.ArrayBindSize = arrayBindSize;
							}
						}

						if (oraParameter.Size == 0)
						{
							// Skip this parameter.
							// Fix Oracle.Net bug #2: Empty arrays can not be sent to the server.
							//
							return;
						}

						if (oraParameter.Value is Stream[])
						{
							var streams = (Stream[]) oraParameter.Value;

							for (var i = 0; i < oraParameter.Size; ++i)
							{
								if (streams[i] is OracleBFile || streams[i] is OracleBlob || streams[i] is OracleClob 
#if !MANAGED
									|| streams[i] is OracleXmlStream
#endif
									)
								{
									// Known Oracle type.
									//
									continue;
								}

								streams[i] = CopyStream(streams[i], (OracleCommand)command);
							}
						}
						else if (oraParameter.Value is XmlDocument[])
						{
							var xmlDocuments = (XmlDocument[]) oraParameter.Value;
							var values       = new object[oraParameter.Size];

							switch (oraParameter.OracleDbType)
							{
								case OracleDbType.XmlType:
#if !MANAGED
									for (var i = 0; i < oraParameter.Size; ++i)
									{
										values[i] = xmlDocuments[i].DocumentElement == null?
											(object) DBNull.Value:
											new OracleXmlType((OracleConnection)command.Connection, xmlDocuments[i]);
									}

									oraParameter.Value = values;
									break;
#else
									throw new NotSupportedException();
#endif
								// Fix Oracle.Net bug #9: XmlDocument.ToString() returns System.Xml.XmlDocument,
								// so m_value.ToString() is not enought.
								//
								case OracleDbType.Clob:
								case OracleDbType.NClob:
								case OracleDbType.Varchar2:
								case OracleDbType.NVarchar2:
								case OracleDbType.Char:
								case OracleDbType.NChar:
									for (var i = 0; i < oraParameter.Size; ++i)
									{
										values[i] = xmlDocuments[i].DocumentElement == null?
											(object) DBNull.Value:
											xmlDocuments[i].InnerXml;
									}

									oraParameter.Value = values;

									break;

								// Or convert to bytes if need.
								//
								case OracleDbType.Blob:
								case OracleDbType.BFile:
								case OracleDbType.Raw:
								case OracleDbType.Long:
								case OracleDbType.LongRaw:
									for (var i = 0; i < oraParameter.Size; ++i)
									{
										if (xmlDocuments[i].DocumentElement == null)
											values[i] = DBNull.Value;
										else
											using (var s = new MemoryStream())
											{
												xmlDocuments[i].Save(s);
												values[i] = s.GetBuffer();
											}
									}

									oraParameter.Value = values;

									break;
							}
						}
					}
					else if (oraParameter.Direction == ParameterDirection.Output)
					{
						// Fix Oracle.Net bug #4: ArrayBindSize must be explicitly specified.
						//
						if (oraParameter.DbType == DbType.String)
						{
							oraParameter.Size = 1024;
							var arrayBindSize = new int[oraParameter.Size];
							for (var i = 0; i < oraParameter.Size; ++i)
							{
								arrayBindSize[i] = 1024;
							}
							
							oraParameter.ArrayBindSize = arrayBindSize;
						}
						else
						{
							oraParameter.Size = 32767;
						}
					}
				}
				else if (oraParameter.Value is Stream)
				{
					var stream = (Stream) oraParameter.Value;

					if (!(stream is OracleBFile) && !(stream is OracleBlob) &&
						!(stream is OracleClob)
#if !MANAGED 
						&& !(stream is OracleXmlStream)
#endif
						)
					{
						oraParameter.Value = CopyStream(stream, (OracleCommand)command);
					}
				}
				else if (oraParameter.Value is Byte[])
				{
					var bytes = (Byte[]) oraParameter.Value;

					if (bytes.Length > 32000)
					{
						oraParameter.Value = CopyStream(bytes, (OracleCommand)command);
					}
				}
				else if (oraParameter.Value is XmlDocument)
				{
					var xmlDocument = (XmlDocument)oraParameter.Value;
					if (xmlDocument.DocumentElement == null)
						oraParameter.Value = DBNull.Value;
					else
					{

						switch (oraParameter.OracleDbType)
						{
							case OracleDbType.XmlType:
#if !MANAGED
								oraParameter.Value = new OracleXmlType((OracleConnection)command.Connection, xmlDocument);
								break;
#else
								throw new NotSupportedException();
#endif

							// Fix Oracle.Net bug #9: XmlDocument.ToString() returns System.Xml.XmlDocument,
							// so m_value.ToString() is not enought.
							//
							case OracleDbType.Clob:
							case OracleDbType.NClob:
							case OracleDbType.Varchar2:
							case OracleDbType.NVarchar2:
							case OracleDbType.Char:
							case OracleDbType.NChar:
								using (TextWriter w = new StringWriter())
								{
									xmlDocument.Save(w);
									oraParameter.Value = w.ToString();
								}
								break;

							// Or convert to bytes if need.
							//
							case OracleDbType.Blob:
							case OracleDbType.BFile:
							case OracleDbType.Raw:
							case OracleDbType.Long:
							case OracleDbType.LongRaw:
								using (var s = new MemoryStream())
								{
									xmlDocument.Save(s);
									oraParameter.Value = s.GetBuffer();
								}
								break;
						}
					}
				}

				parameter = oraParameter;
			}
			
			base.AttachParameter(command, parameter);
		}

		private static Stream CopyStream(Stream stream, OracleCommand cmd)
		{
			return CopyStream(Common.Convert.ToByteArray(stream), cmd);
		}

		private static Stream CopyStream(Byte[] bytes, OracleCommand cmd)
		{
			var ret = new OracleBlob(cmd.Connection);
			ret.Write(bytes, 0, bytes.Length);
			return ret;
		}

		public override bool IsValueParameter(IDbDataParameter parameter)
		{
			var oraParameter = (parameter is OracleParameterWrap)?
				(parameter as OracleParameterWrap).OracleParameter: parameter as OracleParameter;

			if (null != oraParameter)
			{
				if (oraParameter.OracleDbType == OracleDbType.RefCursor
					&& oraParameter.Direction == ParameterDirection.Output)
				{
					// Ignore out ref cursors, while out parameters of other types are o.k.
					return false;
				}
			}

			return base.IsValueParameter(parameter);
		}

		public override IDbDataParameter CreateParameterObject(IDbCommand command)
		{
			var parameter = base.CreateParameterObject(command);

			if (parameter is OracleParameter)
				parameter = OracleParameterWrap.CreateInstance(parameter as OracleParameter);

			return parameter;
		}

		public override IDbDataParameter GetParameter(IDbCommand command, NameOrIndexParameter nameOrIndex)
		{
			var parameter = base.GetParameter(command, nameOrIndex);

			if (parameter is OracleParameter)
				parameter = OracleParameterWrap.CreateInstance(parameter as OracleParameter);

			return parameter;
		}

		/// <summary>
		/// Returns connection type.
		/// </summary>
		/// <remarks>
		/// See the <see cref="DbManager.AddDataProvider(DataProviderBase)"/> method to find an example.
		/// </remarks>
		/// <seealso cref="DbManager.AddDataProvider(DataProviderBase)">AddDataManager Method</seealso>
		/// <value>An instance of the <see cref="Type"/> class.</value>
		public override Type ConnectionType
		{
			get { return typeof(OracleConnection); }
		}

		/// <summary>
		/// Returns the data provider name.
		/// </summary>
		/// <remarks>
		/// See the <see cref="DbManager.AddDataProvider(DataProviderBase)"/> method to find an example.
		/// </remarks>
		/// <seealso cref="DbManager.AddDataProvider(DataProviderBase)">AddDataProvider Method</seealso>
		/// <value>Data provider name.</value>
		public override string Name
		{
			get { return NameString; }
		}

		public override int MaxBatchSize
		{
			get { return 0; }
		}

		public override int ExecuteArray(IDbCommand command, int iterations)
		{
			var cmd = (OracleCommand)command;
			var oracleParameters = cmd.Parameters.OfType<OracleParameter>().ToArray();
			var oldCollectionTypes = oracleParameters.Select(p => p.CollectionType).ToArray();

			try
			{
				foreach (var p in oracleParameters)
				{
					p.CollectionType = OracleCollectionType.None;
				}

				cmd.ArrayBindCount = iterations;
				return cmd.ExecuteNonQuery();
			}
			finally
			{
				foreach (var p in oracleParameters.Zip(oldCollectionTypes, (p, t) => new { Param = p, CollectionType = t }))
				{
					p.Param.CollectionType = p.CollectionType;
				}

				cmd.ArrayBindCount = 0;
			}
		}

		public override ISqlProvider CreateSqlProvider()
		{
			return new OracleSqlProvider();
		}

		public override IDataReader GetDataReader(MappingSchema schema, IDataReader dataReader)
		{
			return dataReader is OracleDataReader ?
				new OracleDataReaderEx((OracleDataReader)dataReader) :
				base.GetDataReader(schema, dataReader);
		}

		class OracleDataReaderEx: DataReaderEx<OracleDataReader>
		{
			public OracleDataReaderEx(OracleDataReader rd)
				: base(rd)
			{
			}

			public override DateTimeOffset GetDateTimeOffset(int i)
			{
				var ts = DataReader.GetOracleTimeStampTZ(i);
				return new DateTimeOffset(ts.Value, ts.GetTimeZoneOffset());
			}
		}

		private string _parameterPrefix = "P";
		public  string  ParameterPrefix
		{
			get { return _parameterPrefix;  }
			set
			{
				_parameterPrefix = string.IsNullOrEmpty(value)? null:
					value.ToUpper(CultureInfo.InvariantCulture);
			}
		}

		/// <summary>
		/// One time initialization from a configuration file.
		/// </summary>
		/// <param name="attributes">Provider specific attributes.</param>
		public override void Configure(System.Collections.Specialized.NameValueCollection attributes)
		{
			var val = attributes["ParameterPrefix"];
			if (val != null)
				ParameterPrefix = val;

			base.Configure(attributes);
		}

		#region Inner types

		public class OdpMappingSchema : MappingSchema
		{
			public override DataReaderMapper CreateDataReaderMapper(IDataReader dataReader)
			{
				return new OracleDataReaderMapper(this, dataReader);
			}

			public override DataReaderMapper CreateDataReaderMapper(
				IDataReader          dataReader,
				NameOrIndexParameter nip)
			{
				return new OracleScalarDataReaderMapper(this, dataReader, nip);
			}

			public override Reflection.Extension.ExtensionList Extensions
			{
				get { return Map.DefaultSchema.Extensions;  }
				set { Map.DefaultSchema.Extensions = value; }
			}

			#region Convert

			#region Primitive Types

			[CLSCompliant(false)]
			public override SByte ConvertToSByte(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? DefaultSByteNullValue: (SByte)oraDecimal.Value;
				}

				return base.ConvertToSByte(value);
			}

			public override Int16 ConvertToInt16(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? DefaultInt16NullValue: oraDecimal.ToInt16();
				}

				return base.ConvertToInt16(value);
			}

			public override Int32 ConvertToInt32(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? DefaultInt32NullValue: oraDecimal.ToInt32();
				}

				return base.ConvertToInt32(value);
			}

			public override Int64 ConvertToInt64(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? DefaultInt64NullValue: oraDecimal.ToInt64();
				}

				return base.ConvertToInt64(value);
			}

			public override Byte ConvertToByte(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? DefaultByteNullValue: oraDecimal.ToByte();
				}

				return base.ConvertToByte(value);
			}

			[CLSCompliant(false)]
			public override UInt16 ConvertToUInt16(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? DefaultUInt16NullValue: (UInt16)oraDecimal.Value;
				}

				return base.ConvertToUInt16(value);
			}

			[CLSCompliant(false)]
			public override UInt32 ConvertToUInt32(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? DefaultUInt32NullValue: (UInt32)oraDecimal.Value;
				}

				return base.ConvertToUInt32(value);
			}

			[CLSCompliant(false)]
			public override UInt64 ConvertToUInt64(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? DefaultUInt64NullValue: (UInt64)oraDecimal.Value;
				}

				return base.ConvertToUInt64(value);
			}

			public override Single ConvertToSingle(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? DefaultSingleNullValue: oraDecimal.ToSingle();
				}

				return base.ConvertToSingle(value);
			}

			public override Double ConvertToDouble(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? DefaultDoubleNullValue: oraDecimal.ToDouble();
				}

				return base.ConvertToDouble(value);
			}

			public override Boolean ConvertToBoolean(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? DefaultBooleanNullValue: (oraDecimal.Value != 0);
				}

				return base.ConvertToBoolean(value);
			}

			public override DateTime ConvertToDateTime(object value)
			{
				if (value is OracleDate)
				{
					var oraDate = (OracleDate)value;
					return oraDate.IsNull? DefaultDateTimeNullValue: oraDate.Value;
				}

				return base.ConvertToDateTime(value);
			}

			public override Decimal ConvertToDecimal(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? DefaultDecimalNullValue: oraDecimal.Value;
				}

				return base.ConvertToDecimal(value);
			}

			public override Guid ConvertToGuid(object value)
			{
				if (value is OracleString)
				{
					var oraString = (OracleString)value;
					return oraString.IsNull? DefaultGuidNullValue: new Guid(oraString.Value);
				}

				if (value is OracleBlob)
				{
					using (var oraBlob = (OracleBlob)value)
						return oraBlob.IsNull? DefaultGuidNullValue: new Guid(oraBlob.Value);
				}

				return base.ConvertToGuid(value);
			}

			public override String ConvertToString(object value)
			{
				if (value is OracleString)
				{
					var oraString = (OracleString)value;
					return oraString.IsNull? DefaultStringNullValue: oraString.Value;
				}
#if !MANAGED
				if (value is OracleXmlType)
				{
					var oraXmlType = (OracleXmlType)value;
					return oraXmlType.IsNull ? DefaultStringNullValue : oraXmlType.Value;
				}
#endif
				if (value is OracleClob)
				{
					using (var oraClob = (OracleClob)value)
						return oraClob.IsNull? DefaultStringNullValue: oraClob.Value;
				}

				return base.ConvertToString(value);
			}

#if !MANAGED
			public override Stream ConvertToStream(object value)
			{
				if (value is OracleXmlType)
				{
					var oraXml = (OracleXmlType)value;
					return oraXml.IsNull? DefaultStreamNullValue: oraXml.GetStream();
				}

				return base.ConvertToStream(value);
			}

			public override XmlReader ConvertToXmlReader(object value)
			{
				if (value is OracleXmlType)
				{
					var oraXml = (OracleXmlType)value;
					return oraXml.IsNull? DefaultXmlReaderNullValue: oraXml.GetXmlReader();
				}

				return base.ConvertToXmlReader(value);
			}

			public override XmlDocument ConvertToXmlDocument(object value)
			{
				if (value is OracleXmlType)
				{
					var oraXml = (OracleXmlType)value;
					return oraXml.IsNull? DefaultXmlDocumentNullValue: oraXml.GetXmlDocument();
				}

				return base.ConvertToXmlDocument(value);
			}
#endif

			public override Byte[] ConvertToByteArray(object value)
			{
				if (value is OracleBlob)
				{
					using (var oraBlob = (OracleBlob)value)
						return oraBlob.IsNull? null: oraBlob.Value;
				}

				if (value is OracleBinary)
				{
					var oraBinary = (OracleBinary)value;
					return oraBinary.IsNull? null: oraBinary.Value;
				}
				
				if (value is OracleBFile)
				{
					var oraBFile = (OracleBFile)value;
					return oraBFile.IsNull? null: oraBFile.Value;
				}

				return base.ConvertToByteArray(value);
			}

			public override Char[] ConvertToCharArray(object value)
			{
				if (value is OracleString)
				{
					var oraString = (OracleString)value;
					return oraString.IsNull? null: oraString.Value.ToCharArray();
				}

				if (value is OracleClob)
				{
					using (var oraClob = (OracleClob)value)
						return oraClob.IsNull? null: oraClob.Value.ToCharArray();
				}

				return base.ConvertToCharArray(value);
			}

			#endregion

			#region Nullable Types

			[CLSCompliant(false)]
			public override SByte? ConvertToNullableSByte(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? null: (SByte?)oraDecimal.Value;
				}

				return base.ConvertToNullableSByte(value);
			}

			public override Int16? ConvertToNullableInt16(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? null: (Int16?)oraDecimal.ToInt16();
				}

				return base.ConvertToNullableInt16(value);
			}

			public override Int32? ConvertToNullableInt32(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? null: (Int32?)oraDecimal.ToInt32();
				}

				return base.ConvertToNullableInt32(value);
			}

			public override Int64? ConvertToNullableInt64(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? null: (Int64?)oraDecimal.ToInt64();
				}

				return base.ConvertToNullableInt64(value);
			}

			public override Byte? ConvertToNullableByte(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? null: (Byte?)oraDecimal.ToByte();
				}

				return base.ConvertToNullableByte(value);
			}

			[CLSCompliant(false)]
			public override UInt16? ConvertToNullableUInt16(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? null: (UInt16?)oraDecimal.Value;
				}

				return base.ConvertToNullableUInt16(value);
			}

			[CLSCompliant(false)]
			public override UInt32? ConvertToNullableUInt32(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? null: (UInt32?)oraDecimal.Value;
				}

				return base.ConvertToNullableUInt32(value);
			}

			[CLSCompliant(false)]
			public override UInt64? ConvertToNullableUInt64(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? null: (UInt64?)oraDecimal.Value;
				}

				return base.ConvertToNullableUInt64(value);
			}

			public override Single? ConvertToNullableSingle(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? null: (Single?)oraDecimal.ToSingle();
				}

				return base.ConvertToNullableSingle(value);
			}

			public override Double? ConvertToNullableDouble(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? null: (Double?)oraDecimal.ToDouble();
				}

				return base.ConvertToNullableDouble(value);
			}

			public override Boolean? ConvertToNullableBoolean(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? null: (Boolean?)(oraDecimal.Value != 0);
				}

				return base.ConvertToNullableBoolean(value);
			}

			public override DateTime? ConvertToNullableDateTime(object value)
			{
				if (value is OracleDate)
				{
					var oraDate = (OracleDate)value;
					return oraDate.IsNull? null: (DateTime?)oraDate.Value;
				}

				return base.ConvertToNullableDateTime(value);
			}

			public override Decimal? ConvertToNullableDecimal(object value)
			{
				if (value is OracleDecimal)
				{
					var oraDecimal = (OracleDecimal)value;
					return oraDecimal.IsNull? null: (Decimal?)oraDecimal.Value;
				}

				return base.ConvertToNullableDecimal(value);
			}

			public override Guid? ConvertToNullableGuid(object value)
			{
				if (value is OracleString)
				{
					var oraString = (OracleString)value;
					return oraString.IsNull? null: (Guid?)new Guid(oraString.Value);
				}

				if (value is OracleBlob)
				{
					using (var oraBlob = (OracleBlob)value)
						return oraBlob.IsNull? null: (Guid?)new Guid(oraBlob.Value);
				}

				return base.ConvertToNullableGuid(value);
			}

			#endregion

			#endregion

			public override object MapValueToEnum(object value, Type type)
			{
				if (value is OracleString)
				{
					var oracleValue = (OracleString)value;
					value = oracleValue.IsNull? null: oracleValue.Value;
				}
				else if (value is OracleDecimal)
				{
					var oracleValue = (OracleDecimal)value;
					if (oracleValue.IsNull)
						value = null;
					else 
						value = oracleValue.Value;
				}

				return base.MapValueToEnum(value, type);
			}

			public override object MapValueToEnum(object value, MemberAccessor ma)
			{
				if (value is OracleString)
				{
					var oracleValue = (OracleString)value;
					value = oracleValue.IsNull ? null : oracleValue.Value;
				}
				else if (value is OracleDecimal)
				{
					var oracleValue = (OracleDecimal)value;
					if (oracleValue.IsNull)
						value = null;
					else
						value = oracleValue.Value;
				}

				return base.MapValueToEnum(value, ma);
			}

			public override object ConvertChangeType(object value, Type conversionType)
			{
				// Handle OracleDecimal with IsNull == true case
				//
				return base.ConvertChangeType(IsNull(value)? null: value, conversionType);
			}

			public override bool IsNull(object value)
			{
				// ODP 10 does not expose this interface to public.
				//
				// return value is INullable && ((INullable)value).IsNull;

				return
					value is OracleDecimal?      ((OracleDecimal)     value).IsNull:
					value is OracleString?       ((OracleString)      value).IsNull:
					value is OracleDate?         ((OracleDate)        value).IsNull:
					value is OracleTimeStamp?    ((OracleTimeStamp)   value).IsNull:
					value is OracleTimeStampTZ?  ((OracleTimeStampTZ) value).IsNull:
					value is OracleTimeStampLTZ? ((OracleTimeStampLTZ)value).IsNull:
#if !MANAGED
					value is OracleXmlType?      ((OracleXmlType)     value).IsNull:
#endif
					value is OracleBlob?         ((OracleBlob)        value).IsNull:
					value is OracleClob?         ((OracleClob)        value).IsNull:
					value is OracleBFile?        ((OracleBFile)       value).IsNull:
					value is OracleBinary?       ((OracleBinary)      value).IsNull:
					value is OracleIntervalDS?   ((OracleIntervalDS)  value).IsNull:
					value is OracleIntervalYM?   ((OracleIntervalYM)  value).IsNull:
						base.IsNull(value);
			}
		}

		// TODO: implement via IDataReaderEx / DataReaderEx
		//
		public class OracleDataReaderMapper : DataReaderMapper
		{
			public OracleDataReaderMapper(MappingSchema mappingSchema, IDataReader dataReader)
				: base(mappingSchema, dataReader)
			{
				_dataReader = dataReader is OracleDataReaderEx?
					((OracleDataReaderEx)dataReader).DataReader:
					(OracleDataReader)dataReader;
			}

			private readonly OracleDataReader _dataReader;

			public override Type GetFieldType(int index)
			{
				var fieldType = _dataReader.GetProviderSpecificFieldType(index);

				if (fieldType != typeof(OracleBlob)
#if !MANAGED
					&& fieldType != typeof(OracleXmlType)
#endif
					)
					fieldType = _dataReader.GetFieldType(index);

				return fieldType;
			}

			public override object GetValue(object o, int index)
			{
				var fieldType = _dataReader.GetProviderSpecificFieldType(index);

#if !MANAGED
				if (fieldType == typeof(OracleXmlType))
				{
					var xml = _dataReader.GetOracleXmlType(index);
					return MappingSchema.ConvertToXmlDocument(xml);
				}
#endif
				if (fieldType == typeof(OracleBlob))
				{
					var blob = _dataReader.GetOracleBlob(index);
					return MappingSchema.ConvertToStream(blob);
				}

				return _dataReader.IsDBNull(index)? null:
					_dataReader.GetValue(index);
			}

			public override Boolean  GetBoolean(object o, int index) { return MappingSchema.ConvertToBoolean(GetValue(o, index)); }
			public override Char     GetChar   (object o, int index) { return MappingSchema.ConvertToChar   (GetValue(o, index)); }
			public override Guid     GetGuid   (object o, int index) { return MappingSchema.ConvertToGuid   (GetValue(o, index)); }

			[CLSCompliant(false)]
			public override SByte    GetSByte  (object o, int index) { return  (SByte)_dataReader.GetDecimal(index); }
			[CLSCompliant(false)]
			public override UInt16   GetUInt16 (object o, int index) { return (UInt16)_dataReader.GetDecimal(index); }
			[CLSCompliant(false)]
			public override UInt32   GetUInt32 (object o, int index) { return (UInt32)_dataReader.GetDecimal(index); }
			[CLSCompliant(false)]
			public override UInt64   GetUInt64 (object o, int index) { return (UInt64)_dataReader.GetDecimal(index); }

			public override Decimal  GetDecimal(object o, int index) { return OracleDecimal.SetPrecision(_dataReader.GetOracleDecimal(index), 28).Value; }

			public override Boolean? GetNullableBoolean(object o, int index) { return MappingSchema.ConvertToNullableBoolean(GetValue(o, index)); }
			public override Char?    GetNullableChar   (object o, int index) { return MappingSchema.ConvertToNullableChar   (GetValue(o, index)); }
			public override Guid?    GetNullableGuid   (object o, int index) { return MappingSchema.ConvertToNullableGuid   (GetValue(o, index)); }

			[CLSCompliant(false)]
			public override SByte?   GetNullableSByte  (object o, int index) { return _dataReader.IsDBNull(index)? null:  (SByte?)_dataReader.GetDecimal(index); }
			[CLSCompliant(false)]
			public override UInt16?  GetNullableUInt16 (object o, int index) { return _dataReader.IsDBNull(index)? null: (UInt16?)_dataReader.GetDecimal(index); }
			[CLSCompliant(false)]
			public override UInt32?  GetNullableUInt32 (object o, int index) { return _dataReader.IsDBNull(index)? null: (UInt32?)_dataReader.GetDecimal(index); }
			[CLSCompliant(false)]
			public override UInt64?  GetNullableUInt64 (object o, int index) { return _dataReader.IsDBNull(index)? null: (UInt64?)_dataReader.GetDecimal(index); }

			public override Decimal? GetNullableDecimal(object o, int index) { return _dataReader.IsDBNull(index)? (decimal?)null: OracleDecimal.SetPrecision(_dataReader.GetOracleDecimal(index), 28).Value; }
		}

		public class OracleScalarDataReaderMapper : ScalarDataReaderMapper
		{
			private readonly OracleDataReader _dataReader;

			public OracleScalarDataReaderMapper(
				MappingSchema        mappingSchema,
				IDataReader          dataReader,
				NameOrIndexParameter nameOrIndex)
				: base(mappingSchema, dataReader, nameOrIndex)
			{
				_dataReader = dataReader is OracleDataReaderEx?
					((OracleDataReaderEx)dataReader).DataReader:
					(OracleDataReader)dataReader;

				_fieldType = _dataReader.GetProviderSpecificFieldType(Index);

				if (_fieldType != typeof(OracleBlob)
#if !MANAGED
					&& _fieldType != typeof(OracleXmlType)
#endif
					)
					_fieldType = _dataReader.GetFieldType(Index);
			}

			private readonly Type _fieldType;

			public override Type GetFieldType(int index)
			{
				return _fieldType;
			}

			public override object GetValue(object o, int index)
			{
#if !MANAGED
				if (_fieldType == typeof(OracleXmlType))
				{
					var xml = _dataReader.GetOracleXmlType(Index);
					return MappingSchema.ConvertToXmlDocument(xml);
				}
#endif
				if (_fieldType == typeof(OracleBlob))
				{
					var blob = _dataReader.GetOracleBlob(Index);
					return MappingSchema.ConvertToStream(blob);
				}

				return _dataReader.IsDBNull(index)? null:
					_dataReader.GetValue(Index);
			}

			public override Boolean  GetBoolean(object o, int index) { return MappingSchema.ConvertToBoolean(GetValue(o, Index)); }
			public override Char     GetChar   (object o, int index) { return MappingSchema.ConvertToChar   (GetValue(o, Index)); }
			public override Guid     GetGuid   (object o, int index) { return MappingSchema.ConvertToGuid   (GetValue(o, Index)); }

			[CLSCompliant(false)]
			public override SByte    GetSByte  (object o, int index) { return  (SByte)_dataReader.GetDecimal(Index); }
			[CLSCompliant(false)]
			public override UInt16   GetUInt16 (object o, int index) { return (UInt16)_dataReader.GetDecimal(Index); }
			[CLSCompliant(false)]
			public override UInt32   GetUInt32 (object o, int index) { return (UInt32)_dataReader.GetDecimal(Index); }
			[CLSCompliant(false)]
			public override UInt64   GetUInt64 (object o, int index) { return (UInt64)_dataReader.GetDecimal(Index); }

			public override Decimal  GetDecimal(object o, int index) { return OracleDecimal.SetPrecision(_dataReader.GetOracleDecimal(Index), 28).Value; }

			public override Boolean? GetNullableBoolean(object o, int index) { return MappingSchema.ConvertToNullableBoolean(GetValue(o, Index)); }
			public override Char?    GetNullableChar   (object o, int index) { return MappingSchema.ConvertToNullableChar   (GetValue(o, Index)); }
			public override Guid?    GetNullableGuid   (object o, int index) { return MappingSchema.ConvertToNullableGuid   (GetValue(o, Index)); }

			[CLSCompliant(false)]
			public override SByte?   GetNullableSByte  (object o, int index) { return _dataReader.IsDBNull(index)? null:  (SByte?)_dataReader.GetDecimal(Index); }
			[CLSCompliant(false)]
			public override UInt16?  GetNullableUInt16 (object o, int index) { return _dataReader.IsDBNull(index)? null: (UInt16?)_dataReader.GetDecimal(Index); }
			[CLSCompliant(false)]
			public override UInt32?  GetNullableUInt32 (object o, int index) { return _dataReader.IsDBNull(index)? null: (UInt32?)_dataReader.GetDecimal(Index); }
			[CLSCompliant(false)]
			public override UInt64?  GetNullableUInt64 (object o, int index) { return _dataReader.IsDBNull(index)? null: (UInt64?)_dataReader.GetDecimal(Index); }

			public override Decimal? GetNullableDecimal(object o, int index) { return _dataReader.IsDBNull(index)? (decimal?)null: OracleDecimal.SetPrecision(_dataReader.GetOracleDecimal(Index), 28).Value; }
		}

		[Mixin(typeof(IDbDataParameter), "_oracleParameter")]
		[Mixin(typeof(IDataParameter),   "_oracleParameter")]
		[Mixin(typeof(IDisposable),      "_oracleParameter")]
		[Mixin(typeof(ICloneable),       "_oracleParameter")]
		[CLSCompliant(false)]
		public abstract class OracleParameterWrap
		{
			protected OracleParameter _oracleParameter;
			public    OracleParameter  OracleParameter
			{
				get { return _oracleParameter; }
			}

			public static IDbDataParameter CreateInstance(OracleParameter oraParameter)
			{
				var wrap = TypeAccessor<OracleParameterWrap>.CreateInstanceEx();

				wrap._oracleParameter = oraParameter;

				return (IDbDataParameter)wrap;
			}

			public override string ToString()
			{
				return _oracleParameter.ToString();
			}

			///<summary>
			///Gets or sets the value of the parameter.
			///</summary>
			///<returns>
			///An <see cref="T:System.Object"/> that is the value of the parameter.
			///The default value is null.
			///</returns>
			protected object Value
			{
#if CONVERTORACLETYPES
				[MixinOverride]
				get
				{
					object value = _oracleParameter.Value;
					if (value is OracleBinary)
					{
						OracleBinary oracleValue = (OracleBinary)value;
						return oracleValue.IsNull? null: oracleValue.Value;
					}
					if (value is OracleDate)
					{
						OracleDate oracleValue = (OracleDate)value;
						if (oracleValue.IsNull)
							return null;
						return oracleValue.Value;
					}
					if (value is OracleDecimal)
					{
						OracleDecimal oracleValue = (OracleDecimal)value;
						if (oracleValue.IsNull)
							return null;
						return oracleValue.Value;
					}
					if (value is OracleIntervalDS)
					{
						OracleIntervalDS oracleValue = (OracleIntervalDS)value;
						if (oracleValue.IsNull)
							return null;
						return oracleValue.Value;
					}
					if (value is OracleIntervalYM)
					{
						OracleIntervalYM oracleValue = (OracleIntervalYM)value;
						if (oracleValue.IsNull)
							return null;
						return oracleValue.Value;
					}
					if (value is OracleString)
					{
						OracleString oracleValue = (OracleString)value;
						return oracleValue.IsNull? null: oracleValue.Value;
					}
					if (value is OracleTimeStamp)
					{
						OracleTimeStamp oracleValue = (OracleTimeStamp)value;
						if (oracleValue.IsNull)
							return null;
						return oracleValue.Value;
					}
					if (value is OracleTimeStampLTZ)
					{
						OracleTimeStampLTZ oracleValue = (OracleTimeStampLTZ)value;
						if (oracleValue.IsNull)
							return null;
						return oracleValue.Value;
					}
					if (value is OracleTimeStampTZ)
					{
						OracleTimeStampTZ oracleValue = (OracleTimeStampTZ)value;
						if (oracleValue.IsNull)
							return null;
						return oracleValue.Value;
					}
					if (value is OracleXmlType)
					{
						OracleXmlType oracleValue = (OracleXmlType)value;
						return oracleValue.IsNull? null: oracleValue.Value;
					}

					return value;
				}
#endif
				[MixinOverride]
				set
				{
					if (null != value)
					{
						if (value is Guid)
						{
							// Fix Oracle.Net bug #6: guid type is not handled
							//
							value = ((Guid)value).ToByteArray();
						}
						else if (value is Array && !(value is byte[] || value is char[]))
						{
							_oracleParameter.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
						}
						else if (value is IConvertible)
						{
							var convertible = (IConvertible)value;
							var typeCode   = convertible.GetTypeCode();

							switch (typeCode)
							{
								case TypeCode.Boolean:
									// Fix Oracle.Net bug #7: bool type is handled wrong
									//
									value = convertible.ToByte(null);
									break;

								case TypeCode.SByte:
								case TypeCode.UInt16:
								case TypeCode.UInt32:
								case TypeCode.UInt64:
									// Fix Oracle.Net bug #8: some integer types are handled wrong
									//
									value = convertible.ToDecimal(null);
									break;

									// Fix Oracle.Net bug #10: zero-length string can not be converted to
									// ORAXML type, but null value can be.
									//
								case TypeCode.String:
									if (((string)value).Length == 0)
										value = null;
									break;

								default:
									// Fix Oracle.Net bug #5: Enum type is not handled
									//
									if (value is Enum)
									{
										// Convert a Enum value to it's underlying type.
										//
										value = System.Convert.ChangeType(value, typeCode);
									}
									break;
							}
						}
					}

					_oracleParameter.Value = value;
				}
			}
		}

		#endregion

		#region InsertBatch

		public override int InsertBatch<T>(
			DbManager      db,
			string         insertText,
			IEnumerable<T> collection,
			MemberMapper[] members,
			int            maxBatchSize,
			DbManager.ParameterProvider<T> getParameters)
		{
			var sb  = new StringBuilder();
			var sp  = new OracleSqlProvider();
			var pn  = 0;
			var n   = 0;
			var cnt = 0;
			var str = "\t" + insertText
				.Substring(0, insertText.IndexOf(") VALUES ("))
				.Substring(7)
				.Replace("\r", "")
				.Replace("\n", "")
				.Replace("\t", " ")
				.Replace("( ", "(")
				//.Replace("  ", " ")
				+ ") VALUES (";

			var parameters = new List<IDbDataParameter>();

			foreach (var item in collection)
			{
				if (sb.Length == 0)
					sb.AppendLine("INSERT ALL");

				sb.Append(str);

				foreach (var member in members)
				{
					var value = member.GetValue(item);

					if (value != null && value.GetType().IsEnum)
						value = MappingSchema.MapEnumToValue(value, true);

					if (value is Nullable<DateTime>)
						value = ((DateTime?)value).Value;

					if (value is DateTime)
					{
						var dt = (DateTime)value;
						sb.Append(string.Format("to_timestamp('{0:dd.MM.yyyy HH:mm:ss.ffffff}', 'DD.MM.YYYY HH24:MI:SS.FF6')", dt));
					}
					else if (value is string && ((string)value).Length >= 2000)
					{
						var par = db.Parameter("p" + ++pn, value);
						parameters.Add(par);
						sb.Append(":" + par.ParameterName);
					}
					else
						sp.BuildValue(sb, value);

					sb.Append(", ");
				}

				sb.Length -= 2;
				sb.AppendLine(")");

				n++;

				if (n >= maxBatchSize)
				{
					sb.AppendLine("SELECT * FROM dual");

					var sql = sb.ToString();

					if (DbManager.TraceSwitch.TraceInfo)
						DbManager.WriteTraceLine("\n" + sql.Replace("\r", ""), DbManager.TraceSwitch.DisplayName);

					cnt += db
						.SetCommand(sql, parameters.Count > 0 ? parameters.ToArray() : null)
						.ExecuteNonQuery();

					parameters.Clear();
					pn = 0;
					n  = 0;
					sb.Length = 0;
				}
			}

			if (n > 0)
			{
				sb.AppendLine("SELECT * FROM dual");

				var sql = sb.ToString();

				if (DbManager.TraceSwitch.TraceInfo)
					DbManager.WriteTraceLine("\n" + sql.Replace("\r", ""), DbManager.TraceSwitch.DisplayName);

				cnt += db
					.SetCommand(sql, parameters.Count > 0 ? parameters.ToArray() : null)
					.ExecuteNonQuery();
			}

			return cnt;
		}

		#endregion
	}
}
